@abelxiaoxing/cadence 1.8.4 → 2.0.1

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 (202) hide show
  1. package/README.md +324 -117
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -18
  4. package/agents/diagnosis-worker.md +14 -14
  5. package/agents/implementation-worker.md +16 -17
  6. package/config/workflow-task.example.json +7 -0
  7. package/docs/remote-access.md +60 -0
  8. package/docs/reuse-and-distribution.md +44 -0
  9. package/docs/service-management.md +108 -0
  10. package/licenses/bundled-web.txt +6245 -0
  11. package/licenses/pi-web-ui-MIT.txt +21 -0
  12. package/licenses/pi-web-ui-reuse.md +22 -0
  13. package/package.json +92 -20
  14. package/src/index.ts +21 -1802
  15. package/src/native-build.ts +6 -0
  16. package/src/native-client.ts +103 -0
  17. package/src/native-export.ts +58 -0
  18. package/src/native-history.ts +359 -0
  19. package/src/operator-cli.mjs +453 -1937
  20. package/src/plan-proposal.ts +87 -0
  21. package/src/sdk-worker.mjs +549 -0
  22. package/src/service/contracts/active.ts +82 -0
  23. package/src/service/contracts/budget.ts +148 -0
  24. package/src/service/contracts/build.ts +67 -0
  25. package/src/service/contracts/capture.ts +48 -0
  26. package/src/service/contracts/core.ts +33 -0
  27. package/src/service/contracts/current.ts +261 -0
  28. package/src/service/contracts/domain.ts +68 -0
  29. package/src/service/contracts/history.ts +2245 -0
  30. package/src/service/contracts/models.ts +139 -0
  31. package/src/service/coordination.ts +862 -0
  32. package/src/service/private-files.ts +204 -0
  33. package/src/service-cli.mjs +5592 -0
  34. package/src/service-client.ts +747 -0
  35. package/src/service-contract.ts +245 -0
  36. package/src/service-host.mjs +22382 -0
  37. package/src/service-http-client.ts +425 -0
  38. package/src/service-wire.ts +372 -0
  39. package/src/task-shell.ts +1045 -0
  40. package/src/task-store.ts +479 -0
  41. package/src/task-types.ts +184 -0
  42. package/src/verification-diagnostics.ts +29 -72
  43. package/src/worker-capture.mjs +1197 -0
  44. package/src/worker-public.mjs +1753 -0
  45. package/src/worker-redaction.mjs +608 -0
  46. package/src/worker-tools.mjs +202 -0
  47. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  50. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  56. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  62. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  68. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  71. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  74. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  77. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  89. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  92. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  98. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  104. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  107. package/web/dist/assets/cadence.css +2 -0
  108. package/web/dist/assets/cadence.js +7 -0
  109. package/web/dist/assets/markdown.js +296 -0
  110. package/web/dist/assets/react.js +9 -0
  111. package/web/dist/assets/rolldown-runtime.js +1 -0
  112. package/web/dist/assets/timeline-client.js +41 -0
  113. package/web/dist/assets/vis-timeline.css +1274 -0
  114. package/web/dist/assets/vis-timeline.js +32 -0
  115. package/web/dist/index.html +18 -0
  116. package/web/dist/manifest.json +505 -0
  117. package/config/plan-draft.example.json +0 -125
  118. package/config/plan-draft.multiple-tasks.example.json +0 -375
  119. package/config/plan-draft.quick.example.json +0 -78
  120. package/prompts/abel-design.md +0 -382
  121. package/prompts/abel-diagnose.md +0 -71
  122. package/prompts/abel-implement.md +0 -284
  123. package/prompts/abel-init.md +0 -52
  124. package/src/activation.ts +0 -44
  125. package/src/activity-contracts.ts +0 -82
  126. package/src/agent-registry.ts +0 -53
  127. package/src/apply-transaction.ts +0 -1437
  128. package/src/artifact-store.ts +0 -213
  129. package/src/build-windows-job.mjs +0 -95
  130. package/src/candidate-artifact.ts +0 -143
  131. package/src/candidate-context.ts +0 -289
  132. package/src/candidate-patch.ts +0 -271
  133. package/src/candidate-workspace.ts +0 -80
  134. package/src/canonical.ts +0 -16
  135. package/src/change-contract-schema.ts +0 -191
  136. package/src/change-contract.ts +0 -324
  137. package/src/change-verification.ts +0 -1617
  138. package/src/contracts.ts +0 -2466
  139. package/src/control-contracts.ts +0 -382
  140. package/src/delivery-compiler.ts +0 -1805
  141. package/src/delivery-revision.ts +0 -42
  142. package/src/design-control.ts +0 -1299
  143. package/src/design-diagnostics.ts +0 -181
  144. package/src/design-journal.ts +0 -1773
  145. package/src/directory-sync.ts +0 -36
  146. package/src/durable-contracts.ts +0 -227
  147. package/src/durable-workflow.ts +0 -822
  148. package/src/evidence-draft.ts +0 -122
  149. package/src/execution-profile.ts +0 -64
  150. package/src/execution-retention.ts +0 -77
  151. package/src/file-snapshot.ts +0 -163
  152. package/src/implement-continuation.ts +0 -559
  153. package/src/implement-graph.ts +0 -690
  154. package/src/implement-plan.ts +0 -72
  155. package/src/install-config.d.mts +0 -6
  156. package/src/isolation-backend.ts +0 -353
  157. package/src/model-source.ts +0 -22
  158. package/src/openspec-cli.ts +0 -494
  159. package/src/operator-cli-entry.mjs +0 -24
  160. package/src/operator-tools.ts +0 -309
  161. package/src/package-candidate-process.ts +0 -206
  162. package/src/package-candidate.ts +0 -24
  163. package/src/package-delivery.ts +0 -486
  164. package/src/package-state.ts +0 -320
  165. package/src/package-verification.ts +0 -635
  166. package/src/package-workflow.ts +0 -305
  167. package/src/packet-runtime.ts +0 -459
  168. package/src/phase-execution.ts +0 -2163
  169. package/src/pi-adapter.ts +0 -19
  170. package/src/plan-draft-summary.ts +0 -527
  171. package/src/plan-draft.ts +0 -502
  172. package/src/remove-path.ts +0 -32
  173. package/src/run-state.ts +0 -420
  174. package/src/run-store.ts +0 -1179
  175. package/src/safe-path.ts +0 -77
  176. package/src/scoped-grep-worker.mjs +0 -23
  177. package/src/scoped-tools.ts +0 -775
  178. package/src/single-task-draft.ts +0 -142
  179. package/src/sqlite-schema.ts +0 -171
  180. package/src/state-root.ts +0 -215
  181. package/src/storage-schema.ts +0 -360
  182. package/src/subagent-activity.ts +0 -1417
  183. package/src/subagent-process.ts +0 -476
  184. package/src/task-ledger.ts +0 -1338
  185. package/src/transport-budget.ts +0 -50
  186. package/src/verification-capability.ts +0 -1191
  187. package/src/verification-environment-io.ts +0 -389
  188. package/src/verification-environment.ts +0 -162
  189. package/src/verification-identity.ts +0 -77
  190. package/src/verification-scan.ts +0 -64
  191. package/src/windows-job-backend.ts +0 -419
  192. package/src/windows-job.c +0 -149
  193. package/src/workflow-engine.ts +0 -28
  194. package/src/workflow-policy.ts +0 -1670
  195. package/src/workflow-recovery-policy.ts +0 -97
  196. package/src/workflow-scheduling.ts +0 -105
  197. package/src/workflow-state-machine.ts +0 -4341
  198. package/src/workflow-status.ts +0 -576
  199. package/src/workspace-io-entry.mjs +0 -76
  200. package/src/workspace-io-worker.mjs +0 -1312
  201. package/src/workspace-io.ts +0 -89
  202. package/src/workspace-store.ts +0 -634
@@ -1,1773 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import { DatabaseSync } from "node:sqlite";
3
- import { compareCanonicalStrings } from "./canonical.ts";
4
- import {
5
- type ChangeContract,
6
- normalizeChangeContract,
7
- } from "./change-contract.ts";
8
- import {
9
- type DesignEvidenceResult,
10
- validateEvidenceResult,
11
- } from "./contracts.ts";
12
- import { canonicalJson } from "./run-state.ts";
13
- import { configureSqlite, ensureSqliteSchema } from "./sqlite-schema.ts";
14
- import { prepareStateRoot, type ResolvedStateRoot } from "./state-root.ts";
15
- import { DESIGN_SCHEMA as SCHEMA } from "./storage-schema.ts";
16
-
17
- const SHA256 = /^[a-f0-9]{64}$/u;
18
- const IDENTIFIER = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
19
- const ZERO_HASH = "0".repeat(64);
20
- const MAX_PLAN_BYTES = 16 * 1024 * 1024;
21
- const MAX_CONTRACT_BYTES = 256 * 1024;
22
-
23
- type DecisionCategory = "behavior" | "technical";
24
- type DesignGate = "gate-a" | "gate-b";
25
-
26
- interface RunRow {
27
- run_id: string;
28
- root_hash: string;
29
- stage: string;
30
- change_name: string | null;
31
- state: string;
32
- }
33
-
34
- interface FactRow {
35
- sequence: number;
36
- kind: string;
37
- identity: string;
38
- payload_json: string;
39
- canonical_hash: string;
40
- prior_record_hash: string;
41
- record_hash: string;
42
- }
43
-
44
- interface OperationRow {
45
- operation: string;
46
- request_hash: string;
47
- outcome_json: string;
48
- }
49
-
50
- export interface DesignJournalOptions {
51
- now?: () => number;
52
- finalizationLeaseMs?: number;
53
- }
54
-
55
- export interface DesignFinalizationLease {
56
- runId: string;
57
- operationId: string;
58
- token: string;
59
- expiresAt: number;
60
- }
61
-
62
- export interface DesignGateProof {
63
- revision: number;
64
- contractHash: string;
65
- recordHash: string;
66
- }
67
-
68
- export interface DesignEvidenceProjection {
69
- packetId: string;
70
- resultHash: string;
71
- recordHash: string;
72
- sequence: number;
73
- moduleName: string;
74
- scope: string[];
75
- filesRead: string[];
76
- citations: Array<{
77
- path: string;
78
- lineStart: number;
79
- lineEnd: number;
80
- claimHash: string;
81
- }>;
82
- }
83
-
84
- export interface DesignDecisionProjection {
85
- decisionId: string;
86
- category: DecisionCategory;
87
- revision: number;
88
- contractHash: string;
89
- refs: string[];
90
- recordHash: string;
91
- sequence: number;
92
- }
93
-
94
- export interface DesignPlanProjection {
95
- revision: number;
96
- rawSha256: string;
97
- canonicalHash: string;
98
- recordHash: string;
99
- sequence: number;
100
- }
101
-
102
- export interface DesignGateProjection {
103
- current: boolean;
104
- proof?: DesignGateProof;
105
- approvedSequence?: number;
106
- staleReason?:
107
- | "behavior-decision-changed"
108
- | "technical-decision-changed"
109
- | "plan-recompiled"
110
- | "gate-a-reapproved"
111
- | "gate-a-stale"
112
- | "plan-missing";
113
- }
114
-
115
- export interface DesignStatusProjection {
116
- changeContract?: ChangeContract;
117
- runId: string;
118
- change?: string;
119
- evidence: DesignEvidenceProjection[];
120
- decisions: DesignDecisionProjection[];
121
- gates: {
122
- gateA: DesignGateProjection;
123
- gateB: DesignGateProjection;
124
- };
125
- plan: DesignPlanProjection | null;
126
- }
127
-
128
- export interface GateProofVerificationInput {
129
- change: string;
130
- gate: DesignGate;
131
- proof: DesignGateProof;
132
- }
133
-
134
- export interface FinalizedDeliveryVerificationInput {
135
- change: string;
136
- deliveryRevision: number;
137
- receiptHash: string;
138
- gateA: DesignGateProof;
139
- gateB: DesignGateProof;
140
- planCanonicalHash: string;
141
- }
142
-
143
- export type DesignArtifactOperationFacts =
144
- | {
145
- runId: string;
146
- operationId: string;
147
- operation: "write-artifact";
148
- path: string;
149
- bytes: number;
150
- rawSha256: string;
151
- }
152
- | {
153
- runId: string;
154
- operationId: string;
155
- operation: "delete-artifact";
156
- path: string;
157
- };
158
-
159
- export type DesignArtifactOperationOutcome =
160
- | {
161
- operation: "write-artifact";
162
- runId: string;
163
- path: string;
164
- bytes: number;
165
- rawSha256: string;
166
- }
167
- | {
168
- operation: "delete-artifact";
169
- runId: string;
170
- path: string;
171
- deleted: false;
172
- }
173
- | {
174
- operation: "delete-artifact";
175
- runId: string;
176
- path: string;
177
- deleted: true;
178
- bytes: number;
179
- rawSha256: string;
180
- };
181
-
182
- export interface DesignArtifactOperationState {
183
- state: "pending" | "committed";
184
- outcome: DesignArtifactOperationOutcome;
185
- }
186
-
187
- function sha256(value: Uint8Array | string): string {
188
- return createHash("sha256").update(value).digest("hex");
189
- }
190
-
191
- function contractHash(domain: string, value: string): string {
192
- if (
193
- typeof value !== "string" ||
194
- value.trim().length === 0 ||
195
- Buffer.byteLength(value, "utf8") > MAX_CONTRACT_BYTES
196
- ) {
197
- throw new Error("design-transient-input-invalid");
198
- }
199
- return createHash("sha256")
200
- .update(domain)
201
- .update("\0")
202
- .update(value.replace(/\r\n?/gu, "\n").trim())
203
- .digest("hex");
204
- }
205
-
206
- function requireIdentifier(value: string, label: string): void {
207
- if (!IDENTIFIER.test(value)) throw new Error(`invalid-${label}`);
208
- }
209
-
210
- function requireHash(value: string, label: string): void {
211
- if (!SHA256.test(value)) throw new Error(`invalid-${label}`);
212
- }
213
-
214
- function parseRecord(value: string, code: string): Record<string, unknown> {
215
- const parsed = JSON.parse(value) as unknown;
216
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
217
- throw new Error(code);
218
- }
219
- return parsed as Record<string, unknown>;
220
- }
221
-
222
- function hasExactKeys(
223
- value: Record<string, unknown>,
224
- keys: readonly string[],
225
- ): boolean {
226
- const actual = Object.keys(value);
227
- return (
228
- actual.length === keys.length && actual.every((key) => keys.includes(key))
229
- );
230
- }
231
-
232
- function normalizeRefs(refs: readonly string[]): string[] {
233
- if (
234
- !Array.isArray(refs) ||
235
- refs.length > 128 ||
236
- refs.some(
237
- (ref) =>
238
- typeof ref !== "string" ||
239
- ref.length === 0 ||
240
- ref.length > 512 ||
241
- ref.trim() !== ref ||
242
- ref.includes("\0") ||
243
- ref.includes("\n") ||
244
- ref.includes("\r"),
245
- )
246
- ) {
247
- throw new Error("design-decision-refs-invalid");
248
- }
249
- return [...new Set(refs)].sort((left, right) =>
250
- compareCanonicalStrings(left, right),
251
- );
252
- }
253
-
254
- export class DesignJournal {
255
- readonly stateRoot: ResolvedStateRoot;
256
- readonly #database: DatabaseSync;
257
- readonly #now: () => number;
258
- readonly #finalizationLeaseMs: number;
259
- #closed = false;
260
-
261
- private constructor(
262
- stateRoot: ResolvedStateRoot,
263
- database: DatabaseSync,
264
- options: DesignJournalOptions,
265
- ) {
266
- this.stateRoot = stateRoot;
267
- this.#database = database;
268
- this.#now = options.now ?? Date.now;
269
- this.#finalizationLeaseMs = options.finalizationLeaseMs ?? 300_000;
270
- }
271
-
272
- static open(
273
- unresolved: ResolvedStateRoot,
274
- options: DesignJournalOptions = {},
275
- ): DesignJournal {
276
- if (
277
- options.finalizationLeaseMs !== undefined &&
278
- (!Number.isSafeInteger(options.finalizationLeaseMs) ||
279
- options.finalizationLeaseMs < 1)
280
- ) {
281
- throw new Error("design-finalization-lease-invalid");
282
- }
283
- const stateRoot = prepareStateRoot(unresolved);
284
- const database = new DatabaseSync(stateRoot.databasePath);
285
- try {
286
- configureSqlite(database);
287
- ensureSqliteSchema(database, SCHEMA);
288
- return new DesignJournal(stateRoot, database, options);
289
- } catch (error) {
290
- try {
291
- database.close();
292
- } catch {
293
- // The Design journal initialization failure remains authoritative.
294
- }
295
- throw error;
296
- }
297
- }
298
-
299
- #assertOpen(): void {
300
- if (this.#closed) throw new Error("design-journal-closed");
301
- }
302
-
303
- #transaction<T>(work: () => T): T {
304
- this.#assertOpen();
305
- this.#database.exec("BEGIN IMMEDIATE");
306
- try {
307
- const result = work();
308
- this.#database.exec("COMMIT");
309
- return result;
310
- } catch (error) {
311
- try {
312
- this.#database.exec("ROLLBACK");
313
- } catch {
314
- // The original operation or commit failure remains authoritative.
315
- }
316
- throw error;
317
- }
318
- }
319
-
320
- #run(runId: string, mutable: boolean): RunRow {
321
- const row = this.#database
322
- .prepare(
323
- `SELECT run_id, root_hash, stage, change_name, state
324
- FROM runs WHERE run_id = ?`,
325
- )
326
- .get(runId) as RunRow | undefined;
327
- if (
328
- !row ||
329
- row.root_hash !== this.stateRoot.consumerRootHash ||
330
- row.stage !== "abel-design" ||
331
- (mutable && ["completed", "discarded", "rejected"].includes(row.state))
332
- ) {
333
- throw new Error("design-run-invalid");
334
- }
335
- return row;
336
- }
337
-
338
- assertDesignRun(runId: string): void {
339
- this.#assertOpen();
340
- this.#run(runId, true);
341
- }
342
-
343
- operationOutcome(
344
- runId: string,
345
- operationId: string,
346
- operation: string,
347
- ): Record<string, unknown> | undefined {
348
- this.#assertOpen();
349
- this.#run(runId, false);
350
- requireIdentifier(operationId, "design-operation-id");
351
- const row = this.#database
352
- .prepare(
353
- `SELECT operation, outcome_json FROM design_operations
354
- WHERE run_id = ? AND operation_id = ?`,
355
- )
356
- .get(runId, operationId) as
357
- | Pick<OperationRow, "operation" | "outcome_json">
358
- | undefined;
359
- if (!row) return undefined;
360
- if (row.operation !== operation)
361
- throw new Error("design-operation-conflict");
362
- return structuredClone(
363
- parseRecord(row.outcome_json, "design-operation-outcome-invalid"),
364
- );
365
- }
366
-
367
- #artifactOperationRequest(
368
- input: DesignArtifactOperationFacts,
369
- ): Record<string, unknown> {
370
- requireIdentifier(input.operationId, "design-operation-id");
371
- if (
372
- typeof input.path !== "string" ||
373
- input.path.length === 0 ||
374
- input.path.length > 512
375
- ) {
376
- throw new Error("design-artifact-path-invalid");
377
- }
378
- if (input.operation === "write-artifact") {
379
- if (
380
- !Number.isSafeInteger(input.bytes) ||
381
- input.bytes < 0 ||
382
- input.bytes > MAX_PLAN_BYTES
383
- ) {
384
- throw new Error("design-artifact-content-too-large");
385
- }
386
- requireHash(input.rawSha256, "design-artifact-hash");
387
- return {
388
- operation: input.operation,
389
- path: input.path,
390
- bytes: input.bytes,
391
- rawSha256: input.rawSha256,
392
- };
393
- }
394
- return { operation: input.operation, path: input.path };
395
- }
396
-
397
- #validateArtifactOperationOutcome(
398
- input: DesignArtifactOperationFacts,
399
- value: unknown,
400
- ): DesignArtifactOperationOutcome {
401
- if (!value || typeof value !== "object" || Array.isArray(value)) {
402
- throw new Error("design-artifact-operation-outcome-invalid");
403
- }
404
- const outcome = value as Record<string, unknown>;
405
- if (
406
- outcome.operation !== input.operation ||
407
- outcome.runId !== input.runId ||
408
- outcome.path !== input.path
409
- ) {
410
- throw new Error("design-artifact-operation-outcome-invalid");
411
- }
412
- if (input.operation === "write-artifact") {
413
- if (
414
- !hasExactKeys(outcome, [
415
- "operation",
416
- "runId",
417
- "path",
418
- "bytes",
419
- "rawSha256",
420
- ]) ||
421
- outcome.bytes !== input.bytes ||
422
- outcome.rawSha256 !== input.rawSha256
423
- ) {
424
- throw new Error("design-artifact-operation-outcome-invalid");
425
- }
426
- return structuredClone(outcome) as DesignArtifactOperationOutcome;
427
- }
428
- if (outcome.deleted === false) {
429
- if (!hasExactKeys(outcome, ["operation", "runId", "path", "deleted"])) {
430
- throw new Error("design-artifact-operation-outcome-invalid");
431
- }
432
- return structuredClone(outcome) as DesignArtifactOperationOutcome;
433
- }
434
- if (
435
- !hasExactKeys(outcome, [
436
- "operation",
437
- "runId",
438
- "path",
439
- "deleted",
440
- "bytes",
441
- "rawSha256",
442
- ]) ||
443
- outcome.deleted !== true ||
444
- !Number.isSafeInteger(outcome.bytes) ||
445
- Number(outcome.bytes) < 0 ||
446
- Number(outcome.bytes) > MAX_PLAN_BYTES ||
447
- !SHA256.test(String(outcome.rawSha256))
448
- ) {
449
- throw new Error("design-artifact-operation-outcome-invalid");
450
- }
451
- return structuredClone(outcome) as DesignArtifactOperationOutcome;
452
- }
453
-
454
- artifactOperationState(
455
- input: DesignArtifactOperationFacts,
456
- ): DesignArtifactOperationState | undefined {
457
- this.#assertOpen();
458
- this.#run(input.runId, false);
459
- const request = this.#artifactOperationRequest(input);
460
- const row = this.#database
461
- .prepare(
462
- `SELECT operation, request_hash, outcome_json
463
- FROM design_operations WHERE run_id = ? AND operation_id = ?`,
464
- )
465
- .get(input.runId, input.operationId) as OperationRow | undefined;
466
- if (!row) return undefined;
467
- if (
468
- row.operation !== input.operation ||
469
- row.request_hash !== sha256(canonicalJson(request))
470
- ) {
471
- throw new Error("design-operation-conflict");
472
- }
473
- const stored = parseRecord(
474
- row.outcome_json,
475
- "design-artifact-operation-state-invalid",
476
- );
477
- if (stored.state !== "pending" && stored.state !== "committed") {
478
- throw new Error("design-artifact-operation-state-invalid");
479
- }
480
- return {
481
- state: stored.state,
482
- outcome: this.#validateArtifactOperationOutcome(input, stored.outcome),
483
- };
484
- }
485
-
486
- prepareArtifactOperation(
487
- input: DesignArtifactOperationFacts,
488
- lease: DesignFinalizationLease,
489
- outcome: DesignArtifactOperationOutcome,
490
- ): DesignArtifactOperationState {
491
- if (
492
- lease.runId !== input.runId ||
493
- lease.operationId !== input.operationId
494
- ) {
495
- throw new Error("design-finalization-lease-fenced");
496
- }
497
- return this.#transaction(() => {
498
- this.#run(input.runId, true);
499
- this.#assertFinalizationLease(lease);
500
- const existing = this.artifactOperationState(input);
501
- if (existing) return existing;
502
- const request = this.#artifactOperationRequest(input);
503
- const validated = this.#validateArtifactOperationOutcome(input, outcome);
504
- const state: DesignArtifactOperationState = {
505
- state: "pending",
506
- outcome: validated,
507
- };
508
- this.#database
509
- .prepare(
510
- `INSERT INTO design_operations(
511
- run_id, operation_id, operation, request_hash, outcome_json
512
- ) VALUES (?, ?, ?, ?, ?)`,
513
- )
514
- .run(
515
- input.runId,
516
- input.operationId,
517
- input.operation,
518
- sha256(canonicalJson(request)),
519
- canonicalJson(state),
520
- );
521
- return structuredClone(state);
522
- });
523
- }
524
-
525
- commitArtifactOperation(
526
- input: DesignArtifactOperationFacts,
527
- lease: DesignFinalizationLease,
528
- ): DesignArtifactOperationOutcome {
529
- if (
530
- lease.runId !== input.runId ||
531
- lease.operationId !== input.operationId
532
- ) {
533
- throw new Error("design-finalization-lease-fenced");
534
- }
535
- return this.#transaction(() => {
536
- this.#run(input.runId, true);
537
- this.#assertFinalizationLease(lease);
538
- const existing = this.artifactOperationState(input);
539
- if (!existing) throw new Error("design-artifact-operation-unprepared");
540
- if (existing.state === "committed") {
541
- return structuredClone(existing.outcome);
542
- }
543
- this.#database
544
- .prepare(
545
- `UPDATE design_operations SET outcome_json = ?
546
- WHERE run_id = ? AND operation_id = ?`,
547
- )
548
- .run(
549
- canonicalJson({ state: "committed", outcome: existing.outcome }),
550
- input.runId,
551
- input.operationId,
552
- );
553
- return structuredClone(existing.outcome);
554
- });
555
- }
556
-
557
- assertNoPendingArtifactOperations(runId: string): void {
558
- this.#assertOpen();
559
- this.#run(runId, true);
560
- const rows = this.#database
561
- .prepare(
562
- `SELECT outcome_json FROM design_operations
563
- WHERE run_id = ? AND operation IN ('write-artifact', 'delete-artifact')`,
564
- )
565
- .all(runId) as unknown as Array<{ outcome_json: string }>;
566
- for (const row of rows) {
567
- const state = parseRecord(
568
- row.outcome_json,
569
- "design-artifact-operation-state-invalid",
570
- ).state;
571
- if (state !== "pending" && state !== "committed") {
572
- throw new Error("design-artifact-operation-state-invalid");
573
- }
574
- if (state === "pending") {
575
- throw new Error("design-artifact-operation-pending");
576
- }
577
- }
578
- }
579
-
580
- latestFinalizationOutcome(
581
- runId: string,
582
- ): { operationId: string; outcome: Record<string, unknown> } | undefined {
583
- this.#assertOpen();
584
- this.#run(runId, false);
585
- const row = this.#database
586
- .prepare(
587
- `SELECT operation_id, outcome_json FROM design_operations
588
- WHERE run_id = ? AND operation = 'finalize-delivery'
589
- ORDER BY rowid DESC LIMIT 1`,
590
- )
591
- .get(runId) as { operation_id: string; outcome_json: string } | undefined;
592
- return row
593
- ? {
594
- operationId: row.operation_id,
595
- outcome: structuredClone(
596
- parseRecord(
597
- row.outcome_json,
598
- "design-finalization-outcome-invalid",
599
- ),
600
- ),
601
- }
602
- : undefined;
603
- }
604
-
605
- acquireFinalizationLease(input: {
606
- runId: string;
607
- operationId: string;
608
- }): DesignFinalizationLease {
609
- requireIdentifier(input.operationId, "design-operation-id");
610
- return this.#transaction(() => {
611
- this.#run(input.runId, true);
612
- const now = this.#now();
613
- const existing = this.#database
614
- .prepare(
615
- `SELECT operation_id, token, expires_at
616
- FROM design_finalization_leases WHERE run_id = ?`,
617
- )
618
- .get(input.runId) as
619
- | { operation_id: string; token: string; expires_at: number }
620
- | undefined;
621
- if (existing && existing.expires_at > now) {
622
- throw new Error("design-finalization-busy");
623
- }
624
- if (existing) {
625
- this.#database
626
- .prepare("DELETE FROM design_finalization_leases WHERE run_id = ?")
627
- .run(input.runId);
628
- }
629
- const lease: DesignFinalizationLease = {
630
- runId: input.runId,
631
- operationId: input.operationId,
632
- token: randomUUID(),
633
- expiresAt: now + this.#finalizationLeaseMs,
634
- };
635
- this.#database
636
- .prepare(
637
- `INSERT INTO design_finalization_leases(
638
- run_id, operation_id, token, expires_at
639
- ) VALUES (?, ?, ?, ?)`,
640
- )
641
- .run(lease.runId, lease.operationId, lease.token, lease.expiresAt);
642
- return lease;
643
- });
644
- }
645
-
646
- #assertFinalizationLease(lease: DesignFinalizationLease): void {
647
- const row = this.#database
648
- .prepare(
649
- `SELECT operation_id, token, expires_at
650
- FROM design_finalization_leases WHERE run_id = ?`,
651
- )
652
- .get(lease.runId) as
653
- | { operation_id: string; token: string; expires_at: number }
654
- | undefined;
655
- if (
656
- !row ||
657
- row.operation_id !== lease.operationId ||
658
- row.token !== lease.token ||
659
- row.expires_at !== lease.expiresAt ||
660
- row.expires_at <= this.#now()
661
- ) {
662
- throw new Error("design-finalization-lease-fenced");
663
- }
664
- }
665
-
666
- assertFinalizationLease(lease: DesignFinalizationLease): void {
667
- this.#assertOpen();
668
- this.#assertFinalizationLease(lease);
669
- }
670
-
671
- releaseFinalizationLease(lease: DesignFinalizationLease): void {
672
- this.#transaction(() => {
673
- this.#database
674
- .prepare(
675
- `DELETE FROM design_finalization_leases
676
- WHERE run_id = ? AND operation_id = ? AND token = ?`,
677
- )
678
- .run(lease.runId, lease.operationId, lease.token);
679
- });
680
- }
681
-
682
- #facts(runId: string): FactRow[] {
683
- return this.#database
684
- .prepare(
685
- `SELECT sequence, kind, identity, payload_json, canonical_hash,
686
- prior_record_hash, record_hash
687
- FROM design_facts WHERE run_id = ? ORDER BY sequence`,
688
- )
689
- .all(runId) as unknown as FactRow[];
690
- }
691
-
692
- #appendFact(
693
- runId: string,
694
- kind: string,
695
- identity: string,
696
- payload: Record<string, unknown>,
697
- ): FactRow {
698
- const latest = this.#database
699
- .prepare(
700
- `SELECT sequence, record_hash FROM design_facts
701
- WHERE run_id = ? ORDER BY sequence DESC LIMIT 1`,
702
- )
703
- .get(runId) as { sequence: number; record_hash: string } | undefined;
704
- const sequence = (latest?.sequence ?? 0) + 1;
705
- const canonicalHash = sha256(canonicalJson(payload));
706
- const priorRecordHash = latest?.record_hash ?? ZERO_HASH;
707
- const recordHash = sha256(
708
- canonicalJson({
709
- runId,
710
- sequence,
711
- kind,
712
- identity,
713
- canonicalHash,
714
- priorRecordHash,
715
- }),
716
- );
717
- this.#database
718
- .prepare(
719
- `INSERT INTO design_facts(
720
- run_id, sequence, kind, identity, payload_json, canonical_hash,
721
- prior_record_hash, record_hash
722
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
723
- )
724
- .run(
725
- runId,
726
- sequence,
727
- kind,
728
- identity,
729
- canonicalJson(payload),
730
- canonicalHash,
731
- priorRecordHash,
732
- recordHash,
733
- );
734
- return {
735
- sequence,
736
- kind,
737
- identity,
738
- payload_json: canonicalJson(payload),
739
- canonical_hash: canonicalHash,
740
- prior_record_hash: priorRecordHash,
741
- record_hash: recordHash,
742
- };
743
- }
744
-
745
- #operationReplay(
746
- runId: string,
747
- operationId: string,
748
- operation: string,
749
- request: Record<string, unknown>,
750
- ): Record<string, unknown> | undefined {
751
- requireIdentifier(operationId, "design-operation-id");
752
- const requestHash = sha256(canonicalJson(request));
753
- const row = this.#database
754
- .prepare(
755
- `SELECT operation, request_hash, outcome_json
756
- FROM design_operations WHERE run_id = ? AND operation_id = ?`,
757
- )
758
- .get(runId, operationId) as OperationRow | undefined;
759
- if (!row) return undefined;
760
- if (row.operation !== operation || row.request_hash !== requestHash) {
761
- throw new Error("design-operation-conflict");
762
- }
763
- return parseRecord(row.outcome_json, "design-operation-outcome-invalid");
764
- }
765
-
766
- #recordOperation(
767
- runId: string,
768
- operationId: string,
769
- operation: string,
770
- request: Record<string, unknown>,
771
- outcome: Record<string, unknown>,
772
- ): void {
773
- this.#database
774
- .prepare(
775
- `INSERT INTO design_operations(
776
- run_id, operation_id, operation, request_hash, outcome_json
777
- ) VALUES (?, ?, ?, ?, ?)`,
778
- )
779
- .run(
780
- runId,
781
- operationId,
782
- operation,
783
- sha256(canonicalJson(request)),
784
- canonicalJson(outcome),
785
- );
786
- }
787
-
788
- recordEvidence(input: {
789
- runId: string;
790
- evidence: DesignEvidenceResult;
791
- }): DesignEvidenceProjection {
792
- return this.#transaction(() => {
793
- this.#run(input.runId, true);
794
- const validation = validateEvidenceResult(input.evidence);
795
- if (!validation.ok || input.evidence.role !== "design-explorer") {
796
- throw new Error("design-evidence-invalid");
797
- }
798
- const resultHash = sha256(canonicalJson(input.evidence));
799
- const existing = this.#database
800
- .prepare(
801
- `SELECT sequence, payload_json, record_hash
802
- FROM design_facts
803
- WHERE run_id = ? AND kind = 'evidence' AND identity = ?`,
804
- )
805
- .get(input.runId, input.evidence.packet_id) as
806
- | { sequence: number; payload_json: string; record_hash: string }
807
- | undefined;
808
- if (existing) {
809
- const payload = parseRecord(
810
- existing.payload_json,
811
- "design-evidence-record-invalid",
812
- );
813
- if (payload.resultHash !== resultHash) {
814
- throw new Error("design-evidence-conflict");
815
- }
816
- return this.#evidenceProjection(existing, payload);
817
- }
818
- const payload = {
819
- packetId: input.evidence.packet_id,
820
- resultHash,
821
- moduleName: input.evidence.module_name,
822
- scope: [...new Set(input.evidence.scope)].sort(),
823
- filesRead: [...new Set(input.evidence.files_read)].sort(),
824
- citations: input.evidence.evidence
825
- .map((citation) => ({
826
- path: citation.path,
827
- lineStart: citation.line_start,
828
- lineEnd: citation.line_end,
829
- claimHash: sha256(citation.claim),
830
- }))
831
- .sort(
832
- (left, right) =>
833
- compareCanonicalStrings(left.path, right.path) ||
834
- left.lineStart - right.lineStart ||
835
- left.lineEnd - right.lineEnd ||
836
- compareCanonicalStrings(left.claimHash, right.claimHash),
837
- ),
838
- };
839
- const fact = this.#appendFact(
840
- input.runId,
841
- "evidence",
842
- input.evidence.packet_id,
843
- payload,
844
- );
845
- return this.#evidenceProjection(fact, payload);
846
- });
847
- }
848
-
849
- #evidenceProjection(
850
- row: Pick<FactRow, "sequence" | "record_hash">,
851
- payload: Record<string, unknown>,
852
- ): DesignEvidenceProjection {
853
- return {
854
- packetId: String(payload.packetId),
855
- resultHash: String(payload.resultHash),
856
- recordHash: row.record_hash,
857
- sequence: row.sequence,
858
- moduleName: String(payload.moduleName),
859
- scope: structuredClone(payload.scope as string[]),
860
- filesRead: structuredClone(payload.filesRead as string[]),
861
- citations: structuredClone(
862
- payload.citations as DesignEvidenceProjection["citations"],
863
- ),
864
- };
865
- }
866
-
867
- recordDecision(input: {
868
- runId: string;
869
- operationId: string;
870
- decisionId: string;
871
- category: DecisionCategory;
872
- contract: string;
873
- refs: string[];
874
- }): Record<string, unknown> {
875
- requireIdentifier(input.decisionId, "design-decision-id");
876
- const decisionContractHash = contractHash(
877
- "abel-design-decision",
878
- input.contract,
879
- );
880
- if (input.category !== "behavior" && input.category !== "technical") {
881
- throw new Error("design-decision-category-invalid");
882
- }
883
- const refs = normalizeRefs(input.refs);
884
- const request = {
885
- operation: "record-decision",
886
- decisionId: input.decisionId,
887
- category: input.category,
888
- contractHash: decisionContractHash,
889
- refs,
890
- };
891
- return this.#transaction(() => {
892
- this.#run(input.runId, true);
893
- const replay = this.#operationReplay(
894
- input.runId,
895
- input.operationId,
896
- "record-decision",
897
- request,
898
- );
899
- if (replay) return structuredClone(replay);
900
- const prior = this.#latestDecision(input.runId, input.decisionId);
901
- if (prior && prior.category !== input.category) {
902
- throw new Error("design-decision-category-conflict");
903
- }
904
- let decision: DesignDecisionProjection;
905
- if (
906
- prior &&
907
- prior.contractHash === decisionContractHash &&
908
- canonicalJson(prior.refs) === canonicalJson(refs)
909
- ) {
910
- decision = prior;
911
- } else {
912
- const revision = (prior?.revision ?? 0) + 1;
913
- const payload = {
914
- decisionId: input.decisionId,
915
- category: input.category,
916
- revision,
917
- contractHash: decisionContractHash,
918
- refs,
919
- authorityChanged: prior?.contractHash !== decisionContractHash,
920
- };
921
- const fact = this.#appendFact(
922
- input.runId,
923
- "decision",
924
- `${input.decisionId}:${revision}`,
925
- payload,
926
- );
927
- decision = this.#decisionProjection(fact, payload);
928
- }
929
- const outcome = {
930
- operation: "record-decision",
931
- runId: input.runId,
932
- decision,
933
- gates: this.#gateStatus(input.runId),
934
- };
935
- this.#recordOperation(
936
- input.runId,
937
- input.operationId,
938
- "record-decision",
939
- request,
940
- outcome,
941
- );
942
- return structuredClone(outcome);
943
- });
944
- }
945
-
946
- #decisionProjection(
947
- fact: Pick<FactRow, "sequence" | "record_hash">,
948
- payload: Record<string, unknown>,
949
- ): DesignDecisionProjection {
950
- return {
951
- decisionId: String(payload.decisionId),
952
- category: payload.category as DecisionCategory,
953
- revision: Number(payload.revision),
954
- contractHash: String(payload.contractHash),
955
- refs: structuredClone(payload.refs as string[]),
956
- recordHash: fact.record_hash,
957
- sequence: fact.sequence,
958
- };
959
- }
960
-
961
- #latestDecision(
962
- runId: string,
963
- decisionId: string,
964
- ): DesignDecisionProjection | undefined {
965
- const rows = this.#facts(runId).filter((row) => row.kind === "decision");
966
- for (let index = rows.length - 1; index >= 0; index -= 1) {
967
- const row = rows[index];
968
- const payload = parseRecord(
969
- row.payload_json,
970
- "design-decision-record-invalid",
971
- );
972
- if (payload.decisionId === decisionId) {
973
- return this.#decisionProjection(row, payload);
974
- }
975
- }
976
- return undefined;
977
- }
978
-
979
- approveGate(
980
- input:
981
- | {
982
- runId: string;
983
- operationId: string;
984
- gate: "gate-a";
985
- contract: string | ChangeContract;
986
- }
987
- | {
988
- runId: string;
989
- operationId: string;
990
- gate: "gate-b";
991
- },
992
- ): {
993
- operation: "approve-gate";
994
- runId: string;
995
- gate: DesignGate;
996
- proof: DesignGateProof;
997
- } {
998
- if (input.gate !== "gate-a" && input.gate !== "gate-b") {
999
- throw new Error("design-gate-invalid");
1000
- }
1001
- const changeContract =
1002
- input.gate === "gate-a" && typeof input.contract !== "string"
1003
- ? normalizeChangeContract(input.contract)
1004
- : undefined;
1005
- const gateAContractHash =
1006
- input.gate === "gate-a"
1007
- ? contractHash(
1008
- "abel-design-gate-a",
1009
- changeContract
1010
- ? canonicalJson(changeContract)
1011
- : (input.contract as string),
1012
- )
1013
- : undefined;
1014
- return this.#transaction(() => {
1015
- this.#run(input.runId, true);
1016
- const request = {
1017
- operation: "approve-gate",
1018
- gate: input.gate,
1019
- ...(gateAContractHash ? { contractHash: gateAContractHash } : {}),
1020
- };
1021
- const replay = this.#operationReplay(
1022
- input.runId,
1023
- input.operationId,
1024
- "approve-gate",
1025
- request,
1026
- );
1027
- if (replay)
1028
- return structuredClone(replay) as ReturnType<
1029
- DesignJournal["approveGate"]
1030
- >;
1031
- let approvedContractHash = gateAContractHash;
1032
- if (input.gate === "gate-b") {
1033
- const current = this.#gateStatus(input.runId);
1034
- if (!current.gateA.current) throw new Error("design-gate-a-required");
1035
- const plan = this.#currentPlanProjection(input.runId);
1036
- if (!plan) throw new Error("design-plan-required");
1037
- const latestDecisionSequence = this.#latestSubstantiveDecisionSequence(
1038
- input.runId,
1039
- );
1040
- if (
1041
- plan.sequence <= (current.gateA.approvedSequence ?? 0) ||
1042
- (latestDecisionSequence !== undefined &&
1043
- plan.sequence <= latestDecisionSequence)
1044
- ) {
1045
- throw new Error("design-plan-stale");
1046
- }
1047
- approvedContractHash = plan.canonicalHash;
1048
- }
1049
- if (!approvedContractHash)
1050
- throw new Error("design-transient-input-invalid");
1051
- const existing = this.#latestApproval(input.runId, input.gate);
1052
- const currentGate = this.#gateStatus(input.runId)[
1053
- input.gate === "gate-a" ? "gateA" : "gateB"
1054
- ];
1055
- let proof: DesignGateProof;
1056
- if (
1057
- existing &&
1058
- currentGate.current &&
1059
- existing.proof.contractHash === approvedContractHash
1060
- ) {
1061
- proof = existing.proof;
1062
- } else {
1063
- const revision = (existing?.proof.revision ?? 0) + 1;
1064
- const payload = {
1065
- gate: input.gate,
1066
- revision,
1067
- contractHash: approvedContractHash,
1068
- ...(changeContract ? { changeContract } : {}),
1069
- };
1070
- const fact = this.#appendFact(
1071
- input.runId,
1072
- "approval",
1073
- `${input.gate}:${revision}`,
1074
- payload,
1075
- );
1076
- proof = {
1077
- revision,
1078
- contractHash: approvedContractHash,
1079
- recordHash: fact.record_hash,
1080
- };
1081
- }
1082
- const outcome = {
1083
- operation: "approve-gate" as const,
1084
- runId: input.runId,
1085
- gate: input.gate,
1086
- proof,
1087
- };
1088
- this.#recordOperation(
1089
- input.runId,
1090
- input.operationId,
1091
- "approve-gate",
1092
- request,
1093
- outcome,
1094
- );
1095
- return structuredClone(outcome);
1096
- });
1097
- }
1098
-
1099
- #latestApproval(
1100
- runId: string,
1101
- gate: DesignGate,
1102
- ): { sequence: number; proof: DesignGateProof } | undefined {
1103
- const rows = this.#facts(runId).filter((row) => row.kind === "approval");
1104
- for (let index = rows.length - 1; index >= 0; index -= 1) {
1105
- const row = rows[index];
1106
- const payload = parseRecord(
1107
- row.payload_json,
1108
- "design-approval-record-invalid",
1109
- );
1110
- if (payload.gate === gate) {
1111
- return {
1112
- sequence: row.sequence,
1113
- proof: {
1114
- revision: Number(payload.revision),
1115
- contractHash: String(payload.contractHash),
1116
- recordHash: row.record_hash,
1117
- },
1118
- };
1119
- }
1120
- }
1121
- return undefined;
1122
- }
1123
-
1124
- #certifyPlan(runId: string, planHash: string): void {
1125
- const gates = this.#gateStatus(runId);
1126
- if (gates.gateB.current && gates.gateB.proof?.contractHash === planHash)
1127
- return;
1128
- const previous = this.#latestApproval(runId, "gate-b");
1129
- const revision = (previous?.proof.revision ?? 0) + 1;
1130
- this.#appendFact(runId, "approval", `gate-b:${revision}`, {
1131
- gate: "gate-b",
1132
- revision,
1133
- contractHash: planHash,
1134
- authority: "compiler",
1135
- });
1136
- }
1137
-
1138
- recordCompiledPlan(input: {
1139
- runId: string;
1140
- operationId: string;
1141
- bytes: Uint8Array;
1142
- rawSha256: string;
1143
- canonicalHash: string;
1144
- lease?: DesignFinalizationLease;
1145
- certify?: boolean;
1146
- }): Record<string, unknown> {
1147
- requireHash(input.rawSha256, "design-plan-raw-hash");
1148
- requireHash(input.canonicalHash, "design-plan-canonical-hash");
1149
- if (
1150
- !(input.bytes instanceof Uint8Array) ||
1151
- input.bytes.byteLength < 1 ||
1152
- input.bytes.byteLength > MAX_PLAN_BYTES ||
1153
- sha256(input.bytes) !== input.rawSha256
1154
- ) {
1155
- throw new Error("design-plan-bytes-invalid");
1156
- }
1157
- const request = {
1158
- operation: "compile-plan",
1159
- rawSha256: input.rawSha256,
1160
- canonicalHash: input.canonicalHash,
1161
- };
1162
- return this.#transaction(() => {
1163
- this.#run(input.runId, true);
1164
- const replay = this.#operationReplay(
1165
- input.runId,
1166
- input.operationId,
1167
- "compile-plan",
1168
- request,
1169
- );
1170
- if (replay) return structuredClone(replay);
1171
- if (input.lease) {
1172
- if (
1173
- input.lease.runId !== input.runId ||
1174
- input.lease.operationId !== input.operationId
1175
- ) {
1176
- throw new Error("design-finalization-lease-fenced");
1177
- }
1178
- this.#assertFinalizationLease(input.lease);
1179
- }
1180
- if (!this.#gateStatus(input.runId).gateA.current) {
1181
- throw new Error("design-gate-a-required");
1182
- }
1183
- const prior = this.#currentPlanProjection(input.runId);
1184
- const status = this.status(input.runId);
1185
- const latestDecision =
1186
- this.#latestSubstantiveDecisionSequence(input.runId) ?? 0;
1187
- if (
1188
- prior &&
1189
- prior.rawSha256 === input.rawSha256 &&
1190
- prior.canonicalHash === input.canonicalHash &&
1191
- prior.sequence > latestDecision &&
1192
- prior.sequence > (status.gates.gateA.approvedSequence ?? 0)
1193
- ) {
1194
- if (input.certify) this.#certifyPlan(input.runId, input.canonicalHash);
1195
- const outcome = {
1196
- operation: "compile-plan",
1197
- runId: input.runId,
1198
- plan: prior,
1199
- gates: this.#gateStatus(input.runId),
1200
- };
1201
- this.#recordOperation(
1202
- input.runId,
1203
- input.operationId,
1204
- "compile-plan",
1205
- request,
1206
- outcome,
1207
- );
1208
- return structuredClone(outcome);
1209
- }
1210
- const revision = (prior?.revision ?? 0) + 1;
1211
- const payload = {
1212
- revision,
1213
- rawSha256: input.rawSha256,
1214
- canonicalHash: input.canonicalHash,
1215
- };
1216
- const fact = this.#appendFact(
1217
- input.runId,
1218
- "plan",
1219
- String(revision),
1220
- payload,
1221
- );
1222
- this.#database
1223
- .prepare(
1224
- `INSERT INTO design_compiled_plans(
1225
- run_id, revision, fact_sequence, raw_sha256, canonical_hash,
1226
- plan_bytes
1227
- ) VALUES (?, ?, ?, ?, ?, ?)`,
1228
- )
1229
- .run(
1230
- input.runId,
1231
- revision,
1232
- fact.sequence,
1233
- input.rawSha256,
1234
- input.canonicalHash,
1235
- Buffer.from(input.bytes),
1236
- );
1237
- const plan: DesignPlanProjection = {
1238
- revision,
1239
- rawSha256: input.rawSha256,
1240
- canonicalHash: input.canonicalHash,
1241
- recordHash: fact.record_hash,
1242
- sequence: fact.sequence,
1243
- };
1244
- if (input.certify) this.#certifyPlan(input.runId, input.canonicalHash);
1245
- const outcome = {
1246
- operation: "compile-plan",
1247
- runId: input.runId,
1248
- plan,
1249
- gates: this.#gateStatus(input.runId),
1250
- };
1251
- this.#recordOperation(
1252
- input.runId,
1253
- input.operationId,
1254
- "compile-plan",
1255
- request,
1256
- outcome,
1257
- );
1258
- return structuredClone(outcome);
1259
- });
1260
- }
1261
-
1262
- #currentPlanProjection(runId: string): DesignPlanProjection | undefined {
1263
- const row = this.#database
1264
- .prepare(
1265
- `SELECT plans.revision, plans.raw_sha256, plans.canonical_hash,
1266
- facts.sequence, facts.record_hash
1267
- FROM design_compiled_plans AS plans
1268
- JOIN design_facts AS facts
1269
- ON facts.run_id = plans.run_id
1270
- AND facts.sequence = plans.fact_sequence
1271
- WHERE plans.run_id = ? ORDER BY plans.revision DESC LIMIT 1`,
1272
- )
1273
- .get(runId) as
1274
- | {
1275
- revision: number;
1276
- raw_sha256: string;
1277
- canonical_hash: string;
1278
- sequence: number;
1279
- record_hash: string;
1280
- }
1281
- | undefined;
1282
- return row
1283
- ? {
1284
- revision: row.revision,
1285
- rawSha256: row.raw_sha256,
1286
- canonicalHash: row.canonical_hash,
1287
- recordHash: row.record_hash,
1288
- sequence: row.sequence,
1289
- }
1290
- : undefined;
1291
- }
1292
-
1293
- currentCompiledPlan(
1294
- runId: string,
1295
- ): { projection: DesignPlanProjection; bytes: Uint8Array } | undefined {
1296
- this.#assertOpen();
1297
- this.#run(runId, false);
1298
- const row = this.#database
1299
- .prepare(
1300
- `SELECT plan_bytes FROM design_compiled_plans
1301
- WHERE run_id = ? ORDER BY revision DESC LIMIT 1`,
1302
- )
1303
- .get(runId) as { plan_bytes: Uint8Array } | undefined;
1304
- const projection = this.#currentPlanProjection(runId);
1305
- if (!row || !projection) return undefined;
1306
- return { projection, bytes: Uint8Array.from(row.plan_bytes) };
1307
- }
1308
-
1309
- verifyGateProof(input: GateProofVerificationInput): boolean {
1310
- this.#assertOpen();
1311
- if (
1312
- typeof input.change !== "string" ||
1313
- input.change.length === 0 ||
1314
- (input.gate !== "gate-a" && input.gate !== "gate-b") ||
1315
- !Number.isSafeInteger(input.proof.revision) ||
1316
- input.proof.revision < 1 ||
1317
- !SHA256.test(input.proof.contractHash) ||
1318
- !SHA256.test(input.proof.recordHash)
1319
- ) {
1320
- return false;
1321
- }
1322
- const row = this.#database
1323
- .prepare(
1324
- `SELECT facts.run_id, facts.payload_json
1325
- FROM design_facts AS facts
1326
- JOIN runs ON runs.run_id = facts.run_id
1327
- WHERE runs.root_hash = ? AND runs.stage = 'abel-design'
1328
- AND runs.change_name = ? AND facts.kind = 'approval'
1329
- AND facts.record_hash = ?`,
1330
- )
1331
- .get(
1332
- this.stateRoot.consumerRootHash,
1333
- input.change,
1334
- input.proof.recordHash,
1335
- ) as { run_id: string; payload_json: string } | undefined;
1336
- if (!row) return false;
1337
- const payload = parseRecord(
1338
- row.payload_json,
1339
- "design-approval-record-invalid",
1340
- );
1341
- if (
1342
- payload.gate !== input.gate ||
1343
- payload.revision !== input.proof.revision ||
1344
- payload.contractHash !== input.proof.contractHash
1345
- ) {
1346
- return false;
1347
- }
1348
- const status = this.#gateStatus(row.run_id)[
1349
- input.gate === "gate-a" ? "gateA" : "gateB"
1350
- ];
1351
- return (
1352
- status.current &&
1353
- status.proof?.recordHash === input.proof.recordHash &&
1354
- status.proof.revision === input.proof.revision &&
1355
- status.proof.contractHash === input.proof.contractHash
1356
- );
1357
- }
1358
-
1359
- verifyFinalizedDelivery(input: FinalizedDeliveryVerificationInput): boolean {
1360
- this.#assertOpen();
1361
- if (
1362
- typeof input.change !== "string" ||
1363
- input.change.length === 0 ||
1364
- !Number.isSafeInteger(input.deliveryRevision) ||
1365
- input.deliveryRevision < 1 ||
1366
- !SHA256.test(input.receiptHash) ||
1367
- !SHA256.test(input.planCanonicalHash)
1368
- ) {
1369
- return false;
1370
- }
1371
- const rows = this.#database
1372
- .prepare(
1373
- `SELECT facts.payload_json
1374
- FROM design_facts AS facts
1375
- JOIN runs ON runs.run_id = facts.run_id
1376
- WHERE runs.root_hash = ? AND runs.stage = 'abel-design'
1377
- AND runs.change_name = ? AND facts.kind = 'finalization'`,
1378
- )
1379
- .all(this.stateRoot.consumerRootHash, input.change) as unknown as Array<{
1380
- payload_json: string;
1381
- }>;
1382
- return rows.some((row) => {
1383
- const payload = parseRecord(
1384
- row.payload_json,
1385
- "design-finalization-record-invalid",
1386
- );
1387
- return (
1388
- payload.deliveryRevision === input.deliveryRevision &&
1389
- payload.receiptHash === input.receiptHash &&
1390
- payload.planCanonicalHash === input.planCanonicalHash &&
1391
- canonicalJson(payload.gateA) === canonicalJson(input.gateA) &&
1392
- canonicalJson(payload.gateB) === canonicalJson(input.gateB)
1393
- );
1394
- });
1395
- }
1396
-
1397
- nextDeliveryRevision(runId: string): number {
1398
- this.#assertOpen();
1399
- const run = this.#run(runId, true);
1400
- if (!run.change_name) throw new Error("design-change-required");
1401
- const rows = this.#database
1402
- .prepare(
1403
- `SELECT facts.payload_json
1404
- FROM design_facts AS facts
1405
- JOIN runs ON runs.run_id = facts.run_id
1406
- WHERE runs.root_hash = ? AND runs.stage = 'abel-design'
1407
- AND runs.change_name = ? AND facts.kind = 'finalization'`,
1408
- )
1409
- .all(
1410
- this.stateRoot.consumerRootHash,
1411
- run.change_name,
1412
- ) as unknown as Array<{
1413
- payload_json: string;
1414
- }>;
1415
- const maximum = rows.reduce((current, row) => {
1416
- const payload = parseRecord(
1417
- row.payload_json,
1418
- "design-finalization-record-invalid",
1419
- );
1420
- const revision = Number(payload.deliveryRevision);
1421
- if (!Number.isSafeInteger(revision) || revision < 1) {
1422
- throw new Error("design-finalization-record-invalid");
1423
- }
1424
- return Math.max(current, revision);
1425
- }, 0);
1426
- return maximum + 1;
1427
- }
1428
-
1429
- recordFinalization(input: {
1430
- runId: string;
1431
- operationId: string;
1432
- lease: DesignFinalizationLease;
1433
- deliveryRevision: number;
1434
- receiptHash: string;
1435
- gateA: DesignGateProof;
1436
- gateB: DesignGateProof;
1437
- planCanonicalHash: string;
1438
- }): Record<string, unknown> {
1439
- if (
1440
- input.lease.runId !== input.runId ||
1441
- input.lease.operationId !== input.operationId
1442
- ) {
1443
- throw new Error("design-finalization-lease-fenced");
1444
- }
1445
- if (
1446
- !Number.isSafeInteger(input.deliveryRevision) ||
1447
- input.deliveryRevision < 1
1448
- ) {
1449
- throw new Error("design-delivery-revision-invalid");
1450
- }
1451
- for (const [label, value] of [
1452
- ["design-receipt-hash", input.receiptHash],
1453
- ["design-plan-canonical-hash", input.planCanonicalHash],
1454
- ["design-gate-a-contract-hash", input.gateA.contractHash],
1455
- ["design-gate-a-record-hash", input.gateA.recordHash],
1456
- ["design-gate-b-contract-hash", input.gateB.contractHash],
1457
- ["design-gate-b-record-hash", input.gateB.recordHash],
1458
- ] as const) {
1459
- requireHash(value, label);
1460
- }
1461
- const request = {
1462
- operation: "finalize-delivery",
1463
- deliveryRevision: input.deliveryRevision,
1464
- receiptHash: input.receiptHash,
1465
- gateA: input.gateA,
1466
- gateB: input.gateB,
1467
- planCanonicalHash: input.planCanonicalHash,
1468
- };
1469
- return this.#transaction(() => {
1470
- this.#run(input.runId, false);
1471
- const replay = this.#operationReplay(
1472
- input.runId,
1473
- input.operationId,
1474
- "finalize-delivery",
1475
- request,
1476
- );
1477
- if (replay) return structuredClone(replay);
1478
- this.#run(input.runId, true);
1479
- this.#assertFinalizationLease(input.lease);
1480
- const gates = this.#gateStatus(input.runId);
1481
- const plan = this.#currentPlanProjection(input.runId);
1482
- if (
1483
- !gates.gateA.current ||
1484
- !gates.gateB.current ||
1485
- canonicalJson(gates.gateA.proof) !== canonicalJson(input.gateA) ||
1486
- canonicalJson(gates.gateB.proof) !== canonicalJson(input.gateB) ||
1487
- !plan ||
1488
- plan.canonicalHash !== input.planCanonicalHash ||
1489
- input.gateB.contractHash !== input.planCanonicalHash
1490
- ) {
1491
- throw new Error("design-finalization-proof-stale");
1492
- }
1493
- const expectedRevision = this.nextDeliveryRevision(input.runId);
1494
- if (input.deliveryRevision !== expectedRevision) {
1495
- throw new Error("design-delivery-revision-conflict");
1496
- }
1497
- const payload = {
1498
- deliveryRevision: input.deliveryRevision,
1499
- receiptHash: input.receiptHash,
1500
- gateA: input.gateA,
1501
- gateB: input.gateB,
1502
- planCanonicalHash: input.planCanonicalHash,
1503
- };
1504
- this.#appendFact(
1505
- input.runId,
1506
- "finalization",
1507
- String(input.deliveryRevision),
1508
- payload,
1509
- );
1510
- const outcome = {
1511
- operation: "finalize-delivery",
1512
- runId: input.runId,
1513
- state: "completed",
1514
- completed: true,
1515
- deliveryRevision: input.deliveryRevision,
1516
- receiptHash: input.receiptHash,
1517
- };
1518
- this.#recordOperation(
1519
- input.runId,
1520
- input.operationId,
1521
- "finalize-delivery",
1522
- request,
1523
- outcome,
1524
- );
1525
- return structuredClone(outcome);
1526
- });
1527
- }
1528
-
1529
- #latestSubstantiveDecisionSequence(
1530
- runId: string,
1531
- category?: DecisionCategory,
1532
- ): number | undefined {
1533
- return this.#facts(runId)
1534
- .filter((fact) => {
1535
- if (fact.kind !== "decision") return false;
1536
- const decision = parseRecord(
1537
- fact.payload_json,
1538
- "design-decision-record-invalid",
1539
- );
1540
- return (
1541
- decision.authorityChanged !== false &&
1542
- (category === undefined || decision.category === category)
1543
- );
1544
- })
1545
- .at(-1)?.sequence;
1546
- }
1547
-
1548
- #gateStatus(runId: string): DesignStatusProjection["gates"] {
1549
- const latestBehavior = this.#latestSubstantiveDecisionSequence(
1550
- runId,
1551
- "behavior",
1552
- );
1553
- const latestTechnical = this.#latestSubstantiveDecisionSequence(
1554
- runId,
1555
- "technical",
1556
- );
1557
- const plan = this.#currentPlanProjection(runId);
1558
- const gateAApproval = this.#latestApproval(runId, "gate-a");
1559
- const gateBApproval = this.#latestApproval(runId, "gate-b");
1560
- const gateAChanged =
1561
- gateAApproval !== undefined &&
1562
- latestBehavior !== undefined &&
1563
- latestBehavior > gateAApproval.sequence;
1564
- const gateA: DesignGateProjection = gateAApproval
1565
- ? {
1566
- current: !gateAChanged,
1567
- proof: gateAApproval.proof,
1568
- approvedSequence: gateAApproval.sequence,
1569
- ...(gateAChanged
1570
- ? { staleReason: "behavior-decision-changed" as const }
1571
- : {}),
1572
- }
1573
- : { current: false };
1574
-
1575
- let staleReason: DesignGateProjection["staleReason"];
1576
- if (gateBApproval) {
1577
- if (
1578
- latestBehavior !== undefined &&
1579
- latestBehavior > gateBApproval.sequence
1580
- ) {
1581
- staleReason = "behavior-decision-changed";
1582
- } else if (
1583
- latestTechnical !== undefined &&
1584
- latestTechnical > gateBApproval.sequence
1585
- ) {
1586
- staleReason = "technical-decision-changed";
1587
- } else if (
1588
- gateAApproval !== undefined &&
1589
- gateAApproval.sequence > gateBApproval.sequence
1590
- ) {
1591
- staleReason = "gate-a-reapproved";
1592
- } else if (plan && plan.sequence > gateBApproval.sequence) {
1593
- staleReason = "plan-recompiled";
1594
- } else if (!plan) {
1595
- staleReason = "plan-missing";
1596
- } else if (!gateA.current) {
1597
- staleReason = "gate-a-stale";
1598
- } else if (plan.canonicalHash !== gateBApproval.proof.contractHash) {
1599
- staleReason = "plan-recompiled";
1600
- }
1601
- }
1602
- const gateB: DesignGateProjection = gateBApproval
1603
- ? {
1604
- current: staleReason === undefined,
1605
- proof: gateBApproval.proof,
1606
- approvedSequence: gateBApproval.sequence,
1607
- ...(staleReason ? { staleReason } : {}),
1608
- }
1609
- : { current: false };
1610
- return { gateA, gateB };
1611
- }
1612
-
1613
- /** Seed a new revision only from a completed, privately finalized delivery. */
1614
- inheritFinalizedAuthority(runId: string): void {
1615
- this.#transaction(() => {
1616
- const run = this.#run(runId, true);
1617
- if (!run.change_name || this.#facts(runId).length > 0) return;
1618
- const candidates = this.#database
1619
- .prepare(`
1620
- SELECT facts.run_id, facts.payload_json FROM design_facts AS facts
1621
- JOIN runs ON runs.run_id = facts.run_id
1622
- WHERE runs.root_hash = ? AND runs.stage = 'abel-design'
1623
- AND runs.change_name = ? AND runs.state = 'completed'
1624
- AND facts.kind = 'finalization' AND runs.run_id <> ?
1625
- `)
1626
- .all(run.root_hash, run.change_name, runId) as unknown as Array<{
1627
- run_id: string;
1628
- payload_json: string;
1629
- }>;
1630
- const source = candidates
1631
- .map((row) => ({
1632
- runId: row.run_id,
1633
- fact: parseRecord(
1634
- row.payload_json,
1635
- "design-finalization-record-invalid",
1636
- ),
1637
- }))
1638
- .sort(
1639
- (left, right) =>
1640
- Number(right.fact.deliveryRevision) -
1641
- Number(left.fact.deliveryRevision),
1642
- )[0];
1643
- if (!source) return;
1644
- const status = this.status(source.runId);
1645
- const plan = this.currentCompiledPlan(source.runId);
1646
- const gateA = status.gates.gateA.proof;
1647
- const gateB = status.gates.gateB.proof;
1648
- if (
1649
- !plan ||
1650
- !gateA ||
1651
- !gateB ||
1652
- !status.gates.gateA.current ||
1653
- !status.gates.gateB.current ||
1654
- sha256(plan.bytes) !== plan.projection.rawSha256 ||
1655
- !this.verifyFinalizedDelivery({
1656
- change: run.change_name,
1657
- deliveryRevision: Number(source.fact.deliveryRevision),
1658
- receiptHash: String(source.fact.receiptHash),
1659
- gateA,
1660
- gateB,
1661
- planCanonicalHash: plan.projection.canonicalHash,
1662
- })
1663
- ) {
1664
- throw new Error("design-inherited-authority-invalid");
1665
- }
1666
- this.#appendFact(runId, "inheritance", "finalized-authority", {
1667
- sourceRunId: source.runId,
1668
- deliveryRevision: source.fact.deliveryRevision,
1669
- receiptHash: source.fact.receiptHash,
1670
- gateA,
1671
- gateB,
1672
- });
1673
- for (const decision of status.decisions) {
1674
- this.#appendFact(
1675
- runId,
1676
- "decision",
1677
- `${decision.decisionId}:${decision.revision}`,
1678
- {
1679
- decisionId: decision.decisionId,
1680
- category: decision.category,
1681
- revision: decision.revision,
1682
- contractHash: decision.contractHash,
1683
- refs: decision.refs,
1684
- },
1685
- );
1686
- }
1687
- this.#appendFact(runId, "approval", `gate-a:${gateA.revision}`, {
1688
- gate: "gate-a",
1689
- revision: gateA.revision,
1690
- contractHash: gateA.contractHash,
1691
- ...(status.changeContract
1692
- ? { changeContract: status.changeContract }
1693
- : {}),
1694
- });
1695
- const projection = plan.projection;
1696
- const fact = this.#appendFact(
1697
- runId,
1698
- "plan",
1699
- String(projection.revision),
1700
- {
1701
- revision: projection.revision,
1702
- rawSha256: projection.rawSha256,
1703
- canonicalHash: projection.canonicalHash,
1704
- },
1705
- );
1706
- this.#database
1707
- .prepare(`INSERT INTO design_compiled_plans(
1708
- run_id, revision, fact_sequence, raw_sha256, canonical_hash, plan_bytes
1709
- ) VALUES (?, ?, ?, ?, ?, ?)`)
1710
- .run(
1711
- runId,
1712
- projection.revision,
1713
- fact.sequence,
1714
- projection.rawSha256,
1715
- projection.canonicalHash,
1716
- Buffer.from(plan.bytes),
1717
- );
1718
- this.#appendFact(runId, "approval", `gate-b:${gateB.revision}`, {
1719
- gate: "gate-b",
1720
- revision: gateB.revision,
1721
- contractHash: gateB.contractHash,
1722
- });
1723
- });
1724
- }
1725
-
1726
- status(runId: string): DesignStatusProjection {
1727
- this.#assertOpen();
1728
- const run = this.#run(runId, false);
1729
- const facts = this.#facts(runId);
1730
- const evidence = facts
1731
- .filter((fact) => fact.kind === "evidence")
1732
- .map((fact) =>
1733
- this.#evidenceProjection(
1734
- fact,
1735
- parseRecord(fact.payload_json, "design-evidence-record-invalid"),
1736
- ),
1737
- );
1738
- const latestDecisions = new Map<string, DesignDecisionProjection>();
1739
- for (const fact of facts.filter(
1740
- (candidate) => candidate.kind === "decision",
1741
- )) {
1742
- const decision = this.#decisionProjection(
1743
- fact,
1744
- parseRecord(fact.payload_json, "design-decision-record-invalid"),
1745
- );
1746
- latestDecisions.set(decision.decisionId, decision);
1747
- }
1748
- const approved = facts
1749
- .filter((fact) => fact.kind === "approval")
1750
- .map((fact) => parseRecord(fact.payload_json, "design-approval-invalid"))
1751
- .filter((fact) => fact.gate === "gate-a")
1752
- .at(-1);
1753
- return {
1754
- runId,
1755
- ...(run.change_name ? { change: run.change_name } : {}),
1756
- ...(approved?.changeContract
1757
- ? { changeContract: normalizeChangeContract(approved.changeContract) }
1758
- : {}),
1759
- evidence,
1760
- decisions: [...latestDecisions.values()].sort((left, right) =>
1761
- compareCanonicalStrings(left.decisionId, right.decisionId),
1762
- ),
1763
- gates: this.#gateStatus(runId),
1764
- plan: this.#currentPlanProjection(runId) ?? null,
1765
- };
1766
- }
1767
-
1768
- close(): void {
1769
- if (this.#closed) return;
1770
- this.#database.close();
1771
- this.#closed = true;
1772
- }
1773
- }