sealbridge 0.4.1 → 0.5.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.
Files changed (139) hide show
  1. package/README.ja.md +4 -2
  2. package/README.md +6 -2
  3. package/dist/cli/dist/accept.d.ts +2 -1
  4. package/dist/cli/dist/accept.js +19 -8
  5. package/dist/cli/dist/accept.js.map +1 -1
  6. package/dist/cli/dist/board.js +21 -0
  7. package/dist/cli/dist/board.js.map +1 -1
  8. package/dist/cli/dist/config.js +1 -1
  9. package/dist/cli/dist/config.js.map +1 -1
  10. package/dist/cli/dist/connections.js +7 -2
  11. package/dist/cli/dist/connections.js.map +1 -1
  12. package/dist/cli/dist/contacts.js +6 -1
  13. package/dist/cli/dist/contacts.js.map +1 -1
  14. package/dist/cli/dist/index.js +65 -20
  15. package/dist/cli/dist/index.js.map +1 -1
  16. package/dist/cli/dist/invite-links.d.ts +14 -0
  17. package/dist/cli/dist/invite-links.js +263 -0
  18. package/dist/cli/dist/invite-links.js.map +1 -0
  19. package/dist/cli/dist/invite-output.d.ts +1 -1
  20. package/dist/cli/dist/invite-output.js +3 -2
  21. package/dist/cli/dist/invite-output.js.map +1 -1
  22. package/dist/cli/dist/invite-share.js +14 -0
  23. package/dist/cli/dist/invite-share.js.map +1 -1
  24. package/dist/cli/dist/invites-migrate.js +1 -1
  25. package/dist/cli/dist/invites-migrate.js.map +1 -1
  26. package/dist/cli/dist/mcp.js +1 -1
  27. package/dist/cli/dist/mcp.js.map +1 -1
  28. package/dist/cli/dist/network.js +107 -48
  29. package/dist/cli/dist/network.js.map +1 -1
  30. package/dist/cli/dist/pending.js +1 -1
  31. package/dist/cli/dist/pending.js.map +1 -1
  32. package/dist/cli/dist/receipt.d.ts +12 -2
  33. package/dist/cli/dist/receipt.js +10 -5
  34. package/dist/cli/dist/receipt.js.map +1 -1
  35. package/dist/cli/dist/requests.js +1 -1
  36. package/dist/cli/dist/requests.js.map +1 -1
  37. package/dist/cli/dist/sdk.d.ts +6 -4
  38. package/dist/cli/dist/sdk.js +3 -2
  39. package/dist/cli/dist/sdk.js.map +1 -1
  40. package/dist/cli/dist/setup.js +1 -1
  41. package/dist/cli/dist/setup.js.map +1 -1
  42. package/dist/cli/dist/tasks.js +1 -1
  43. package/dist/cli/dist/tasks.js.map +1 -1
  44. package/dist/examples/two-owner-demo/dist/run-demo.js +3 -1
  45. package/dist/examples/two-owner-demo/dist/run-demo.js.map +1 -1
  46. package/dist/package.json +1 -1
  47. package/dist/packages/core/dist/context-grant.d.ts +13 -0
  48. package/dist/packages/core/dist/context-grant.js +65 -0
  49. package/dist/packages/core/dist/context-grant.js.map +1 -0
  50. package/dist/packages/core/dist/delegation.d.ts +17 -0
  51. package/dist/packages/core/dist/delegation.js +126 -0
  52. package/dist/packages/core/dist/delegation.js.map +1 -0
  53. package/dist/packages/core/dist/edge.d.ts +2 -0
  54. package/dist/packages/core/dist/edge.js +2 -0
  55. package/dist/packages/core/dist/edge.js.map +1 -1
  56. package/dist/packages/core/dist/index.d.ts +4 -0
  57. package/dist/packages/core/dist/index.js +4 -0
  58. package/dist/packages/core/dist/index.js.map +1 -1
  59. package/dist/packages/core/dist/invite-link.d.ts +72 -0
  60. package/dist/packages/core/dist/invite-link.js +225 -0
  61. package/dist/packages/core/dist/invite-link.js.map +1 -0
  62. package/dist/packages/core/dist/invite-sas.d.ts +7 -0
  63. package/dist/packages/core/dist/invite-sas.js +15 -0
  64. package/dist/packages/core/dist/invite-sas.js.map +1 -0
  65. package/dist/packages/core/dist/relay-auth.d.ts +1 -1
  66. package/dist/packages/core/dist/relay-auth.js +1 -1
  67. package/dist/packages/core/dist/relay-auth.js.map +1 -1
  68. package/dist/packages/core/dist/schema.d.ts +1 -1
  69. package/dist/packages/core/dist/schema.js +4 -0
  70. package/dist/packages/core/dist/schema.js.map +1 -1
  71. package/dist/packages/core/dist/types.d.ts +52 -0
  72. package/dist/packages/ledger/dist/index.d.ts +70 -2
  73. package/dist/packages/ledger/dist/index.js +404 -26
  74. package/dist/packages/ledger/dist/index.js.map +1 -1
  75. package/dist/packages/relay/dist/fetch.js +4 -0
  76. package/dist/packages/relay/dist/fetch.js.map +1 -1
  77. package/dist/packages/relay/dist/invites.d.ts +42 -0
  78. package/dist/packages/relay/dist/invites.js +347 -0
  79. package/dist/packages/relay/dist/invites.js.map +1 -0
  80. package/dist/packages/relay/dist/worker.d.ts +3 -0
  81. package/dist/packages/relay/dist/worker.js +21 -0
  82. package/dist/packages/relay/dist/worker.js.map +1 -1
  83. package/dist/packages/runtime/dist/a2a.js +1 -1
  84. package/dist/packages/runtime/dist/adapter.d.ts +1 -0
  85. package/dist/packages/runtime/dist/approval-providers.d.ts +4 -0
  86. package/dist/packages/runtime/dist/approval-providers.js +3 -0
  87. package/dist/packages/runtime/dist/approval-providers.js.map +1 -1
  88. package/dist/packages/runtime/dist/connection-persistence.d.ts +1 -1
  89. package/dist/packages/runtime/dist/context-grant-audit.d.ts +8 -0
  90. package/dist/packages/runtime/dist/context-grant-audit.js +41 -0
  91. package/dist/packages/runtime/dist/context-grant-audit.js.map +1 -0
  92. package/dist/packages/runtime/dist/delegation.d.ts +37 -0
  93. package/dist/packages/runtime/dist/delegation.js +125 -0
  94. package/dist/packages/runtime/dist/delegation.js.map +1 -0
  95. package/dist/packages/runtime/dist/gateway.d.ts +1 -0
  96. package/dist/packages/runtime/dist/gateway.js.map +1 -1
  97. package/dist/packages/runtime/dist/index.d.ts +2 -0
  98. package/dist/packages/runtime/dist/index.js +2 -0
  99. package/dist/packages/runtime/dist/index.js.map +1 -1
  100. package/dist/packages/runtime/dist/mcp-server.js +22 -8
  101. package/dist/packages/runtime/dist/mcp-server.js.map +1 -1
  102. package/dist/packages/runtime/dist/mcp-tool-catalog.js +44 -5
  103. package/dist/packages/runtime/dist/mcp-tool-catalog.js.map +1 -1
  104. package/dist/packages/runtime/dist/protocol-release.js +5 -2
  105. package/dist/packages/runtime/dist/protocol-release.js.map +1 -1
  106. package/dist/packages/runtime/dist/session-audit.d.ts +6 -1
  107. package/dist/packages/runtime/dist/session-audit.js +35 -4
  108. package/dist/packages/runtime/dist/session-audit.js.map +1 -1
  109. package/dist/packages/runtime/dist/session.d.ts +21 -1
  110. package/dist/packages/runtime/dist/session.js +427 -62
  111. package/dist/packages/runtime/dist/session.js.map +1 -1
  112. package/dist/packages/runtime/dist/task-journal.d.ts +10 -1
  113. package/dist/packages/runtime/dist/task-journal.js +26 -1
  114. package/dist/packages/runtime/dist/task-journal.js.map +1 -1
  115. package/dist/packages/runtime/dist/verifier.d.ts +5 -2
  116. package/dist/packages/runtime/dist/verifier.js +14 -4
  117. package/dist/packages/runtime/dist/verifier.js.map +1 -1
  118. package/dist/spec/audit-entry.schema.json +25 -1
  119. package/dist/spec/audit-evidence.schema.json +179 -0
  120. package/dist/spec/audit-file-record.schema.json +177 -0
  121. package/dist/spec/audit-interruption.schema.json +110 -0
  122. package/dist/spec/context-grant-audit-payload.schema.json +24 -0
  123. package/dist/spec/context-grant.schema.json +146 -0
  124. package/dist/spec/delegation-attachment.schema.json +167 -0
  125. package/dist/spec/delegation-link.schema.json +141 -0
  126. package/dist/spec/examples/context-pooling/approval_redelegation/sample-delegation-attachment.json +413 -0
  127. package/dist/spec/examples/context-pooling/hand_over/sample-context-grant.json +18 -0
  128. package/dist/spec/examples/context-pooling/hand_over/sample-task-manifest.0.2.0.json +94 -0
  129. package/dist/spec/examples/context-pooling/sample-context-grant-audit-payload.json +4 -0
  130. package/dist/spec/examples/context-pooling/view_only/sample-context-grant.json +18 -0
  131. package/dist/spec/examples/context-pooling/view_only/sample-task-manifest.0.2.0.json +94 -0
  132. package/dist/spec/examples/sample-audit-evidence.json +138 -0
  133. package/dist/spec/examples/sample-audit-file-record.json +31 -0
  134. package/dist/spec/examples/sample-audit-interruption.json +29 -0
  135. package/dist/spec/task-manifest.0.2.0.schema.json +87 -1
  136. package/docs/context-grants.md +221 -0
  137. package/docs/sdk.ja.md +4 -0
  138. package/docs/sdk.md +4 -0
  139. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -97,6 +97,7 @@ export interface BudgetSpec {
97
97
  onOverrun: 'hard_stop' | 'request_hitl_amendment';
98
98
  }
99
99
  export interface TaskManifest {
100
+ contextGrant?: ContextGrant;
100
101
  protocolVersion: string;
101
102
  taskId: string;
102
103
  createdAt: string;
@@ -170,3 +171,54 @@ export interface TaskManifest {
170
171
  };
171
172
  signatures?: Record<string, Signature>;
172
173
  }
174
+ export type DeliveryMode = 'view_only' | 'approval_redelegation' | 'hand_over';
175
+ export interface ContextGrant {
176
+ deliveryMode: DeliveryMode;
177
+ /** Opaque selection declarations; CP1 does not extract data from a source. */
178
+ selectors: string[];
179
+ fieldsAllowed?: string[];
180
+ fieldsDenied?: string[];
181
+ angle: string;
182
+ expiresAt: string;
183
+ maxBytes: number;
184
+ retention: 'ephemeral_view' | 'keep_deliverable';
185
+ inputRetentionAllowed?: boolean;
186
+ outputSchema?: Schema;
187
+ }
188
+ export type ContextGrantOptions = Pick<ContextGrant, 'selectors' | 'angle'> & Partial<Omit<ContextGrant, 'selectors' | 'angle'>>;
189
+ export type ContextGrantAuditEventType = 'CONTEXT_GRANT' | 'CONTEXT_OPENED' | 'CONTEXT_DISCARDED' | 'CONTEXT_DELETION_ATTEMPTED' | 'CONTEXT_MODE';
190
+ export interface ContextGrantAuditPayload {
191
+ grantDigest: string;
192
+ deliveryMode: DeliveryMode;
193
+ }
194
+ /** Cross-task provenance, distinct from the same-task capability chain. */
195
+ export interface DelegationLimits {
196
+ deadline: string;
197
+ expiresAt: string;
198
+ budget: TaskManifest['budget'];
199
+ sandboxPolicy: TaskManifest['sandboxPolicy'];
200
+ verification: TaskManifest['verification'];
201
+ depthRemaining: number;
202
+ }
203
+ export interface DelegationLinkPayload {
204
+ type: 'DelegationLink';
205
+ parentTaskId: string;
206
+ parentContractDigest: string;
207
+ childTaskId: string;
208
+ childContractDigest: string;
209
+ delegatorOwnerDid: string;
210
+ delegateeOwnerDid: string;
211
+ depthRemaining: number;
212
+ limits: DelegationLimits;
213
+ issuedAt: string;
214
+ prevLinkDigest: string | null;
215
+ }
216
+ export type DelegationLink = Signed<DelegationLinkPayload>;
217
+ export interface DelegationProof {
218
+ link: DelegationLink;
219
+ childContract: import('./contract.js').TaskContract;
220
+ }
221
+ export interface DelegationAttachment {
222
+ parentContract: import('./contract.js').TaskContract;
223
+ children: DelegationProof[];
224
+ }
@@ -28,23 +28,64 @@ export interface AuditBundle {
28
28
  receiver: Signed<Signed<Checkpoint>>;
29
29
  };
30
30
  }
31
+ /** Local storage format 1; never a wire event or bilateral checkpoint. */
32
+ export interface AuditInterruption {
33
+ type: 'AuditInterruption';
34
+ formatVersion: 1;
35
+ taskId: string;
36
+ actors: [string, string];
37
+ seq: number;
38
+ prevEntryHash: string;
39
+ acknowledgedPrefix: {
40
+ count: number;
41
+ lastSeq: number;
42
+ head: string;
43
+ entriesDigest: string;
44
+ };
45
+ phase: 'OWNER_APPROVAL' | 'ACCEPT_ACK_WAIT';
46
+ reason: 'SESSION_TIMEOUT' | 'WORKER_STOPPED' | 'WORKER_RESTART_OBSERVED';
47
+ observedAt: string;
48
+ }
49
+ export interface InterruptedAuditBundle {
50
+ entries: AuditEntry[];
51
+ interruption: Signed<AuditInterruption>;
52
+ }
53
+ export type AuditEvidence = AuditBundle | InterruptedAuditBundle;
54
+ export type AuditFileRecord = {
55
+ entry: AuditEntry;
56
+ } | {
57
+ checkpoint: AuditBundle['checkpoint'];
58
+ } | {
59
+ interruption: Signed<AuditInterruption>;
60
+ };
61
+ export interface InterruptionStore {
62
+ /** Idempotent only for exactly the same signed terminal record and prefix.
63
+ * beforeWrite must synchronously persist the signed intent; promises are rejected. */
64
+ closeInterrupted(bundle: InterruptedAuditBundle, beforeWrite?: () => void): void;
65
+ }
31
66
  export interface AuditStore {
32
67
  read(): AuditEntry[];
33
68
  append(entry: AuditEntry, checkpoint: AuditBundle['checkpoint']): void;
34
69
  seal?(checkpoint: AuditBundle['checkpoint']): void;
35
70
  }
36
- export declare class MemoryStore implements AuditStore {
71
+ export declare class MemoryStore implements AuditStore, InterruptionStore {
37
72
  #private;
38
73
  read(): AuditEntry[];
39
74
  append(entry: AuditEntry): void;
75
+ seal(checkpoint: AuditBundle['checkpoint']): void;
76
+ closeInterrupted(bundle: InterruptedAuditBundle, beforeWrite?: () => void): void;
40
77
  }
41
- export declare class JsonlStore implements AuditStore {
78
+ export declare class JsonlStore implements AuditStore, InterruptionStore {
42
79
  readonly path: string;
43
80
  constructor(path: string);
81
+ /** All cooperating writers use this exclusive file lock. Never share with an older writer.
82
+ * A lock left by process death is an explicit local recovery operation, never stolen. */
83
+ private exclusive;
44
84
  private records;
45
85
  read(): AuditEntry[];
46
86
  append(entry: AuditEntry): void;
47
87
  seal(checkpoint: AuditBundle['checkpoint']): void;
88
+ closeInterrupted(bundle: InterruptedAuditBundle, beforeWrite?: () => void): void;
48
89
  }
49
90
  export declare function parseJsonl(text: string): AuditBundle;
50
91
  export declare class BilateralLedger {
@@ -82,3 +123,30 @@ export interface AuditVerificationResult {
82
123
  */
83
124
  export declare const AUDIT_VALID_WITHOUT_TRUST_ANCHOR = "VALID (no trust anchor: truncation not checked)";
84
125
  export declare function verifyAudit(bundle: AuditBundle, expected?: AuditVerificationOptions): AuditVerificationResult;
126
+ export interface AuditEvidenceTrustAnchor extends AuditTrustAnchor {
127
+ expectedClosureKind?: 'bilateral' | 'interruption';
128
+ expectedInterruptionHash?: string;
129
+ }
130
+ export interface AuditEvidenceVerificationOptions extends AuditEvidenceTrustAnchor {
131
+ trustAnchor?: AuditEvidenceTrustAnchor;
132
+ }
133
+ export interface InterruptedAuditVerificationResult {
134
+ valid: false;
135
+ status: 'UNILATERALLY_CLOSED' | 'INVALID';
136
+ statusLabel: string;
137
+ reason: string;
138
+ prefixVerified: boolean;
139
+ terminalSignatureVerified: boolean;
140
+ bilateralCheckpointVerified: false;
141
+ trustAnchorVerified: boolean;
142
+ /** For interruption only: an actual tail condition was compared, not identity alone. */
143
+ truncationChecked: boolean;
144
+ closureKind: 'interruption';
145
+ }
146
+ export type AuditEvidenceVerificationResult = AuditVerificationResult | InterruptedAuditVerificationResult;
147
+ /** Read either terminal form, retaining physical JSONL locations out of band. */
148
+ export declare function parseAuditJsonl(text: string): AuditEvidence;
149
+ /** Both formats are verified locally. A worker-only observation never becomes VALID. */
150
+ export declare function verifyAuditEvidence(bundle: unknown, expected?: AuditEvidenceVerificationOptions): AuditEvidenceVerificationResult;
151
+ /** Creates a worker observation; callers must preserve the original prefix and journal the intent before writing. */
152
+ export declare function createAuditInterruption(entries: AuditEntry[], worker: KeyPair, phase: AuditInterruption['phase'], reason: AuditInterruption['reason'], observedAt?: string): Signed<AuditInterruption>;
@@ -1,27 +1,151 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- import { appendFileSync, readFileSync } from 'node:fs';
3
- import { digest, signPayload, signAck, verifySigned, verifySignature, validateSchema, } from '../../core/dist/index.js';
2
+ import { appendFileSync, readFileSync, openSync, closeSync, fsyncSync, unlinkSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { digest, signPayload, signAck, verifySigned, verifySignature, validateSchema, validateManifest, } from '../../core/dist/index.js';
5
+ function persistInterruptionIntent(beforeWrite) {
6
+ const result = beforeWrite?.();
7
+ if (result && typeof result.then === 'function') {
8
+ void Promise.resolve(result).catch(() => { });
9
+ throw new Error('ASYNC_AUDIT_INTENT_FORBIDDEN');
10
+ }
11
+ }
12
+ // Source locations are diagnostic metadata only: never serialize them into a bundle.
13
+ const jsonlLocations = new WeakMap();
14
+ function object(value) {
15
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
16
+ ? value : undefined;
17
+ }
18
+ function diagnosticText(value) {
19
+ if (typeof value !== 'string')
20
+ return undefined;
21
+ if (/acp-invite:/i.test(value))
22
+ return '[redacted]';
23
+ return value.length > 160 ? value.slice(0, 160) + '...' : value;
24
+ }
25
+ function diagnosticJson(value) {
26
+ // JSON escaping covers C0 controls; also escape DEL, C1 and Unicode display controls.
27
+ return JSON.stringify(value).replace(/[\u007f-\uffff]/g, char => '\\u' + char.charCodeAt(0).toString(16).padStart(4, '0'));
28
+ }
29
+ function entryLocation(entry, locationKind, index, line) {
30
+ const e = object(entry), body = object(e?.body);
31
+ return {
32
+ locationKind,
33
+ taskId: diagnosticText(body?.taskId),
34
+ seq: typeof body?.seq === 'number' && Number.isSafeInteger(body.seq) && body.seq >= 0
35
+ ? body.seq : undefined,
36
+ entryHash: diagnosticText(e?.entryHash),
37
+ line,
38
+ index,
39
+ verified: false,
40
+ };
41
+ }
42
+ function checkpointLocation(bundle) {
43
+ const checkpoint = object(bundle?.checkpoint), payload = object(object(checkpoint?.actor)?.payload);
44
+ const entries = Array.isArray(bundle?.entries) ? bundle.entries : [];
45
+ const source = jsonlLocations.get(bundle);
46
+ return {
47
+ locationKind: 'checkpoint',
48
+ taskId: diagnosticText(payload?.taskId),
49
+ head: diagnosticText(payload?.head),
50
+ line: source?.checkpointLine,
51
+ lastEntry: entries.length
52
+ ? entryLocation(entries.at(-1), 'last-entry', entries.length - 1, source?.entryLines.at(-1)) : undefined,
53
+ verified: false,
54
+ };
55
+ }
56
+ function parseRecords(text) {
57
+ // Keep the existing trim/filter rules while counting physical lines before filtering.
58
+ const start = text.length - text.trimStart().length;
59
+ const firstLine = text.slice(0, start).split('\n').length;
60
+ return text.trim().split('\n').flatMap((line, index) => {
61
+ if (!line)
62
+ return [];
63
+ const physicalLine = firstLine + index;
64
+ let record;
65
+ try {
66
+ record = JSON.parse(line);
67
+ }
68
+ catch {
69
+ // Engine JSON errors can quote the input, including task payloads or invitations.
70
+ throw new SyntaxError('Invalid JSON; auditLocation=' + diagnosticJson({ line: physicalLine, verified: false }));
71
+ }
72
+ return [{ record, line: physicalLine }];
73
+ });
74
+ }
4
75
  export class MemoryStore {
5
76
  #entries = [];
77
+ #interruption;
78
+ #checkpoint;
79
+ #closing = false;
6
80
  read() {
7
81
  return structuredClone(this.#entries);
8
82
  }
9
83
  append(entry) {
84
+ if (this.#closing)
85
+ throw new Error('LEDGER_BUSY');
86
+ if (this.#interruption || this.#checkpoint)
87
+ throw new Error('LEDGER_SEALED');
10
88
  this.#entries.push(structuredClone(entry));
11
89
  }
90
+ seal(checkpoint) {
91
+ if (this.#closing)
92
+ throw new Error('LEDGER_BUSY');
93
+ if (this.#interruption)
94
+ throw new Error('LEDGER_SEALED');
95
+ if (this.#checkpoint && digest(this.#checkpoint.actor.payload) !== digest(checkpoint.actor.payload))
96
+ throw new Error('LEDGER_SEALED');
97
+ this.#checkpoint = structuredClone(checkpoint);
98
+ }
99
+ closeInterrupted(bundle, beforeWrite) {
100
+ if (this.#closing)
101
+ throw new Error('LEDGER_BUSY');
102
+ bundle = structuredClone(bundle);
103
+ if (this.#checkpoint)
104
+ throw new Error('LEDGER_SEALED');
105
+ if (digest(this.#entries) !== digest(bundle.entries) ||
106
+ verifyAuditEvidence(bundle).status !== 'UNILATERALLY_CLOSED')
107
+ throw new Error('INVALID_AUDIT_INTERRUPTION');
108
+ if (this.#interruption && digest(this.#interruption) !== digest(bundle.interruption))
109
+ throw new Error('LEDGER_SEALED');
110
+ this.#closing = true;
111
+ try {
112
+ persistInterruptionIntent(beforeWrite);
113
+ this.#interruption = bundle.interruption;
114
+ }
115
+ finally {
116
+ this.#closing = false;
117
+ }
118
+ }
12
119
  }
13
120
  export class JsonlStore {
14
121
  path;
15
122
  constructor(path) {
16
123
  this.path = path;
17
124
  }
125
+ /** All cooperating writers use this exclusive file lock. Never share with an older writer.
126
+ * A lock left by process death is an explicit local recovery operation, never stolen. */
127
+ exclusive(operation) {
128
+ const path = resolve(this.path) + '.lock';
129
+ let fd;
130
+ try {
131
+ fd = openSync(path, 'wx', 0o600);
132
+ }
133
+ catch (error) {
134
+ if (error.code === 'EEXIST')
135
+ throw new Error('LEDGER_BUSY');
136
+ throw error;
137
+ }
138
+ try {
139
+ return operation();
140
+ }
141
+ finally {
142
+ closeSync(fd);
143
+ unlinkSync(path);
144
+ }
145
+ }
18
146
  records() {
19
147
  try {
20
- return readFileSync(this.path, 'utf8')
21
- .trim()
22
- .split('\n')
23
- .filter(Boolean)
24
- .map((l) => JSON.parse(l));
148
+ return parseRecords(readFileSync(this.path, 'utf8')).map(({ record }) => record);
25
149
  }
26
150
  catch (e) {
27
151
  if (e.code === 'ENOENT')
@@ -35,30 +159,93 @@ export class JsonlStore {
35
159
  .map((r) => r.entry);
36
160
  }
37
161
  append(entry) {
38
- if (this.records().some((r) => r.checkpoint))
39
- throw new Error('LEDGER_SEALED');
40
- appendFileSync(this.path, JSON.stringify({ entry }) + '\n', { mode: 0o600 });
162
+ this.exclusive(() => {
163
+ if (this.records().some((r) => r.checkpoint || Object.hasOwn(r, 'interruption')))
164
+ throw new Error('LEDGER_SEALED');
165
+ appendFileSync(this.path, JSON.stringify({ entry }) + '\n', { mode: 0o600 });
166
+ });
41
167
  }
42
168
  seal(checkpoint) {
43
- const old = this.records().at(-1)?.checkpoint;
44
- if (old) {
45
- if (digest(old.actor.payload) !== digest(checkpoint.actor.payload))
169
+ this.exclusive(() => {
170
+ const records = this.records();
171
+ if (records.some(r => Object.hasOwn(r, 'interruption')))
46
172
  throw new Error('LEDGER_SEALED');
47
- return;
48
- }
49
- appendFileSync(this.path, JSON.stringify({ checkpoint }) + '\n', { mode: 0o600 });
173
+ const old = records.at(-1)?.checkpoint;
174
+ if (old) {
175
+ if (digest(old.actor.payload) !== digest(checkpoint.actor.payload))
176
+ throw new Error('LEDGER_SEALED');
177
+ return;
178
+ }
179
+ appendFileSync(this.path, JSON.stringify({ checkpoint }) + '\n', { mode: 0o600 });
180
+ });
181
+ }
182
+ closeInterrupted(bundle, beforeWrite) {
183
+ bundle = structuredClone(bundle);
184
+ this.exclusive(() => {
185
+ if (verifyAuditEvidence(bundle).status !== 'UNILATERALLY_CLOSED')
186
+ throw new Error('INVALID_AUDIT_INTERRUPTION');
187
+ const text = readFileSync(this.path, 'utf8'), records = parseRecords(text);
188
+ const terminal = records.at(-1)?.record.interruption;
189
+ if (terminal) {
190
+ const existing = parseAuditJsonl(text);
191
+ if (digest(existing) !== digest(bundle))
192
+ throw new Error('LEDGER_SEALED');
193
+ }
194
+ else {
195
+ if (records.some(({ record }) => !object(record) || Object.keys(record).length !== 1 || !record.entry) ||
196
+ digest(records.map(({ record }) => record.entry)) !== digest(bundle.entries))
197
+ throw new Error('AUDIT_PREFIX_MISMATCH');
198
+ }
199
+ persistInterruptionIntent(beforeWrite);
200
+ // Journal callbacks must not change the audit. Cooperating processes remain excluded.
201
+ if (readFileSync(this.path, 'utf8') !== text)
202
+ throw new Error('AUDIT_PREFIX_MISMATCH');
203
+ const fd = openSync(this.path, 'a');
204
+ try {
205
+ if (!terminal)
206
+ appendFileSync(fd, (text.endsWith('\n') ? '' : '\n') + JSON.stringify({ interruption: bundle.interruption }) + '\n');
207
+ // Includes the original acknowledged prefix, and also retries a failed sync after append.
208
+ fsyncSync(fd);
209
+ }
210
+ finally {
211
+ closeSync(fd);
212
+ }
213
+ if (process.platform !== 'win32') {
214
+ const dir = openSync(dirname(this.path), 'r');
215
+ try {
216
+ fsyncSync(dir);
217
+ }
218
+ finally {
219
+ closeSync(dir);
220
+ }
221
+ }
222
+ });
50
223
  }
51
224
  }
52
225
  export function parseJsonl(text) {
53
- const records = text
54
- .trim()
55
- .split('\n')
56
- .filter(Boolean)
57
- .map((l) => JSON.parse(l));
58
- const last = records.pop();
59
- if (!last?.checkpoint || last.entry || records.some((r) => !r.entry || r.checkpoint))
60
- throw new Error('MISSING_OR_MISPLACED_CHECKPOINT');
61
- return { entries: records.map((r) => r.entry), checkpoint: last.checkpoint };
226
+ const records = parseRecords(text);
227
+ const last = records.at(-1);
228
+ const misplaced = records.slice(0, -1).find(({ record }) => !object(record)?.entry || object(record)?.checkpoint || Object.hasOwn(record, 'interruption'));
229
+ if (!object(last?.record)?.checkpoint || object(last?.record)?.entry || Object.hasOwn(last.record, 'interruption') || misplaced) {
230
+ const offending = misplaced ?? last;
231
+ const entry = object(offending?.record)?.entry;
232
+ const checkpoint = object(offending?.record)?.checkpoint;
233
+ const payload = object(object(object(checkpoint)?.actor)?.payload);
234
+ const lastEntry = !entry && !checkpoint
235
+ ? records.findLast(({ record }) => object(record)?.entry) : undefined;
236
+ const location = entry
237
+ ? entryLocation(entry, !misplaced && !object(last?.record)?.checkpoint ? 'last-entry' : 'entry')
238
+ : { locationKind: checkpoint ? 'checkpoint' : 'record', taskId: diagnosticText(payload?.taskId),
239
+ head: diagnosticText(payload?.head), verified: false };
240
+ throw new Error('MISSING_OR_MISPLACED_CHECKPOINT; auditLocation=' + diagnosticJson({
241
+ ...location, line: offending?.line,
242
+ lastEntry: lastEntry ? { ...entryLocation(lastEntry.record.entry, 'last-entry'), line: lastEntry.line } : undefined,
243
+ }));
244
+ }
245
+ const entryRecords = records.slice(0, -1);
246
+ const bundle = { entries: entryRecords.map(({ record }) => record.entry), checkpoint: last.record.checkpoint };
247
+ jsonlLocations.set(bundle, { entryLines: entryRecords.map(({ line }) => line), checkpointLine: last.line });
248
+ return bundle;
62
249
  }
63
250
  export class BilateralLedger {
64
251
  taskId;
@@ -224,9 +411,22 @@ function auditResult(supplied, error) {
224
411
  truncationChecked: true,
225
412
  };
226
413
  }
414
+ function verificationError(error) {
415
+ // Retain existing verifier codes and schema explanations, without forwarding arbitrary exceptions.
416
+ if (error instanceof Error && (/^(checkpoint|trust anchor|entry \d+|[A-Z][A-Z0-9_]{0,79})$/.test(error.message)
417
+ || error.message.startsWith('SCHEMA_VIOLATION: '))) {
418
+ const message = error.message;
419
+ const bounded = message.length > 1024 ? message.slice(0, 1024) + '...' : message;
420
+ return 'Error: ' + diagnosticJson(bounded).slice(1, -1);
421
+ }
422
+ return error instanceof TypeError ? 'TypeError' : 'Error';
423
+ }
227
424
  export function verifyAudit(bundle, expected) {
228
425
  const supplied = anchorSupplied(expected);
426
+ let location = checkpointLocation(bundle);
229
427
  try {
428
+ if (object(bundle) && Object.hasOwn(bundle, 'interruption'))
429
+ throw new Error('checkpoint');
230
430
  const { entries, checkpoint } = bundle, c = checkpoint.actor.payload;
231
431
  const selfDelegated = Array.isArray(c?.actors) && c.actors.length === 2 && c.actors[0] === c.actors[1];
232
432
  if (c.type !== 'AuditCheckpoint' ||
@@ -243,6 +443,7 @@ export function verifyAudit(bundle, expected) {
243
443
  throw new Error('checkpoint');
244
444
  assertTrustAnchor(resolveAnchor(expected), c, entries);
245
445
  for (let i = 0; i < entries.length; i++) {
446
+ location = entryLocation(entries[i], 'entry', i, jsonlLocations.get(bundle)?.entryLines[i]);
246
447
  const e = entries[i], b = e.body, a = e.counterSignature;
247
448
  validateSchema('audit-entry', e);
248
449
  if (b.taskId !== c.taskId ||
@@ -263,7 +464,184 @@ export function verifyAudit(bundle, expected) {
263
464
  return auditResult(supplied);
264
465
  }
265
466
  catch (e) {
266
- return auditResult(supplied, e);
467
+ return auditResult(supplied, verificationError(e) + '; auditLocation=' + diagnosticJson(location));
267
468
  }
268
469
  }
470
+ const interruptionLocations = new WeakMap();
471
+ function interruptionLocation(bundle) {
472
+ const signed = object(bundle?.interruption), p = object(signed?.payload);
473
+ const source = interruptionLocations.get(bundle);
474
+ const entries = Array.isArray(bundle?.entries) ? bundle.entries : [];
475
+ let interruptionHash;
476
+ try {
477
+ if (p)
478
+ interruptionHash = digest(p);
479
+ }
480
+ catch { /* Only expose a computed digest. */ }
481
+ return {
482
+ locationKind: 'interruption', taskId: diagnosticText(p?.taskId),
483
+ seq: typeof p?.seq === 'number' && Number.isSafeInteger(p.seq) && p.seq >= 0 ? p.seq : undefined,
484
+ interruptionHash, line: source?.terminalLine, verified: false,
485
+ lastEntry: entries.length ? entryLocation(entries.at(-1), 'last-entry', entries.length - 1, source?.entryLines.at(-1)) : undefined,
486
+ };
487
+ }
488
+ /** Read either terminal form, retaining physical JSONL locations out of band. */
489
+ export function parseAuditJsonl(text) {
490
+ const records = parseRecords(text);
491
+ // Keep the bilateral parser's diagnostics for old files, including missing checkpoints.
492
+ if (!records.some(({ record }) => object(record) && Object.hasOwn(record, 'interruption'))) {
493
+ const bilateral = parseJsonl(text);
494
+ const malformed = records.find(({ record }) => !object(record) || Object.keys(record).length !== 1 ||
495
+ !['entry', 'checkpoint'].includes(Object.keys(record)[0]));
496
+ if (!malformed)
497
+ return bilateral;
498
+ throw new Error('INVALID_AUDIT_RECORD; auditLocation=' + diagnosticJson({ line: malformed.line, verified: false }));
499
+ }
500
+ const bad = records.find(({ record }, index) => {
501
+ const r = object(record);
502
+ return !r || Object.keys(r).length !== 1 ||
503
+ (index === records.length - 1 ? !Object.hasOwn(r, 'interruption') : !Object.hasOwn(r, 'entry'));
504
+ });
505
+ if (bad)
506
+ throw new Error('MISPLACED_AUDIT_TERMINAL; auditLocation=' + diagnosticJson({ line: bad.line, verified: false }));
507
+ const bundle = { entries: records.slice(0, -1).map(({ record }) => record.entry), interruption: records.at(-1).record.interruption };
508
+ interruptionLocations.set(bundle, { entryLines: records.slice(0, -1).map(({ line }) => line), terminalLine: records.at(-1).line });
509
+ return bundle;
510
+ }
511
+ /** Verify a PROPOSE-only acknowledged prefix, including offline manifest roles/signature. */
512
+ function assertInterruptionPrefix(entries, taskId, actors) {
513
+ if (!Array.isArray(entries) || entries.length !== 1 || actors.length !== 2 || actors[0] === actors[1])
514
+ throw new Error('INTERRUPTION_SCOPE');
515
+ const e = entries[0];
516
+ validateSchema('audit-entry', e);
517
+ const b = e.body, a = e.counterSignature;
518
+ if (b.eventType !== 'PROPOSE' || b.taskId !== taskId || b.seq !== 0 || b.prevEntryHash !== null ||
519
+ b.actorDid !== actors[0] || b.receiverDid !== actors[1] || e.entryHash !== digest(b) ||
520
+ !verifySignature(b, e.actorSignature, actors[0]) || !verifySigned(a, actors[1]) ||
521
+ a.payload.type !== 'AckReceipt' || a.payload.taskId !== taskId || a.payload.entryHash !== e.entryHash ||
522
+ a.payload.actorDid !== actors[0] || a.payload.receiverDid !== actors[1])
523
+ throw new Error('entry 0');
524
+ const proposal = object(b.payload)?.proposal;
525
+ if (!proposal)
526
+ throw new Error('INTERRUPTION_MANIFEST');
527
+ validateManifest(proposal.payload);
528
+ if (!verifySigned(proposal, actors[0]) || proposal.payload.taskId !== taskId ||
529
+ proposal.payload.requester.ownerDid !== actors[0] || proposal.payload.worker?.ownerDid !== actors[1])
530
+ throw new Error('INTERRUPTION_MANIFEST');
531
+ // Deliberately no live expiry, retirement clock, or peer-floor policy in offline verification.
532
+ }
533
+ function evidencePins(options) {
534
+ const base = resolveAnchor(options);
535
+ for (const key of ['expectedClosureKind', 'expectedInterruptionHash']) {
536
+ if (!sameAnchorField(options?.[key], options?.trustAnchor?.[key]))
537
+ throw new Error('trust anchor');
538
+ }
539
+ return { ...base,
540
+ expectedClosureKind: options?.expectedClosureKind ?? options?.trustAnchor?.expectedClosureKind,
541
+ expectedInterruptionHash: options?.expectedInterruptionHash ?? options?.trustAnchor?.expectedInterruptionHash };
542
+ }
543
+ function assertInterruptionPins(options) {
544
+ if (options !== undefined && !object(options))
545
+ throw new Error('trust anchor');
546
+ if (options?.trustAnchor !== undefined && !object(options.trustAnchor))
547
+ throw new Error('trust anchor');
548
+ for (const part of [options, options?.trustAnchor]) {
549
+ if (!part)
550
+ continue;
551
+ for (const key of ['taskId', 'head', 'expectedLastEntryHash', 'expectedFinalState', 'expectedInterruptionHash'])
552
+ if (part[key] !== undefined && !hasText(part[key]))
553
+ throw new Error('trust anchor');
554
+ if (part.expectedClosureKind !== undefined && !['bilateral', 'interruption'].includes(part.expectedClosureKind))
555
+ throw new Error('trust anchor');
556
+ if (part.actors !== undefined && (!Array.isArray(part.actors) || part.actors.length !== 2 || !part.actors.every(hasText)))
557
+ throw new Error('trust anchor');
558
+ if (part.expectedEntryCount !== undefined && (!Number.isSafeInteger(part.expectedEntryCount) || part.expectedEntryCount < 0))
559
+ throw new Error('trust anchor');
560
+ }
561
+ }
562
+ /** Both formats are verified locally. A worker-only observation never becomes VALID. */
563
+ export function verifyAuditEvidence(bundle, expected) {
564
+ const value = object(bundle);
565
+ if (!value || !Object.hasOwn(value, 'interruption')) {
566
+ const bilateral = bundle;
567
+ const result = verifyAudit(bilateral, expected);
568
+ if (!result.valid)
569
+ return result;
570
+ try {
571
+ validateSchema('audit-evidence', bundle);
572
+ const pins = evidencePins(expected);
573
+ if (pins.expectedClosureKind !== undefined && pins.expectedClosureKind !== 'bilateral' ||
574
+ pins.expectedInterruptionHash !== undefined)
575
+ throw new Error('trust anchor');
576
+ return pins.expectedClosureKind === 'bilateral' ? { ...result, trustAnchorVerified: true } : result;
577
+ }
578
+ catch (error) {
579
+ return auditResult(anchorSupplied(expected), verificationError(error) + '; auditLocation=' + diagnosticJson(checkpointLocation(bilateral)));
580
+ }
581
+ }
582
+ const interrupted = bundle;
583
+ let prefixVerified = false, terminalSignatureVerified = false, truncationChecked = false;
584
+ let location = interruptionLocation(interrupted);
585
+ try {
586
+ // Exclusive object shape and terminal schema before interpreting its fields.
587
+ if (Object.keys(value).length !== 2 || !Object.hasOwn(value, 'entries'))
588
+ throw new Error('INVALID_AUDIT_EVIDENCE');
589
+ validateSchema('audit-interruption', interrupted.interruption);
590
+ const { entries, interruption } = interrupted, p = interruption.payload;
591
+ location = entryLocation(entries?.[0], 'entry', 0, interruptionLocations.get(interrupted)?.entryLines[0]);
592
+ assertInterruptionPrefix(entries, p.taskId, p.actors);
593
+ prefixVerified = true;
594
+ location = interruptionLocation(interrupted);
595
+ const prefix = p.acknowledgedPrefix, head = entries[0].entryHash;
596
+ if (p.formatVersion !== 1 || p.seq !== 1 || prefix.count !== 1 || prefix.lastSeq !== 0 ||
597
+ p.prevEntryHash !== head || prefix.head !== head || prefix.entriesDigest !== digest(entries) ||
598
+ !Number.isSafeInteger(p.seq) || !Number.isSafeInteger(prefix.count) || !Number.isSafeInteger(prefix.lastSeq) ||
599
+ p.observedAt !== interruption.signature.signedAt || !Number.isFinite(Date.parse(p.observedAt)) ||
600
+ !verifySigned(interruption, p.actors[1]))
601
+ throw new Error('INVALID_AUDIT_INTERRUPTION');
602
+ terminalSignatureVerified = true;
603
+ assertInterruptionPins(expected);
604
+ const pins = evidencePins(expected);
605
+ const prefixCheckpoint = { type: 'AuditCheckpoint', taskId: p.taskId, actors: p.actors, count: 1, head };
606
+ assertTrustAnchor({ taskId: pins.taskId, actors: pins.actors }, prefixCheckpoint, entries);
607
+ if (pins.expectedClosureKind !== undefined && pins.expectedClosureKind !== 'interruption')
608
+ throw new Error('trust anchor');
609
+ if ([pins.head, pins.expectedLastEntryHash, pins.expectedFinalState, pins.expectedEntryCount].some(v => v !== undefined)) {
610
+ truncationChecked = true;
611
+ assertTrustAnchor(pins, prefixCheckpoint, entries);
612
+ }
613
+ if (pins.expectedInterruptionHash !== undefined) {
614
+ truncationChecked = true;
615
+ if (pins.expectedInterruptionHash !== digest(p))
616
+ throw new Error('trust anchor');
617
+ }
618
+ const supplied = anchorSupplied(expected) || pins.expectedClosureKind !== undefined || pins.expectedInterruptionHash !== undefined;
619
+ return { valid: false, status: 'UNILATERALLY_CLOSED',
620
+ statusLabel: 'UNILATERALLY_CLOSED (worker-only terminal; no peer terminal signature)', reason: 'WORKER_ONLY_INTERRUPTION',
621
+ prefixVerified, terminalSignatureVerified, bilateralCheckpointVerified: false,
622
+ trustAnchorVerified: supplied, truncationChecked, closureKind: 'interruption' };
623
+ }
624
+ catch (error) {
625
+ return { valid: false, status: 'INVALID', statusLabel: 'INVALID',
626
+ reason: verificationError(error) + '; auditLocation=' + diagnosticJson(location),
627
+ prefixVerified, terminalSignatureVerified, bilateralCheckpointVerified: false,
628
+ trustAnchorVerified: false, truncationChecked, closureKind: 'interruption' };
629
+ }
630
+ }
631
+ /** Creates a worker observation; callers must preserve the original prefix and journal the intent before writing. */
632
+ export function createAuditInterruption(entries, worker, phase, reason, observedAt = new Date().toISOString()) {
633
+ const first = entries[0];
634
+ if (!first)
635
+ throw new Error('INTERRUPTION_SCOPE');
636
+ const actors = [first.body.actorDid, worker.did];
637
+ assertInterruptionPrefix(entries, first.body.taskId, actors);
638
+ const payload = { type: 'AuditInterruption', formatVersion: 1,
639
+ taskId: first.body.taskId, actors, seq: 1, prevEntryHash: first.entryHash,
640
+ acknowledgedPrefix: { count: 1, lastSeq: 0, head: first.entryHash, entriesDigest: digest(entries) },
641
+ phase, reason, observedAt };
642
+ const interruption = signPayload(payload, worker, observedAt);
643
+ if (verifyAuditEvidence({ entries, interruption }).status !== 'UNILATERALLY_CLOSED')
644
+ throw new Error('INVALID_AUDIT_INTERRUPTION');
645
+ return interruption;
646
+ }
269
647
  //# sourceMappingURL=index.js.map