@claudian-collab/protocol 3.3.1 → 4.0.0
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 +12 -28
- package/dist/CollabCloudBinding.d.ts +1 -1
- package/dist/CollabCloudBinding.js +11 -11
- package/dist/CollabCloudProjectSnapshot.d.ts +1 -0
- package/dist/CollabCloudProjectSnapshot.js +2 -0
- package/dist/CollabConstants.d.ts +1 -1
- package/dist/CollabConstants.js +1 -1
- package/dist/CollabProjectBackupCheckpoint.js +42 -106
- package/dist/CollabProjectCheckpoint.d.ts +25 -0
- package/dist/CollabProjectCheckpoint.js +55 -25
- package/dist/CollabProjectMembership.d.ts +3 -5
- package/dist/CollabProjectMembership.js +27 -9
- package/dist/esm/CollabCloudBinding.mjs +11 -11
- package/dist/esm/CollabCloudProjectSnapshot.mjs +2 -0
- package/dist/esm/CollabConstants.mjs +1 -1
- package/dist/esm/CollabProjectBackupCheckpoint.mjs +43 -107
- package/dist/esm/CollabProjectCheckpoint.mjs +50 -25
- package/dist/esm/CollabProjectMembership.mjs +27 -9
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ or compatibility rules.
|
|
|
20
20
|
- Shared limits that client and server must agree on (`COLLAB_LIMITS`)
|
|
21
21
|
- Protocol-version negotiation (`COLLAB_PROTOCOL_VERSION`, envelope decoding)
|
|
22
22
|
- Canonical Project checkpoint profiles, logical records, authority-generation transfer, exact membership claims, target-signed redemption receipts, offline backup operational continuity, and Project retirement contracts
|
|
23
|
-
- Cloud binding
|
|
23
|
+
- Cloud binding v3 route construction/matching, bounded checkpoint streams, capability negotiation, strict success/error envelopes, private-development bootstrap, Project snapshot, and redacted event contracts
|
|
24
24
|
- Markdown-derived Ticket-reference and Member-mention semantics shared by
|
|
25
25
|
client rendering and authority derivation
|
|
26
26
|
|
|
@@ -37,7 +37,9 @@ fixed part of the detail so the whole detail stays within
|
|
|
37
37
|
has one compatible final-serialization limit. Request comments and Ticket
|
|
38
38
|
accepted relations also have shared authority-enforced total limits; summary
|
|
39
39
|
counts let complete consumers reject partial or cross-snapshot assembly.
|
|
40
|
-
Cloud binding
|
|
40
|
+
Cloud binding v3 retains the authority-neutral Project snapshot, nine redacted durable event kinds plus `snapshot.required`, eighteen capability tokens, the exact ordinary Project/Git route catalog, bounded authority-transfer artifact routes, and six private-development bootstrap bindings. Wire v7 adds the required logical Project generation, state-consistent imported-claim generation, and complete reissued-claim transfer identity to the existing Cloud Project-membership read contracts. The shared checkpoint container is exactly `checkpoint.json`, `coordination.ndjson`, and `repository.bundle`; authority-transfer and export use wire-visible coordination format v1, and offline backup uses format v3. Backup v3 adds Project invitations, protected invitation and transferred-claim override envelopes, manager-responsibility offers, membership recovery journals, secret-replay tombstones, and compacted Manager-responsibility idempotency tombstones to the existing continuity records. Portable profiles exclude engine storage, paths, credentials, tokens, private keys, and operational refs, and backups exclude plaintext invitation secrets, raw claims, and private keys. Domain-separated canonical UTF-8 inputs define protected-claim AEAD associated data and bind every declared field of transferred-membership redemption receipts and authority-relinquishment proofs before consumer-owned Ed25519 signing or verification. Existing LAN v9 Join, invitation, endpoint, membership lifecycle, Host-transfer, snapshot, event, and HTTP bindings remain independently application-owned; the dedicated LAN authority-transfer transport binds these shared payloads without creating another shared operation registry.
|
|
41
|
+
|
|
42
|
+
Backup coordination artifacts are limited to 256 MiB of actual UTF-8 bytes, including the final newline. The decoder, encoder, and consistency checker apply this same inclusive limit without rewriting principals into an intermediate format. Valid current-format artifacts at the limit are accepted; an additional byte is rejected.
|
|
41
43
|
|
|
42
44
|
## Usage
|
|
43
45
|
|
|
@@ -62,31 +64,9 @@ part of the public surface.
|
|
|
62
64
|
|
|
63
65
|
Package SemVer, canonical wire version, and Cloud binding version are independent concepts.
|
|
64
66
|
|
|
65
|
-
- **Package version** (this `package.json`): `
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
declaration, export, runtime behavior, codec, error, limit, ref rule,
|
|
69
|
-
operation, or compatibility rule requires a major release. An independently
|
|
70
|
-
proven additive control operation may use a package minor release without a
|
|
71
|
-
wire-version increase; existing operation declarations and fixtures must stay
|
|
72
|
-
exact. Package 3 and wire 6 support exact transfer-claim batches and a
|
|
73
|
-
source-pinned target receipt verifier. Package 3.3 adds the Cloud Project-membership
|
|
74
|
-
operation family and capability tokens and directly replaces the pre-production
|
|
75
|
-
offline backup contract with coordination format v3, without changing
|
|
76
|
-
authority-transfer/export format v1, canonical wire v6, or Cloud binding v2.
|
|
77
|
-
Backup v2 has no supported decoder or migration path. The pre-production
|
|
78
|
-
`3.3.1` corrective patch adds only the missing backup-v3 tombstone needed to
|
|
79
|
-
compact terminal Manager-responsibility offers and exact responses without
|
|
80
|
-
allowing their idempotency identities to be reused. The package version never
|
|
81
|
-
signals wire compatibility by itself.
|
|
82
|
-
- **Wire version** (`COLLAB_PROTOCOL_VERSION`): currently `6`. The supported
|
|
83
|
-
range is exactly `[6, 6]`. This is independent from Cloud binding version `2` and the existing application
|
|
84
|
-
LAN control version `9`. Any change to an existing envelope, DTO, operation
|
|
85
|
-
payload shape, or operation definition is wire-breaking and requires a new
|
|
86
|
-
wire-protocol version. A strictly additive operation may remain on the
|
|
87
|
-
current wire version when the compatibility classifier proves that every
|
|
88
|
-
existing operation contract is unchanged.
|
|
89
|
-
- **Cloud binding version** (`COLLAB_CLOUD_BINDING_VERSION`): currently `2`. It versions Cloud routes, bounded transfer streams, and capabilities independently from package and wire versions. Binding v1 and wire versions before v6 are unsupported rather than translated or dual-interpreted.
|
|
67
|
+
- **Package version** (this `package.json`): `4.0.0`. 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
|
+
- **Wire version** (`COLLAB_PROTOCOL_VERSION`): currently `7`. The supported range is exactly `[7, 7]`. This is independent from Cloud binding version `3` and the existing application LAN control version `9`. 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
|
+
- **Cloud binding version** (`COLLAB_CLOUD_BINDING_VERSION`): currently `3`. 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 v3 and wire versions before v7 are unsupported rather than translated or dual-interpreted.
|
|
90
70
|
|
|
91
71
|
### Compatibility behavior
|
|
92
72
|
|
|
@@ -105,14 +85,18 @@ Compatibility is tested with executable fixtures in this package
|
|
|
105
85
|
(`tests/`), and cross-repository contract fixtures pin client and server to
|
|
106
86
|
the same behavior.
|
|
107
87
|
|
|
88
|
+
The compatibility snapshot also fingerprints implementation tokens. Those fingerprints detect edits but cannot establish whether behavior changed. A reviewed implementation-only refactor or approved compatible defect correction uses `compatibility-review.json`, generated with `npm run check:compatibility -- --base <base-sha> --record-implementation-only-review "<reviewed reason>"` after updating the generated snapshot and characterizing the public seams. The reason distinguishes preserved behavior from any approved correction and its executable evidence. The record binds the exact base and candidate snapshots; the classifier independently rejects any changed public declaration, export, or wire/binding semantic fact. Missing or stale evidence cannot authorize an implementation change, and the record never applies to another version or later edit merely because an earlier change was accepted. This review requirement is general, not a prelaunch or one-release compatibility exception, and it is not an automated proof of behavioral equivalence.
|
|
89
|
+
|
|
108
90
|
## Development
|
|
109
91
|
|
|
110
92
|
```bash
|
|
111
93
|
npm run build # compile src/ -> dist/ (CommonJS + declarations)
|
|
112
94
|
npm test # package unit tests
|
|
113
95
|
npm run check:compatibility
|
|
114
|
-
npm run
|
|
96
|
+
npm run release:candidate # build and verify the reviewed release artifact
|
|
97
|
+
npm run verify:pack # smoke-test that exact artifact in a clean consumer
|
|
115
98
|
```
|
|
116
99
|
|
|
117
100
|
`dist/` is generated output and is never committed. The packed artifact is
|
|
118
101
|
reproducible from source: `npm pack` rebuilds via `prepack`.
|
|
102
|
+
The full verification gate constructs one release candidate and uses those same reviewed tarball bytes for clean-consumer smoke tests and publication.
|
|
@@ -2,7 +2,7 @@ import { COLLAB_PROTOCOL_VERSION } from './CollabConstants';
|
|
|
2
2
|
import { CollabError, type CollabDiagnosticContext, type CollabErrorCode, type CollabRecoveryAction } from './CollabError';
|
|
3
3
|
import { COLLAB_PROJECT_CHECKPOINT_ARTIFACTS } from './CollabProjectCheckpoint';
|
|
4
4
|
import type { CollabIsoTimestamp, CollabProjectId } from './types';
|
|
5
|
-
export declare const COLLAB_CLOUD_BINDING_VERSION:
|
|
5
|
+
export declare const COLLAB_CLOUD_BINDING_VERSION: 3;
|
|
6
6
|
export declare const COLLAB_CLOUD_CAPABILITY_DOCUMENT_SCHEMA_VERSION: 2;
|
|
7
7
|
export declare const COLLAB_CLOUD_CAPABILITIES: readonly ["accept", "authority-transfer", "cloud-imported-membership-claims", "cloud-project-create", "cloud-project-invitations", "cloud-project-join", "cloud-project-leave", "cloud-project-manager-responsibility", "cloud-project-membership", "development-bootstrap", "git-receive-pack-personal-ref", "git-upload-pack", "project-checkpoint-export", "project-events", "project-retirement", "project-snapshot", "requests", "tickets"];
|
|
8
8
|
export type CollabCloudCapability = typeof COLLAB_CLOUD_CAPABILITIES[number];
|
|
@@ -23,7 +23,7 @@ const CollabControlOperationCodecs_1 = require("./CollabControlOperationCodecs")
|
|
|
23
23
|
const CollabError_1 = require("./CollabError");
|
|
24
24
|
const CollabValidation_1 = require("./CollabValidation");
|
|
25
25
|
const CollabProjectCheckpoint_1 = require("./CollabProjectCheckpoint");
|
|
26
|
-
exports.COLLAB_CLOUD_BINDING_VERSION =
|
|
26
|
+
exports.COLLAB_CLOUD_BINDING_VERSION = 3;
|
|
27
27
|
exports.COLLAB_CLOUD_CAPABILITY_DOCUMENT_SCHEMA_VERSION = 2;
|
|
28
28
|
exports.COLLAB_CLOUD_CAPABILITIES = Object.freeze([
|
|
29
29
|
'accept',
|
|
@@ -188,7 +188,7 @@ function collabCloudProjectOperationRoute(projectId, operation) {
|
|
|
188
188
|
assertProjectId(projectId);
|
|
189
189
|
if (!isCloudJsonOperation(operation))
|
|
190
190
|
invalidRoute();
|
|
191
|
-
return route('POST', `/
|
|
191
|
+
return route('POST', `/v3/projects/${projectId}/operations/${operation}`, {
|
|
192
192
|
kind: 'project-operation',
|
|
193
193
|
operation,
|
|
194
194
|
projectId,
|
|
@@ -198,7 +198,7 @@ function collabCloudProjectEventsRoute(projectId, afterSequence) {
|
|
|
198
198
|
assertProjectId(projectId);
|
|
199
199
|
if (!Number.isSafeInteger(afterSequence) || afterSequence < 0)
|
|
200
200
|
invalidRoute();
|
|
201
|
-
return route('GET', `/
|
|
201
|
+
return route('GET', `/v3/projects/${projectId}/events?afterSequence=${afterSequence}`, {
|
|
202
202
|
afterSequence,
|
|
203
203
|
kind: 'project-events',
|
|
204
204
|
projectId,
|
|
@@ -209,11 +209,11 @@ function collabCloudGitRoute(projectId, routeKind, service) {
|
|
|
209
209
|
if (routeKind === 'info-refs') {
|
|
210
210
|
if (service !== 'git-upload-pack' && service !== 'git-receive-pack')
|
|
211
211
|
invalidRoute();
|
|
212
|
-
return route('GET', `/
|
|
212
|
+
return route('GET', `/v3/projects/${projectId}/repository.git/info/refs?service=${service}`, { kind: 'git-info-refs', projectId, service });
|
|
213
213
|
}
|
|
214
214
|
if (service !== undefined)
|
|
215
215
|
invalidRoute();
|
|
216
|
-
return route('POST', `/
|
|
216
|
+
return route('POST', `/v3/projects/${projectId}/repository.git/${routeKind}`, {
|
|
217
217
|
kind: routeKind,
|
|
218
218
|
projectId,
|
|
219
219
|
});
|
|
@@ -224,7 +224,7 @@ function collabCloudAuthorityTransferArtifactRoute(projectId, transferId, direct
|
|
|
224
224
|
if (direction !== 'download' && direction !== 'upload'
|
|
225
225
|
|| !COLLAB_PROJECT_CHECKPOINT_ARTIFACTS_SET.has(artifact))
|
|
226
226
|
invalidRoute();
|
|
227
|
-
return route(direction === 'upload' ? 'PUT' : 'GET', `/
|
|
227
|
+
return route(direction === 'upload' ? 'PUT' : 'GET', `/v3/projects/${projectId}/authority-transfers/${transferId}/checkpoint/${artifact}`, {
|
|
228
228
|
artifact,
|
|
229
229
|
direction,
|
|
230
230
|
kind: 'authority-transfer-artifact',
|
|
@@ -237,7 +237,7 @@ function collabCloudProjectCheckpointExportArtifactRoute(projectId, exportId, ar
|
|
|
237
237
|
assertAttemptId(exportId);
|
|
238
238
|
if (!COLLAB_PROJECT_CHECKPOINT_ARTIFACTS_SET.has(artifact))
|
|
239
239
|
invalidRoute();
|
|
240
|
-
return route('GET', `/
|
|
240
|
+
return route('GET', `/v3/projects/${projectId}/checkpoint-exports/${exportId}/checkpoint/${artifact}`, {
|
|
241
241
|
artifact,
|
|
242
242
|
exportId,
|
|
243
243
|
kind: 'project-checkpoint-export-artifact',
|
|
@@ -249,7 +249,7 @@ function collabCloudProjectCheckpointExportRoute(projectId, exportId, operation)
|
|
|
249
249
|
assertAttemptId(exportId);
|
|
250
250
|
if (operation !== 'begin' && operation !== 'status')
|
|
251
251
|
invalidRoute();
|
|
252
|
-
return route(operation === 'begin' ? 'POST' : 'GET', `/
|
|
252
|
+
return route(operation === 'begin' ? 'POST' : 'GET', `/v3/projects/${projectId}/checkpoint-exports/${exportId}`, { exportId, kind: 'project-checkpoint-export', operation, projectId });
|
|
253
253
|
}
|
|
254
254
|
function decodeCollabCloudProjectCheckpointExportStatus(value) {
|
|
255
255
|
const source = exactRecord(value, 'checkpointExport', [
|
|
@@ -287,7 +287,7 @@ function decodeCollabCloudProjectCheckpointExportStatus(value) {
|
|
|
287
287
|
});
|
|
288
288
|
}
|
|
289
289
|
function collabDevelopmentBootstrapRoute(operation, attemptId) {
|
|
290
|
-
const base = '/
|
|
290
|
+
const base = '/v3/development/bootstrap/attempts';
|
|
291
291
|
if (operation === 'beginDevelopmentBootstrap') {
|
|
292
292
|
if (attemptId !== undefined)
|
|
293
293
|
invalidRoute();
|
|
@@ -346,7 +346,7 @@ function matchCollabCloudRoute(method, target) {
|
|
|
346
346
|
&& url.pathname === '/collab/capabilities'
|
|
347
347
|
&& exactQuery(url, []))
|
|
348
348
|
return { kind: 'capabilities' };
|
|
349
|
-
if (segments[0] === '
|
|
349
|
+
if (segments[0] === 'v3'
|
|
350
350
|
&& segments[1] === 'projects'
|
|
351
351
|
&& (0, CollabValidation_1.isCollabProjectId)(segments[2])) {
|
|
352
352
|
const projectId = segments[2];
|
|
@@ -425,7 +425,7 @@ function matchCollabCloudRoute(method, target) {
|
|
|
425
425
|
&& exactQuery(url, []))
|
|
426
426
|
return { kind: segments[4], projectId };
|
|
427
427
|
}
|
|
428
|
-
if (segments[0] !== '
|
|
428
|
+
if (segments[0] !== 'v3'
|
|
429
429
|
|| segments[1] !== 'development'
|
|
430
430
|
|| segments[2] !== 'bootstrap'
|
|
431
431
|
|| segments[3] !== 'attempts'
|
|
@@ -2,6 +2,7 @@ import { COLLAB_MAIN_REF } from './CollabConstants';
|
|
|
2
2
|
import type { CollabDecodeResult } from './CollabProtocol';
|
|
3
3
|
import type { CollabChangeRequest, CollabGitOid, CollabIsoTimestamp, CollabMemberId, CollabProjectId, CollabRole, CollabTicketSummary } from './types';
|
|
4
4
|
export interface CollabCloudProjectSummary {
|
|
5
|
+
readonly authorityGeneration: number;
|
|
5
6
|
readonly createdAt: CollabIsoTimestamp;
|
|
6
7
|
readonly expectedMainOid: CollabGitOid;
|
|
7
8
|
readonly id: CollabProjectId;
|
|
@@ -67,6 +67,7 @@ function positiveInteger(source, field) {
|
|
|
67
67
|
}
|
|
68
68
|
function decodeProject(value) {
|
|
69
69
|
const source = exactRecord(value, 'project', [
|
|
70
|
+
'authorityGeneration',
|
|
70
71
|
'createdAt',
|
|
71
72
|
'expectedMainOid',
|
|
72
73
|
'id',
|
|
@@ -76,6 +77,7 @@ function decodeProject(value) {
|
|
|
76
77
|
if (source.mainRef !== CollabConstants_1.COLLAB_MAIN_REF)
|
|
77
78
|
throw invalidPayload('mainRef');
|
|
78
79
|
return {
|
|
80
|
+
authorityGeneration: positiveInteger(source, 'authorityGeneration'),
|
|
79
81
|
createdAt: timestamp(source, 'createdAt'),
|
|
80
82
|
expectedMainOid: stringField(source, 'expectedMainOid', 64, CollabValidation_1.isCollabGitOid),
|
|
81
83
|
id: stringField(source, 'id', 64, CollabValidation_1.isCollabProjectId),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const COLLAB_PROTOCOL_VERSION:
|
|
1
|
+
export declare const COLLAB_PROTOCOL_VERSION: 7;
|
|
2
2
|
export declare const COLLAB_MAIN_REF: "refs/heads/main";
|
|
3
3
|
export declare const COLLAB_MEMBER_REF_PREFIX: "refs/heads/members/";
|
|
4
4
|
export declare const COLLAB_LIMITS: Readonly<{
|
package/dist/CollabConstants.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.COLLAB_LIMITS = exports.COLLAB_MEMBER_REF_PREFIX = exports.COLLAB_MAIN_REF = exports.COLLAB_PROTOCOL_VERSION = void 0;
|
|
4
|
-
exports.COLLAB_PROTOCOL_VERSION =
|
|
4
|
+
exports.COLLAB_PROTOCOL_VERSION = 7;
|
|
5
5
|
exports.COLLAB_MAIN_REF = 'refs/heads/main';
|
|
6
6
|
exports.COLLAB_MEMBER_REF_PREFIX = 'refs/heads/members/';
|
|
7
7
|
exports.COLLAB_LIMITS = Object.freeze({
|
|
@@ -208,65 +208,19 @@ function absoluteTargetUrl(source) {
|
|
|
208
208
|
function principal(source, field) {
|
|
209
209
|
return token(source, field, value => (typeof value === 'string' && PRINCIPAL_PATTERN.test(value)));
|
|
210
210
|
}
|
|
211
|
-
function
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (source.kind === 'principal-binding') {
|
|
215
|
-
return {
|
|
216
|
-
...source,
|
|
217
|
-
value: { ...value, principalId: `principal_${String(value.memberId)}` },
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
if (source.kind === 'terminal-responder' && Array.isArray(value.acknowledgements)) {
|
|
221
|
-
return {
|
|
222
|
-
...source,
|
|
223
|
-
value: {
|
|
224
|
-
...value,
|
|
225
|
-
acknowledgements: value.acknowledgements.map(item => ({
|
|
226
|
-
...record(item, 'acknowledgement'),
|
|
227
|
-
principalId: `principal_${String(record(item, 'acknowledgement').memberId)}`,
|
|
228
|
-
})),
|
|
229
|
-
},
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
return item;
|
|
233
|
-
}
|
|
234
|
-
function restoreBackupBasePrincipals(source, base) {
|
|
235
|
-
if (base.kind === 'principal-binding') {
|
|
236
|
-
const value = exactRecord(source.value, 'value', [
|
|
237
|
-
'boundAt', 'memberId', 'principalId', 'projectId',
|
|
238
|
-
]);
|
|
239
|
-
return {
|
|
240
|
-
...base,
|
|
241
|
-
value: { ...base.value, principalId: principal(value, 'principalId') },
|
|
242
|
-
};
|
|
243
|
-
}
|
|
211
|
+
function validateBackupBasePrincipals(base) {
|
|
212
|
+
if (base.kind === 'principal-binding')
|
|
213
|
+
principal(base.value, 'principalId');
|
|
244
214
|
if (base.kind === 'terminal-responder') {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
'
|
|
249
|
-
'operation',
|
|
250
|
-
'operationId',
|
|
251
|
-
'projectId',
|
|
252
|
-
'responseJson',
|
|
253
|
-
]);
|
|
254
|
-
const sourceAcknowledgements = value.acknowledgements;
|
|
255
|
-
if (!Array.isArray(sourceAcknowledgements)
|
|
256
|
-
|| sourceAcknowledgements.length !== base.value.acknowledgements.length)
|
|
257
|
-
throw invalidPayload('acknowledgements');
|
|
258
|
-
const acknowledgements = base.value.acknowledgements.map((item, index) => {
|
|
259
|
-
const original = exactRecord(sourceAcknowledgements[index], 'acknowledgement', [
|
|
260
|
-
'acknowledgedAt', 'memberId', 'principalId',
|
|
261
|
-
]);
|
|
262
|
-
return { ...item, principalId: principal(original, 'principalId') };
|
|
263
|
-
});
|
|
264
|
-
if (new Set(acknowledgements.map(item => item.principalId)).size !== acknowledgements.length) {
|
|
265
|
-
throw invalidPayload('acknowledgements');
|
|
215
|
+
for (const acknowledgement of base.value.acknowledgements) {
|
|
216
|
+
if (acknowledgement.principalId === undefined)
|
|
217
|
+
throw invalidPayload('acknowledgement');
|
|
218
|
+
principal(acknowledgement, 'principalId');
|
|
266
219
|
}
|
|
267
|
-
|
|
220
|
+
if (new Set(base.value.acknowledgements.map(item => item.principalId)).size
|
|
221
|
+
!== base.value.acknowledgements.length)
|
|
222
|
+
throw invalidPayload('acknowledgements');
|
|
268
223
|
}
|
|
269
|
-
return base;
|
|
270
224
|
}
|
|
271
225
|
function repositoryPublicationRef(value) {
|
|
272
226
|
const source = exactRecord(value, 'ref', ['name', 'oid']);
|
|
@@ -2010,55 +1964,18 @@ function validateContinuity(records) {
|
|
|
2010
1964
|
}
|
|
2011
1965
|
}
|
|
2012
1966
|
}
|
|
2013
|
-
function
|
|
1967
|
+
function decodeCollabProjectBackupCheckpointManifest(value) {
|
|
2014
1968
|
const source = record(value, 'manifest');
|
|
2015
1969
|
if (source.profile !== 'backup'
|
|
2016
1970
|
|| source.coordinationFormatVersion !== exports.COLLAB_PROJECT_BACKUP_COORDINATION_FORMAT_VERSION)
|
|
2017
1971
|
throw invalidPayload('manifest');
|
|
2018
|
-
return (0, CollabProjectCheckpoint_1.
|
|
2019
|
-
...source,
|
|
2020
|
-
coordinationFormatVersion: CollabProjectCheckpoint_1.COLLAB_PROJECT_COORDINATION_FORMAT_VERSION,
|
|
2021
|
-
});
|
|
2022
|
-
}
|
|
2023
|
-
function decodeCollabProjectBackupCheckpointManifest(value) {
|
|
2024
|
-
const decoded = manifestWithFormatOne(value);
|
|
2025
|
-
return {
|
|
2026
|
-
artifacts: decoded.artifacts,
|
|
2027
|
-
coordinationFormatVersion: exports.COLLAB_PROJECT_BACKUP_COORDINATION_FORMAT_VERSION,
|
|
2028
|
-
createdAt: decoded.createdAt,
|
|
2029
|
-
expectedMainOid: decoded.expectedMainOid,
|
|
2030
|
-
gitObjectFormat: decoded.gitObjectFormat,
|
|
2031
|
-
manifestSchemaVersion: decoded.manifestSchemaVersion,
|
|
2032
|
-
manifestSha256: decoded.manifestSha256,
|
|
2033
|
-
operationId: decoded.operationId,
|
|
2034
|
-
profile: 'backup',
|
|
2035
|
-
projectId: decoded.projectId,
|
|
2036
|
-
protocolVersion: decoded.protocolVersion,
|
|
2037
|
-
refs: decoded.refs,
|
|
2038
|
-
sourceAuthority: decoded.sourceAuthority,
|
|
2039
|
-
targetAuthority: decoded.targetAuthority,
|
|
2040
|
-
};
|
|
1972
|
+
return (0, CollabProjectCheckpoint_1.decodeCheckpointManifestFields)(source, exports.COLLAB_PROJECT_BACKUP_COORDINATION_FORMAT_VERSION);
|
|
2041
1973
|
}
|
|
2042
1974
|
function encodeCollabProjectBackupCheckpointManifestCanonicalJson(manifest) {
|
|
2043
1975
|
return JSON.stringify(decodeCollabProjectBackupCheckpointManifest(manifest));
|
|
2044
1976
|
}
|
|
2045
1977
|
function encodeCollabProjectBackupCheckpointManifestDigestInput(manifest) {
|
|
2046
|
-
|
|
2047
|
-
return JSON.stringify({
|
|
2048
|
-
artifacts: decoded.artifacts,
|
|
2049
|
-
coordinationFormatVersion: decoded.coordinationFormatVersion,
|
|
2050
|
-
createdAt: decoded.createdAt,
|
|
2051
|
-
expectedMainOid: decoded.expectedMainOid,
|
|
2052
|
-
gitObjectFormat: decoded.gitObjectFormat,
|
|
2053
|
-
manifestSchemaVersion: decoded.manifestSchemaVersion,
|
|
2054
|
-
operationId: decoded.operationId,
|
|
2055
|
-
profile: decoded.profile,
|
|
2056
|
-
projectId: decoded.projectId,
|
|
2057
|
-
protocolVersion: decoded.protocolVersion,
|
|
2058
|
-
refs: decoded.refs,
|
|
2059
|
-
sourceAuthority: decoded.sourceAuthority,
|
|
2060
|
-
targetAuthority: decoded.targetAuthority,
|
|
2061
|
-
});
|
|
1978
|
+
return (0, CollabProjectCheckpoint_1.encodeCheckpointManifestDigestInput)(decodeCollabProjectBackupCheckpointManifest(manifest));
|
|
2062
1979
|
}
|
|
2063
1980
|
function decodeCollabProjectBackupCheckpointCoordinationNdjson(value) {
|
|
2064
1981
|
if (typeof value !== 'string'
|
|
@@ -2078,22 +1995,43 @@ function decodeCollabProjectBackupCheckpointCoordinationNdjson(value) {
|
|
|
2078
1995
|
}
|
|
2079
1996
|
if (JSON.stringify(item) !== line)
|
|
2080
1997
|
throw invalidPayload('coordination');
|
|
2081
|
-
return {
|
|
1998
|
+
return {
|
|
1999
|
+
envelope: recordEnvelope(item),
|
|
2000
|
+
item,
|
|
2001
|
+
base: undefined,
|
|
2002
|
+
};
|
|
2082
2003
|
});
|
|
2083
2004
|
const baseItems = parsed.filter(({ envelope }) => (!CONTINUITY_KIND_SET.has(envelope.kind) && envelope.kind !== 'idempotency-result'));
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2005
|
+
for (const { envelope } of baseItems) {
|
|
2006
|
+
const value = record(envelope.source.value, 'value');
|
|
2007
|
+
if (envelope.kind === 'terminal-responder' && Array.isArray(value.acknowledgements)) {
|
|
2008
|
+
for (const acknowledgement of value.acknowledgements)
|
|
2009
|
+
record(acknowledgement, 'acknowledgement');
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
const baseRecords = [];
|
|
2013
|
+
for (const entry of baseItems) {
|
|
2014
|
+
entry.base = (0, CollabProjectCheckpoint_1.decodeCheckpointRecord)(entry.item, 'deferred');
|
|
2015
|
+
if (JSON.stringify(entry.base) !== JSON.stringify(entry.item)
|
|
2016
|
+
|| (entry.base.kind === 'principal-binding' && entry.base.value.principalId === undefined)) {
|
|
2017
|
+
throw invalidPayload('coordination');
|
|
2018
|
+
}
|
|
2019
|
+
baseRecords.push(entry.base);
|
|
2020
|
+
}
|
|
2021
|
+
if (baseRecords.length === 0)
|
|
2022
|
+
throw invalidPayload('coordination');
|
|
2023
|
+
(0, CollabProjectCheckpoint_1.validateCheckpointRecordSequence)(baseRecords, 'backup', 'member');
|
|
2024
|
+
const decoded = parsed.map(({ envelope, base }) => {
|
|
2087
2025
|
if (envelope.kind === 'idempotency-result') {
|
|
2088
2026
|
return idempotencyResultRecord(envelope.source, envelope.recordId, envelope.revision);
|
|
2089
2027
|
}
|
|
2090
2028
|
if (CONTINUITY_KIND_SET.has(envelope.kind)) {
|
|
2091
2029
|
return decodeContinuityRecord(envelope.kind, envelope.source, envelope.recordId, envelope.revision);
|
|
2092
2030
|
}
|
|
2093
|
-
const base = baseByIdentity.get(`${envelope.kind}\0${envelope.recordId}`);
|
|
2094
2031
|
if (base === undefined)
|
|
2095
2032
|
throw invalidPayload('records');
|
|
2096
|
-
|
|
2033
|
+
validateBackupBasePrincipals(base);
|
|
2034
|
+
return base;
|
|
2097
2035
|
});
|
|
2098
2036
|
if (decoded.some((item, index) => JSON.stringify(item) !== lines[index])) {
|
|
2099
2037
|
throw invalidPayload('coordination');
|
|
@@ -2157,9 +2095,7 @@ function validateCollabProjectBackupCheckpointConsistency(manifest, records) {
|
|
|
2157
2095
|
}))
|
|
2158
2096
|
throw invalidPayload('records');
|
|
2159
2097
|
const baseRecords = decodedRecords.filter(item => (!CONTINUITY_KIND_SET.has(item.kind) && item.kind !== 'idempotency-result'));
|
|
2160
|
-
(0, CollabProjectCheckpoint_1.
|
|
2161
|
-
|
|
2162
|
-
coordinationFormatVersion: CollabProjectCheckpoint_1.COLLAB_PROJECT_COORDINATION_FORMAT_VERSION,
|
|
2163
|
-
}), baseRecords);
|
|
2098
|
+
(0, CollabProjectCheckpoint_1.validateCheckpointRecordSequence)(baseRecords, 'backup');
|
|
2099
|
+
(0, CollabProjectCheckpoint_1.validateCheckpointManifestConsistency)(decodedManifest, baseRecords);
|
|
2164
2100
|
return records;
|
|
2165
2101
|
}
|
|
@@ -265,11 +265,36 @@ export type CollabCheckpointAuthorityVolumePairRecord = CollabCheckpointRecordBa
|
|
|
265
265
|
}>;
|
|
266
266
|
export type CollabCheckpointPortableRecord = CollabCheckpointProjectRecord | CollabCheckpointMemberRecord | CollabCheckpointRequestRecord | CollabCheckpointRequestCommentRecord | CollabCheckpointTicketRecord | CollabCheckpointTicketCommentRecord | CollabCheckpointTicketRelationRecord | CollabCheckpointTicketMentionRecord;
|
|
267
267
|
export type CollabCheckpointBackupRecord = CollabCheckpointPortableRecord | CollabCheckpointCloudEventRecord | CollabCheckpointCloudEventCursorRecord | CollabCheckpointIdempotencyResultRecord | CollabCheckpointPrincipalBindingRecord | CollabCheckpointRepositoryPlacementRecord | CollabCheckpointLifecycleStateRecord | CollabCheckpointTerminalResponderRecord | CollabCheckpointProtectedClaimEnvelopeRecord | CollabCheckpointTombstoneRecord | CollabCheckpointSchemaCatalogRecord | CollabCheckpointServerCompatibilityRecord | CollabCheckpointAuthorityVolumePairRecord;
|
|
268
|
+
type ParsedCheckpointPrincipalBindingRecord = CollabCheckpointRecordBase<'principal-binding', Omit<CollabCheckpointPrincipalBindingRecord['value'], 'principalId'> & {
|
|
269
|
+
readonly principalId: unknown;
|
|
270
|
+
}>;
|
|
271
|
+
type ParsedCheckpointTerminalResponderRecord = CollabCheckpointRecordBase<'terminal-responder', Omit<CollabCheckpointTerminalResponderRecord['value'], 'acknowledgements'> & {
|
|
272
|
+
readonly acknowledgements: readonly (Omit<CollabCheckpointTerminalAcknowledgement, 'principalId'> & {
|
|
273
|
+
readonly principalId: unknown;
|
|
274
|
+
})[];
|
|
275
|
+
}>;
|
|
276
|
+
/** @internal Format v3 validates principal fields after the shared base-record phase. */
|
|
277
|
+
export type ParsedCheckpointRecord = Exclude<CollabCheckpointBackupRecord, CollabCheckpointPrincipalBindingRecord | CollabCheckpointTerminalResponderRecord> | ParsedCheckpointPrincipalBindingRecord | ParsedCheckpointTerminalResponderRecord;
|
|
278
|
+
type CheckpointManifestFields<CoordinationVersion extends number> = Omit<CollabProjectCheckpointManifest, 'coordinationFormatVersion'> & {
|
|
279
|
+
readonly coordinationFormatVersion: CoordinationVersion;
|
|
280
|
+
};
|
|
281
|
+
/** @internal Shared field validation; public entry points select their own format. */
|
|
282
|
+
export declare function decodeCheckpointManifestFields<CoordinationVersion extends number>(value: unknown, coordinationFormatVersion: CoordinationVersion): CheckpointManifestFields<CoordinationVersion>;
|
|
268
283
|
export declare function decodeCollabProjectCheckpointManifest(value: unknown): CollabProjectCheckpointManifest;
|
|
269
284
|
export declare function encodeCollabProjectCheckpointManifestCanonicalJson(manifest: CollabProjectCheckpointManifest): string;
|
|
270
285
|
export declare function encodeCollabProjectCheckpointManifestDigestInput(manifest: CollabProjectCheckpointManifest): string;
|
|
286
|
+
/** @internal Encode already validated fields without changing their format. */
|
|
287
|
+
export declare function encodeCheckpointManifestDigestInput(decoded: CheckpointManifestFields<number>): string;
|
|
271
288
|
export declare function encodeCollabProtectedClaimAssociatedData(associatedData: CollabProtectedClaimAssociatedData): string;
|
|
289
|
+
/** @internal Public v1 decoding validates principals in each record. */
|
|
290
|
+
export declare function decodeCheckpointRecord(value: unknown): CollabCheckpointBackupRecord;
|
|
291
|
+
/** @internal Offline v3 decoding validates principals after the base-record sequence. */
|
|
292
|
+
export declare function decodeCheckpointRecord(value: unknown, principalPhase: 'deferred'): ParsedCheckpointRecord;
|
|
293
|
+
/** @internal V3 decoding checks terminal principals separately; consistency binds current principals. */
|
|
294
|
+
export declare function validateCheckpointRecordSequence(records: readonly ParsedCheckpointRecord[], profile: CollabCheckpointProfile, acknowledgementBinding?: 'current-principal' | 'member'): void;
|
|
272
295
|
export declare function decodeCollabProjectCheckpointCoordinationNdjson(value: string, profile: CollabCheckpointProfile): readonly CollabCheckpointBackupRecord[];
|
|
273
296
|
export declare function encodeCollabProjectCheckpointCoordinationNdjson(records: readonly CollabCheckpointBackupRecord[], profile: CollabCheckpointProfile): string;
|
|
274
297
|
export declare function validateCollabProjectCheckpointConsistency(manifest: CollabProjectCheckpointManifest, records: readonly CollabCheckpointBackupRecord[]): readonly CollabCheckpointBackupRecord[];
|
|
298
|
+
/** @internal Cross-artifact checks operate on validated fields, not another format. */
|
|
299
|
+
export declare function validateCheckpointManifestConsistency(decodedManifest: CheckpointManifestFields<number>, records: readonly CollabCheckpointBackupRecord[]): readonly CollabCheckpointBackupRecord[];
|
|
275
300
|
export {};
|