@claudian-collab/protocol 4.1.2 → 4.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ part of the public surface.
|
|
|
64
64
|
|
|
65
65
|
Package SemVer, canonical wire version, and Cloud binding version are independent concepts.
|
|
66
66
|
|
|
67
|
-
- **Package version** (this `package.json`): `4.1.
|
|
67
|
+
- **Package version** (this `package.json`): `4.1.4`. Behavior-preserving implementation refactors and reviewed compatible defect corrections may use a patch release when public declarations, runtime exports, and accepted wire/binding semantics remain unchanged. Minor releases may add backward-compatible API. Removing or incompatibly changing an existing declaration, export, runtime behavior, codec, error, limit, ref rule, operation, or compatibility rule requires a major release. Package API SemVer is classified independently from the wire and Cloud binding contracts, but a compatible package addition cannot exempt a changed wire contract from a wire-version increase or a changed Cloud binding contract from a binding-version increase. The package version never signals wire compatibility by itself.
|
|
68
68
|
- **Wire version** (`COLLAB_PROTOCOL_VERSION`): currently `8`. The supported range is exactly `[8, 8]`. This is independent from Cloud binding version `4` and the application-owned LAN bindings. Any change to an existing envelope, DTO, operation payload shape, or operation definition requires a new wire-protocol version. This includes additive operations: every wire-contract change increases the canonical wire version.
|
|
69
69
|
- **Cloud binding version** (`COLLAB_CLOUD_BINDING_VERSION`): currently `4`. It versions Cloud routes, bounded transfer streams, and capabilities independently from package and wire versions. Every Cloud binding-contract change increases this version. Bindings before v4 and wire versions before v8 are unsupported rather than translated or dual-interpreted.
|
|
70
70
|
|
|
@@ -1520,7 +1520,10 @@ function validateContinuity(records) {
|
|
|
1520
1520
|
const recoveryEvidenceInvalid = direction === null
|
|
1521
1521
|
|| (direction === 'lan-to-cloud' && recovery.value.sourceEvidence === null)
|
|
1522
1522
|
|| (targetEvidenceRequired && recovery.value.targetEvidence === null)
|
|
1523
|
-
|| (
|
|
1523
|
+
|| (isCancellation
|
|
1524
|
+
? (lifecycle.value.batchRevision !== null)
|
|
1525
|
+
!== (recovery.value.stageSha256 !== null)
|
|
1526
|
+
: stageRequired !== (recovery.value.stageSha256 !== null))
|
|
1524
1527
|
|| (direction === 'lan-to-cloud'
|
|
1525
1528
|
&& recovery.value.stageSha256 !== null
|
|
1526
1529
|
&& recovery.value.stageSha256 !== lifecycle.value.checkpointSha256)
|
|
@@ -1543,33 +1546,51 @@ function validateContinuity(records) {
|
|
|
1543
1546
|
if (projectAuthorityGeneration !== expectedProjectAuthorityGeneration) {
|
|
1544
1547
|
throw invalidPayload('records');
|
|
1545
1548
|
}
|
|
1549
|
+
const hostMemberId = direction === 'lan-to-cloud'
|
|
1550
|
+
? recovery.value.sourceHostMemberId
|
|
1551
|
+
: recovery.value.targetHostMemberId;
|
|
1552
|
+
const transferMemberIds = [...memberRecords.values()]
|
|
1553
|
+
.filter(item => (item.value.status === 'active'
|
|
1554
|
+
&& item.value.memberId !== hostMemberId
|
|
1555
|
+
&& Date.parse(item.value.createdAt) <= Date.parse(recovery.value.createdAt)))
|
|
1556
|
+
.map(item => item.value.memberId);
|
|
1546
1557
|
if (!isCancellation) {
|
|
1547
|
-
const hostMemberId = direction === 'lan-to-cloud'
|
|
1548
|
-
? recovery.value.sourceHostMemberId
|
|
1549
|
-
: recovery.value.targetHostMemberId;
|
|
1550
|
-
const eligibleMemberIds = [...memberRecords.values()]
|
|
1551
|
-
.filter(item => item.value.status === 'active' && item.value.memberId !== hostMemberId)
|
|
1552
|
-
.map(item => item.value.memberId);
|
|
1553
|
-
const memberCustodyRequired = normalPhaseIndex >= 3;
|
|
1554
1558
|
const batchReceiptRequired = direction === 'cloud-to-lan'
|
|
1555
1559
|
? normalPhaseIndex >= 3
|
|
1556
1560
|
: normalPhaseIndex >= 4;
|
|
1557
1561
|
if ((batchReceipt !== undefined) !== batchReceiptRequired) {
|
|
1558
1562
|
throw invalidPayload('records');
|
|
1559
1563
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1564
|
+
}
|
|
1565
|
+
const memberCustody = transferMemberIds.map((memberId) => {
|
|
1566
|
+
const identity = `${recovery.value.transferId}:${memberId}`;
|
|
1567
|
+
const claimPresent = claims.has(identity);
|
|
1568
|
+
const envelopePresent = protectedEnvelopes.has(identity);
|
|
1569
|
+
const redemptionPresent = redemptionReceipts.has(identity);
|
|
1570
|
+
return {
|
|
1571
|
+
absent: !claimPresent && !envelopePresent && !redemptionPresent,
|
|
1572
|
+
exact: direction === 'lan-to-cloud'
|
|
1566
1573
|
? claimPresent && !envelopePresent
|
|
1567
|
-
: !claimPresent && envelopePresent !== redemptionPresent
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1574
|
+
: !claimPresent && envelopePresent !== redemptionPresent,
|
|
1575
|
+
};
|
|
1576
|
+
});
|
|
1577
|
+
const cleanupInProgress = isCancellation
|
|
1578
|
+
&& cancellationIndex === 1
|
|
1579
|
+
&& lifecycle.value.batchRevision !== null;
|
|
1580
|
+
if (cleanupInProgress) {
|
|
1581
|
+
const allAbsent = memberCustody.every(item => item.absent);
|
|
1582
|
+
const allExact = memberCustody.every(item => item.exact);
|
|
1583
|
+
if (!allAbsent && !allExact) {
|
|
1584
|
+
throw invalidPayload('records');
|
|
1571
1585
|
}
|
|
1572
1586
|
}
|
|
1587
|
+
else {
|
|
1588
|
+
const memberCustodyRequired = isCancellation
|
|
1589
|
+
? !cleanupCompleted && lifecycle.value.batchRevision !== null
|
|
1590
|
+
: normalPhaseIndex >= 3;
|
|
1591
|
+
if (memberCustody.some(item => (memberCustodyRequired ? !item.exact : !item.absent)))
|
|
1592
|
+
throw invalidPayload('records');
|
|
1593
|
+
}
|
|
1573
1594
|
if (recovery.value.targetEvidence !== null && (recovery.value.targetHostMemberId === null
|
|
1574
1595
|
|| principalBindings.get(recovery.value.targetHostMemberId)
|
|
1575
1596
|
!== recovery.value.targetEvidence.principalId))
|
|
@@ -1585,6 +1606,7 @@ function validateContinuity(records) {
|
|
|
1585
1606
|
? 'cloud-to-lan'
|
|
1586
1607
|
: 'lan-to-cloud')
|
|
1587
1608
|
|| lifecycle.value.expectedAuthorityGeneration !== recovery.value.sourceAuthority.generation
|
|
1609
|
+
|| lifecycle.value.createdAt !== recovery.value.createdAt
|
|
1588
1610
|
|| lifecycle.value.scheduledAt !== recovery.value.expiresAt
|
|
1589
1611
|
|| (recovery.value.sourceHostMemberId !== null
|
|
1590
1612
|
&& !members.has(recovery.value.sourceHostMemberId))
|
|
@@ -1608,23 +1630,39 @@ function validateContinuity(records) {
|
|
|
1608
1630
|
}
|
|
1609
1631
|
for (const key of keys.values()) {
|
|
1610
1632
|
const recovery = recoveries.get(key.value.transferId);
|
|
1633
|
+
const lifecycle = lifecycles.get(key.value.transferId);
|
|
1611
1634
|
const evidence = recovery?.value.sourceEvidence ?? recovery?.value.targetEvidence;
|
|
1612
1635
|
const isEvidenceKey = evidence !== null
|
|
1613
1636
|
&& evidence !== undefined
|
|
1614
1637
|
&& evidence.receiptKeyId === key.value.receiptKeyId
|
|
1615
1638
|
&& evidence.receiptPublicKey === key.value.receiptPublicKey;
|
|
1616
|
-
const
|
|
1617
|
-
|
|
1618
|
-
|
|
1639
|
+
const effectivePhase = lifecycle?.value.state === 'recovery-required'
|
|
1640
|
+
? lifecycle.value.recoveryFromPhase
|
|
1641
|
+
: lifecycle?.value.phase;
|
|
1642
|
+
const cancellationIndex = effectivePhase === null || effectivePhase === undefined
|
|
1643
|
+
? undefined
|
|
1644
|
+
: CANCELLATION_PHASE_INDEX.get(effectivePhase);
|
|
1645
|
+
const normalPhaseIndex = lifecycle?.value.direction === 'cloud-to-lan'
|
|
1646
|
+
? CollabAuthorityTransfer_1.COLLAB_CLOUD_TO_LAN_TRANSFER_PHASES.indexOf(effectivePhase)
|
|
1647
|
+
: -1;
|
|
1648
|
+
const isCloudSourceVerifier = recovery?.value.sourceAuthority.kind === 'cloud'
|
|
1649
|
+
&& lifecycle?.value.direction === 'cloud-to-lan'
|
|
1650
|
+
&& keysByTransfer.get(key.value.transferId)?.length === 2
|
|
1651
|
+
&& (recovery.value.relinquishmentProof !== null
|
|
1652
|
+
|| (cancellationIndex === undefined && normalPhaseIndex >= 2)
|
|
1653
|
+
|| (cancellationIndex !== undefined
|
|
1654
|
+
&& cancellationIndex <= 1
|
|
1655
|
+
&& lifecycle.value.checkpointSha256 !== null));
|
|
1619
1656
|
if (recovery === undefined
|
|
1620
1657
|
|| evidence === null
|
|
1621
1658
|
|| evidence === undefined
|
|
1622
|
-
|| (!isEvidenceKey && !
|
|
1659
|
+
|| (!isEvidenceKey && !isCloudSourceVerifier))
|
|
1623
1660
|
throw invalidPayload('records');
|
|
1624
1661
|
}
|
|
1625
1662
|
for (const claim of claims.values()) {
|
|
1626
1663
|
const lifecycle = lifecycles.get(claim.value.transferId);
|
|
1627
1664
|
const recovery = recoveries.get(claim.value.transferId);
|
|
1665
|
+
const member = memberRecords.get(claim.value.memberId);
|
|
1628
1666
|
const redemptionReceipt = redemptionReceipts.get(`${claim.value.transferId}:${claim.value.memberId}`);
|
|
1629
1667
|
const redeemedOverride = redemptionReceipt === undefined
|
|
1630
1668
|
? undefined
|
|
@@ -1633,8 +1671,9 @@ function validateContinuity(records) {
|
|
|
1633
1671
|
&& item.value.state === 'redeemed'
|
|
1634
1672
|
&& item.value.claimSha256 === redemptionReceipt.value.receipt.claimSha256
|
|
1635
1673
|
&& item.value.redemptionReceiptId === redemptionReceipt.value.receipt.receiptId));
|
|
1636
|
-
if (
|
|
1674
|
+
if (member?.value.status !== 'active'
|
|
1637
1675
|
|| recovery === undefined
|
|
1676
|
+
|| Date.parse(member.value.createdAt) > Date.parse(recovery.value.createdAt)
|
|
1638
1677
|
|| recovery.value.sourceAuthority.kind !== 'lan'
|
|
1639
1678
|
|| claim.value.memberId === recovery.value.sourceHostMemberId
|
|
1640
1679
|
|| lifecycle?.value.batchRevision !== claim.value.batchRevision
|
|
@@ -1817,10 +1856,12 @@ function validateContinuity(records) {
|
|
|
1817
1856
|
const identity = `${item.value.transferId}:${item.value.memberId}`;
|
|
1818
1857
|
const recovery = recoveries.get(item.value.transferId);
|
|
1819
1858
|
const lifecycle = lifecycles.get(item.value.transferId);
|
|
1859
|
+
const member = memberRecords.get(item.value.memberId);
|
|
1820
1860
|
if (item.recordId !== identity
|
|
1821
1861
|
|| recovery === undefined
|
|
1822
1862
|
|| lifecycle === undefined
|
|
1823
|
-
||
|
|
1863
|
+
|| member?.value.status !== 'active'
|
|
1864
|
+
|| Date.parse(member.value.createdAt) > Date.parse(recovery.value.createdAt)
|
|
1824
1865
|
|| item.value.memberId === recovery.value.targetHostMemberId
|
|
1825
1866
|
|| item.value.associatedData.authorityGeneration
|
|
1826
1867
|
!== recovery.value.sourceAuthority.generation
|
|
@@ -1510,7 +1510,10 @@ function validateContinuity(records) {
|
|
|
1510
1510
|
const recoveryEvidenceInvalid = direction === null
|
|
1511
1511
|
|| (direction === 'lan-to-cloud' && recovery.value.sourceEvidence === null)
|
|
1512
1512
|
|| (targetEvidenceRequired && recovery.value.targetEvidence === null)
|
|
1513
|
-
|| (
|
|
1513
|
+
|| (isCancellation
|
|
1514
|
+
? (lifecycle.value.batchRevision !== null)
|
|
1515
|
+
!== (recovery.value.stageSha256 !== null)
|
|
1516
|
+
: stageRequired !== (recovery.value.stageSha256 !== null))
|
|
1514
1517
|
|| (direction === 'lan-to-cloud'
|
|
1515
1518
|
&& recovery.value.stageSha256 !== null
|
|
1516
1519
|
&& recovery.value.stageSha256 !== lifecycle.value.checkpointSha256)
|
|
@@ -1533,33 +1536,51 @@ function validateContinuity(records) {
|
|
|
1533
1536
|
if (projectAuthorityGeneration !== expectedProjectAuthorityGeneration) {
|
|
1534
1537
|
throw invalidPayload('records');
|
|
1535
1538
|
}
|
|
1539
|
+
const hostMemberId = direction === 'lan-to-cloud'
|
|
1540
|
+
? recovery.value.sourceHostMemberId
|
|
1541
|
+
: recovery.value.targetHostMemberId;
|
|
1542
|
+
const transferMemberIds = [...memberRecords.values()]
|
|
1543
|
+
.filter(item => (item.value.status === 'active'
|
|
1544
|
+
&& item.value.memberId !== hostMemberId
|
|
1545
|
+
&& Date.parse(item.value.createdAt) <= Date.parse(recovery.value.createdAt)))
|
|
1546
|
+
.map(item => item.value.memberId);
|
|
1536
1547
|
if (!isCancellation) {
|
|
1537
|
-
const hostMemberId = direction === 'lan-to-cloud'
|
|
1538
|
-
? recovery.value.sourceHostMemberId
|
|
1539
|
-
: recovery.value.targetHostMemberId;
|
|
1540
|
-
const eligibleMemberIds = [...memberRecords.values()]
|
|
1541
|
-
.filter(item => item.value.status === 'active' && item.value.memberId !== hostMemberId)
|
|
1542
|
-
.map(item => item.value.memberId);
|
|
1543
|
-
const memberCustodyRequired = normalPhaseIndex >= 3;
|
|
1544
1548
|
const batchReceiptRequired = direction === 'cloud-to-lan'
|
|
1545
1549
|
? normalPhaseIndex >= 3
|
|
1546
1550
|
: normalPhaseIndex >= 4;
|
|
1547
1551
|
if ((batchReceipt !== undefined) !== batchReceiptRequired) {
|
|
1548
1552
|
throw invalidPayload('records');
|
|
1549
1553
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1554
|
+
}
|
|
1555
|
+
const memberCustody = transferMemberIds.map((memberId) => {
|
|
1556
|
+
const identity = `${recovery.value.transferId}:${memberId}`;
|
|
1557
|
+
const claimPresent = claims.has(identity);
|
|
1558
|
+
const envelopePresent = protectedEnvelopes.has(identity);
|
|
1559
|
+
const redemptionPresent = redemptionReceipts.has(identity);
|
|
1560
|
+
return {
|
|
1561
|
+
absent: !claimPresent && !envelopePresent && !redemptionPresent,
|
|
1562
|
+
exact: direction === 'lan-to-cloud'
|
|
1556
1563
|
? claimPresent && !envelopePresent
|
|
1557
|
-
: !claimPresent && envelopePresent !== redemptionPresent
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1564
|
+
: !claimPresent && envelopePresent !== redemptionPresent,
|
|
1565
|
+
};
|
|
1566
|
+
});
|
|
1567
|
+
const cleanupInProgress = isCancellation
|
|
1568
|
+
&& cancellationIndex === 1
|
|
1569
|
+
&& lifecycle.value.batchRevision !== null;
|
|
1570
|
+
if (cleanupInProgress) {
|
|
1571
|
+
const allAbsent = memberCustody.every(item => item.absent);
|
|
1572
|
+
const allExact = memberCustody.every(item => item.exact);
|
|
1573
|
+
if (!allAbsent && !allExact) {
|
|
1574
|
+
throw invalidPayload('records');
|
|
1561
1575
|
}
|
|
1562
1576
|
}
|
|
1577
|
+
else {
|
|
1578
|
+
const memberCustodyRequired = isCancellation
|
|
1579
|
+
? !cleanupCompleted && lifecycle.value.batchRevision !== null
|
|
1580
|
+
: normalPhaseIndex >= 3;
|
|
1581
|
+
if (memberCustody.some(item => (memberCustodyRequired ? !item.exact : !item.absent)))
|
|
1582
|
+
throw invalidPayload('records');
|
|
1583
|
+
}
|
|
1563
1584
|
if (recovery.value.targetEvidence !== null && (recovery.value.targetHostMemberId === null
|
|
1564
1585
|
|| principalBindings.get(recovery.value.targetHostMemberId)
|
|
1565
1586
|
!== recovery.value.targetEvidence.principalId))
|
|
@@ -1575,6 +1596,7 @@ function validateContinuity(records) {
|
|
|
1575
1596
|
? 'cloud-to-lan'
|
|
1576
1597
|
: 'lan-to-cloud')
|
|
1577
1598
|
|| lifecycle.value.expectedAuthorityGeneration !== recovery.value.sourceAuthority.generation
|
|
1599
|
+
|| lifecycle.value.createdAt !== recovery.value.createdAt
|
|
1578
1600
|
|| lifecycle.value.scheduledAt !== recovery.value.expiresAt
|
|
1579
1601
|
|| (recovery.value.sourceHostMemberId !== null
|
|
1580
1602
|
&& !members.has(recovery.value.sourceHostMemberId))
|
|
@@ -1598,23 +1620,39 @@ function validateContinuity(records) {
|
|
|
1598
1620
|
}
|
|
1599
1621
|
for (const key of keys.values()) {
|
|
1600
1622
|
const recovery = recoveries.get(key.value.transferId);
|
|
1623
|
+
const lifecycle = lifecycles.get(key.value.transferId);
|
|
1601
1624
|
const evidence = recovery?.value.sourceEvidence ?? recovery?.value.targetEvidence;
|
|
1602
1625
|
const isEvidenceKey = evidence !== null
|
|
1603
1626
|
&& evidence !== undefined
|
|
1604
1627
|
&& evidence.receiptKeyId === key.value.receiptKeyId
|
|
1605
1628
|
&& evidence.receiptPublicKey === key.value.receiptPublicKey;
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
|
|
1629
|
+
const effectivePhase = lifecycle?.value.state === 'recovery-required'
|
|
1630
|
+
? lifecycle.value.recoveryFromPhase
|
|
1631
|
+
: lifecycle?.value.phase;
|
|
1632
|
+
const cancellationIndex = effectivePhase === null || effectivePhase === undefined
|
|
1633
|
+
? undefined
|
|
1634
|
+
: CANCELLATION_PHASE_INDEX.get(effectivePhase);
|
|
1635
|
+
const normalPhaseIndex = lifecycle?.value.direction === 'cloud-to-lan'
|
|
1636
|
+
? COLLAB_CLOUD_TO_LAN_TRANSFER_PHASES.indexOf(effectivePhase)
|
|
1637
|
+
: -1;
|
|
1638
|
+
const isCloudSourceVerifier = recovery?.value.sourceAuthority.kind === 'cloud'
|
|
1639
|
+
&& lifecycle?.value.direction === 'cloud-to-lan'
|
|
1640
|
+
&& keysByTransfer.get(key.value.transferId)?.length === 2
|
|
1641
|
+
&& (recovery.value.relinquishmentProof !== null
|
|
1642
|
+
|| (cancellationIndex === undefined && normalPhaseIndex >= 2)
|
|
1643
|
+
|| (cancellationIndex !== undefined
|
|
1644
|
+
&& cancellationIndex <= 1
|
|
1645
|
+
&& lifecycle.value.checkpointSha256 !== null));
|
|
1609
1646
|
if (recovery === undefined
|
|
1610
1647
|
|| evidence === null
|
|
1611
1648
|
|| evidence === undefined
|
|
1612
|
-
|| (!isEvidenceKey && !
|
|
1649
|
+
|| (!isEvidenceKey && !isCloudSourceVerifier))
|
|
1613
1650
|
throw invalidPayload('records');
|
|
1614
1651
|
}
|
|
1615
1652
|
for (const claim of claims.values()) {
|
|
1616
1653
|
const lifecycle = lifecycles.get(claim.value.transferId);
|
|
1617
1654
|
const recovery = recoveries.get(claim.value.transferId);
|
|
1655
|
+
const member = memberRecords.get(claim.value.memberId);
|
|
1618
1656
|
const redemptionReceipt = redemptionReceipts.get(`${claim.value.transferId}:${claim.value.memberId}`);
|
|
1619
1657
|
const redeemedOverride = redemptionReceipt === undefined
|
|
1620
1658
|
? undefined
|
|
@@ -1623,8 +1661,9 @@ function validateContinuity(records) {
|
|
|
1623
1661
|
&& item.value.state === 'redeemed'
|
|
1624
1662
|
&& item.value.claimSha256 === redemptionReceipt.value.receipt.claimSha256
|
|
1625
1663
|
&& item.value.redemptionReceiptId === redemptionReceipt.value.receipt.receiptId));
|
|
1626
|
-
if (
|
|
1664
|
+
if (member?.value.status !== 'active'
|
|
1627
1665
|
|| recovery === undefined
|
|
1666
|
+
|| Date.parse(member.value.createdAt) > Date.parse(recovery.value.createdAt)
|
|
1628
1667
|
|| recovery.value.sourceAuthority.kind !== 'lan'
|
|
1629
1668
|
|| claim.value.memberId === recovery.value.sourceHostMemberId
|
|
1630
1669
|
|| lifecycle?.value.batchRevision !== claim.value.batchRevision
|
|
@@ -1807,10 +1846,12 @@ function validateContinuity(records) {
|
|
|
1807
1846
|
const identity = `${item.value.transferId}:${item.value.memberId}`;
|
|
1808
1847
|
const recovery = recoveries.get(item.value.transferId);
|
|
1809
1848
|
const lifecycle = lifecycles.get(item.value.transferId);
|
|
1849
|
+
const member = memberRecords.get(item.value.memberId);
|
|
1810
1850
|
if (item.recordId !== identity
|
|
1811
1851
|
|| recovery === undefined
|
|
1812
1852
|
|| lifecycle === undefined
|
|
1813
|
-
||
|
|
1853
|
+
|| member?.value.status !== 'active'
|
|
1854
|
+
|| Date.parse(member.value.createdAt) > Date.parse(recovery.value.createdAt)
|
|
1814
1855
|
|| item.value.memberId === recovery.value.targetHostMemberId
|
|
1815
1856
|
|| item.value.associatedData.authorityGeneration
|
|
1816
1857
|
!== recovery.value.sourceAuthority.generation
|