@abelxiaoxing/cadence 1.0.14 → 1.1.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 (48) hide show
  1. package/README.md +39 -13
  2. package/agents/contract-reviewer.md +2 -2
  3. package/agents/design-explorer.md +1 -1
  4. package/agents/diagnosis-worker.md +6 -3
  5. package/agents/implementation-worker.md +14 -9
  6. package/config/.env.example +5 -17
  7. package/config/routes.example.json +42 -0
  8. package/package.json +3 -3
  9. package/prompts/abel-design.md +206 -228
  10. package/prompts/abel-diagnose.md +39 -18
  11. package/prompts/abel-implement.md +122 -178
  12. package/prompts/abel-init.md +36 -33
  13. package/skills/abel-workflow/SKILL.md +190 -168
  14. package/src/apply-transaction.ts +1427 -0
  15. package/src/artifact-store.ts +204 -0
  16. package/src/child-session.ts +136 -17
  17. package/src/contracts.ts +121 -696
  18. package/src/control-contracts.ts +246 -0
  19. package/src/delivery-compiler.ts +1118 -0
  20. package/src/design-control.ts +846 -0
  21. package/src/design-journal.ts +1569 -0
  22. package/src/file-snapshot.ts +5 -0
  23. package/src/implement-graph.ts +11 -4
  24. package/src/index.ts +1833 -118
  25. package/src/isolation-backend.ts +269 -0
  26. package/src/packet-runtime.ts +533 -0
  27. package/src/parent-provider.ts +23 -4
  28. package/src/route-policy.ts +483 -0
  29. package/src/run-state.ts +430 -0
  30. package/src/run-store.ts +990 -0
  31. package/src/safe-path.ts +11 -0
  32. package/src/scoped-tools.ts +36 -0
  33. package/src/state-root.ts +215 -0
  34. package/src/subagent-activity.ts +526 -27
  35. package/src/submit-tool.ts +274 -0
  36. package/src/task-ledger.ts +1380 -0
  37. package/src/verification-capability.ts +560 -45
  38. package/src/worker-broker.ts +703 -0
  39. package/src/workflow-engine.ts +7706 -0
  40. package/src/workspace-store.ts +544 -0
  41. package/src/candidate-preflight.ts +0 -1618
  42. package/src/drain.ts +0 -20
  43. package/src/patch.ts +0 -505
  44. package/src/result-store.ts +0 -312
  45. package/src/runtime.ts +0 -2436
  46. package/src/scheduler.ts +0 -384
  47. package/src/subagent-endpoint.ts +0 -158
  48. package/src/worker.ts +0 -204
@@ -0,0 +1,1118 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ type AgentsImpact,
4
+ type ImplementGraphBoundary,
5
+ type ImplementGraphOutput,
6
+ type ImplementTaskBoundary,
7
+ isAgentsPath,
8
+ isValidRelativePath,
9
+ type PhaseBoundary,
10
+ type StructuredVerificationContract,
11
+ validateImplementGraphBoundary,
12
+ validateVerificationContract,
13
+ } from "./contracts.ts";
14
+ import {
15
+ assessImplementGraphReadiness,
16
+ canonicalJson,
17
+ hashCanonicalValue,
18
+ type ImplementGraphReadiness,
19
+ } from "./implement-graph.ts";
20
+ import { validateVerificationAdapterCapability } from "./verification-capability.ts";
21
+
22
+ export const IMPLEMENT_PLAN_SCHEMA_VERSION = 3 as const;
23
+ export const IMPLEMENT_PLAN_PATH = "implement-plan.json" as const;
24
+ export const DELIVERY_RECEIPT_VERSION = 4 as const;
25
+ export const GATE_A_RECEIPT_PATH = "gate-a.yaml" as const;
26
+ export const READY_RECEIPT_PATH = "ready.yaml" as const;
27
+
28
+ const SHA256 = /^[a-f0-9]{64}$/u;
29
+ const CHANGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,126}[a-z0-9])?$/u;
30
+ const AGENTS_START = "<!-- ABEL:AGENTS-INDEX:START -->";
31
+ const AGENTS_END = "<!-- ABEL:AGENTS-INDEX:END -->";
32
+
33
+ export interface DeliveryArtifactBinding {
34
+ path: string;
35
+ rawSha256: string;
36
+ }
37
+
38
+ export interface GateApprovalProof {
39
+ revision: number;
40
+ contractHash: string;
41
+ recordHash: string;
42
+ }
43
+
44
+ export interface GateAReceipt {
45
+ receiptVersion: typeof DELIVERY_RECEIPT_VERSION;
46
+ change: string;
47
+ schema: string;
48
+ approval: GateApprovalProof;
49
+ artifacts: DeliveryArtifactBinding[];
50
+ }
51
+
52
+ export interface DeliveryTraceability {
53
+ taskPath: "tasks.md";
54
+ referenceCount: number;
55
+ taskCount: number;
56
+ verificationCount: number;
57
+ canonicalHash: string;
58
+ }
59
+
60
+ export interface ReadyReceipt {
61
+ receiptVersion: typeof DELIVERY_RECEIPT_VERSION;
62
+ change: string;
63
+ schema: string;
64
+ deliveryRevision: number;
65
+ approvals: {
66
+ gateA: {
67
+ path: typeof GATE_A_RECEIPT_PATH;
68
+ rawSha256: string;
69
+ };
70
+ gateB: {
71
+ revision: number;
72
+ contractHash: string;
73
+ recordHash: string;
74
+ };
75
+ };
76
+ artifacts: DeliveryArtifactBinding[];
77
+ plan: CompiledDelivery["receipt"]["plan"];
78
+ verificationClosure: ImplementGraphReadiness["closure"];
79
+ traceability: DeliveryTraceability;
80
+ openspec: {
81
+ strict: true;
82
+ planningComplete: true;
83
+ };
84
+ }
85
+
86
+ export type DeliveryTraceabilityAssessment =
87
+ | { ok: true; value: DeliveryTraceability }
88
+ | { ok: false; diagnostics: string[] };
89
+
90
+ export class DeliveryValidationError extends Error {
91
+ readonly diagnostics: readonly string[];
92
+
93
+ constructor(diagnostics: readonly string[]) {
94
+ const normalized = [...new Set(diagnostics)].sort();
95
+ super("delivery-invalid");
96
+ this.name = "DeliveryValidationError";
97
+ this.diagnostics = Object.freeze(normalized);
98
+ }
99
+ }
100
+
101
+ export function assessDeliveryTraceability(input: {
102
+ tasksMarkdown: string;
103
+ specs: readonly { path: string; text: string }[];
104
+ plan: ImplementPlan;
105
+ }): DeliveryTraceabilityAssessment {
106
+ const diagnostics = new Set<string>();
107
+ const headings = new Set<string>();
108
+ for (const spec of input.specs) {
109
+ if (!isValidRelativePath(spec.path) || !spec.path.endsWith("/spec.md")) {
110
+ diagnostics.add("traceability-spec-path-invalid");
111
+ continue;
112
+ }
113
+ let requirement = "";
114
+ for (const line of spec.text.split(/\r?\n/u)) {
115
+ const requirementMatch = /^### Requirement: (.+)$/u.exec(line);
116
+ if (requirementMatch?.[1]) {
117
+ requirement = requirementMatch[1].trim();
118
+ continue;
119
+ }
120
+ const scenarioMatch = /^#### Scenario: (.+)$/u.exec(line);
121
+ if (scenarioMatch?.[1] && requirement) {
122
+ const heading = `${spec.path}#${requirement}/${scenarioMatch[1].trim()}`;
123
+ if (headings.has(heading)) {
124
+ diagnostics.add("traceability-heading-duplicate");
125
+ }
126
+ headings.add(heading);
127
+ }
128
+ }
129
+ }
130
+ const references = [
131
+ ...input.tasksMarkdown.matchAll(/`(specs\/[^`\n]+\/spec\.md#[^`\n]+)`/gu),
132
+ ].flatMap((match) => (match[1] ? [match[1].trim()] : []));
133
+ if (references.length === 0) diagnostics.add("traceability-reference-empty");
134
+ const referenceSet = new Set(references);
135
+ if (referenceSet.size !== references.length) {
136
+ diagnostics.add("traceability-reference-duplicate");
137
+ }
138
+ if (references.some((reference) => !headings.has(reference))) {
139
+ diagnostics.add("traceability-reference-unresolved");
140
+ }
141
+ if ([...headings].some((heading) => !referenceSet.has(heading))) {
142
+ diagnostics.add("traceability-scenario-unowned");
143
+ }
144
+ const taskIds = input.plan.tasks.map((task) => task.taskId).sort();
145
+ if (
146
+ taskIds.some((taskId) => !input.tasksMarkdown.includes(`\`${taskId}\``))
147
+ ) {
148
+ diagnostics.add("traceability-task-unmapped");
149
+ }
150
+ const verificationIds = input.plan.tasks
151
+ .flatMap((task) => [
152
+ task.phases.red.verification.id,
153
+ task.phases.green.verification.id,
154
+ ...(task.phases.refactor ? [task.phases.refactor.verification.id] : []),
155
+ ])
156
+ .sort();
157
+ if (
158
+ verificationIds.some(
159
+ (verificationId) => !input.tasksMarkdown.includes(verificationId),
160
+ )
161
+ ) {
162
+ diagnostics.add("traceability-verification-unmapped");
163
+ }
164
+ if (diagnostics.size > 0) {
165
+ return { ok: false, diagnostics: [...diagnostics].sort() };
166
+ }
167
+ return {
168
+ ok: true,
169
+ value: {
170
+ taskPath: "tasks.md",
171
+ referenceCount: references.length,
172
+ taskCount: taskIds.length,
173
+ verificationCount: verificationIds.length,
174
+ canonicalHash: hashCanonicalValue({
175
+ references: [...referenceSet].sort(),
176
+ taskIds,
177
+ verificationIds,
178
+ }),
179
+ },
180
+ };
181
+ }
182
+
183
+ export type PlanPhaseDraft = PhaseBoundary;
184
+
185
+ export interface PlanTaskDraft extends Omit<ImplementTaskBoundary, "phases"> {
186
+ phases: {
187
+ red: PlanPhaseDraft;
188
+ green: PlanPhaseDraft;
189
+ refactor?: PlanPhaseDraft;
190
+ };
191
+ affectedVerification: StructuredVerificationContract;
192
+ repairVerification: StructuredVerificationContract;
193
+ }
194
+
195
+ export interface PlanVerification {
196
+ baseline: {
197
+ target: "task-red-contracts";
198
+ affected: "task-affected-contracts";
199
+ fullSuite: StructuredVerificationContract;
200
+ failureIdentity: "normalized-v1";
201
+ };
202
+ change: {
203
+ affected: "task-affected-contracts";
204
+ fullSuite: StructuredVerificationContract;
205
+ postApply: StructuredVerificationContract;
206
+ };
207
+ artifactCorrection: {
208
+ maxAttempts: number;
209
+ };
210
+ repair: {
211
+ maxAttempts: number;
212
+ inBoundaryOnly: true;
213
+ approvalOnBoundaryExpansion: true;
214
+ attribution: ["pre-existing", "introduced", "unresolved", "environment"];
215
+ };
216
+ agentsCheckpoint: {
217
+ required: boolean;
218
+ verification: StructuredVerificationContract | null;
219
+ operations: PlanAgentsCheckpointOperation[];
220
+ };
221
+ }
222
+
223
+ export interface PlanAgentsCheckpointOperation {
224
+ target: string;
225
+ impact: Exclude<AgentsImpact, "none">;
226
+ taskIds: string[];
227
+ managedBlock: string | null;
228
+ }
229
+
230
+ export interface PlanTracking {
231
+ path: "tasks.md";
232
+ format: "markdown-checkbox";
233
+ taskIds: string[];
234
+ completionOwner: "parent";
235
+ }
236
+
237
+ export interface PlanDraft {
238
+ schemaVersion: typeof IMPLEMENT_PLAN_SCHEMA_VERSION;
239
+ changeId: string;
240
+ tasks: PlanTaskDraft[];
241
+ outputs: ImplementGraphOutput[];
242
+ verification: PlanVerification;
243
+ tracking: PlanTracking;
244
+ }
245
+
246
+ export interface ImplementPlan extends PlanDraft {
247
+ schemaVersion: typeof IMPLEMENT_PLAN_SCHEMA_VERSION;
248
+ }
249
+
250
+ export interface CompiledDelivery {
251
+ plan: ImplementPlan;
252
+ bytes: Uint8Array;
253
+ rawSha256: string;
254
+ planHash: string;
255
+ closure: ImplementGraphReadiness["closure"];
256
+ receipt: {
257
+ plan: {
258
+ path: typeof IMPLEMENT_PLAN_PATH;
259
+ schemaVersion: typeof IMPLEMENT_PLAN_SCHEMA_VERSION;
260
+ rawSha256: string;
261
+ canonicalHash: string;
262
+ };
263
+ verificationClosure: ImplementGraphReadiness["closure"];
264
+ };
265
+ tasksMarkdown: string;
266
+ }
267
+
268
+ function isRecord(value: unknown): value is Record<string, unknown> {
269
+ return value !== null && typeof value === "object" && !Array.isArray(value);
270
+ }
271
+
272
+ function exactKeys(
273
+ value: Record<string, unknown>,
274
+ keys: readonly string[],
275
+ ): boolean {
276
+ const actual = Object.keys(value);
277
+ return (
278
+ actual.length === keys.length && actual.every((key) => keys.includes(key))
279
+ );
280
+ }
281
+
282
+ function canonicalBytes(value: unknown): Uint8Array {
283
+ return new TextEncoder().encode(`${canonicalJson(value)}\n`);
284
+ }
285
+
286
+ function normalizeArtifactBindings(
287
+ value: readonly DeliveryArtifactBinding[],
288
+ ): DeliveryArtifactBinding[] {
289
+ if (!Array.isArray(value) || value.length === 0) {
290
+ throw new Error("delivery-artifacts-invalid");
291
+ }
292
+ const paths = new Set<string>();
293
+ const normalized = value.map((binding) => {
294
+ if (
295
+ !isRecord(binding) ||
296
+ !exactKeys(binding, ["path", "rawSha256"]) ||
297
+ typeof binding.path !== "string" ||
298
+ !isValidRelativePath(binding.path) ||
299
+ binding.path === READY_RECEIPT_PATH ||
300
+ typeof binding.rawSha256 !== "string" ||
301
+ !SHA256.test(binding.rawSha256) ||
302
+ paths.has(binding.path)
303
+ ) {
304
+ throw new Error("delivery-artifacts-invalid");
305
+ }
306
+ paths.add(binding.path);
307
+ return { path: binding.path, rawSha256: binding.rawSha256 };
308
+ });
309
+ return normalized.sort((left, right) => left.path.localeCompare(right.path));
310
+ }
311
+
312
+ function requireReceiptIdentity(
313
+ change: unknown,
314
+ schema: unknown,
315
+ ): asserts change is string {
316
+ if (
317
+ typeof change !== "string" ||
318
+ !CHANGE_NAME.test(change) ||
319
+ typeof schema !== "string" ||
320
+ schema.length === 0 ||
321
+ schema.length > 128
322
+ ) {
323
+ throw new Error("delivery-receipt-identity-invalid");
324
+ }
325
+ }
326
+
327
+ function normalizeGateProof(value: GateApprovalProof): GateApprovalProof {
328
+ if (
329
+ !isRecord(value) ||
330
+ !exactKeys(value, ["revision", "contractHash", "recordHash"]) ||
331
+ !Number.isSafeInteger(value.revision) ||
332
+ value.revision < 1 ||
333
+ typeof value.contractHash !== "string" ||
334
+ !SHA256.test(value.contractHash) ||
335
+ typeof value.recordHash !== "string" ||
336
+ !SHA256.test(value.recordHash)
337
+ ) {
338
+ throw new Error("delivery-gate-proof-invalid");
339
+ }
340
+ return structuredClone(value);
341
+ }
342
+
343
+ function parseCanonicalReceipt<T>(bytes: Uint8Array, code: string): T {
344
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
345
+ let parsed: unknown;
346
+ try {
347
+ parsed = JSON.parse(text);
348
+ } catch {
349
+ throw new Error(code);
350
+ }
351
+ if (`${canonicalJson(parsed)}\n` !== text) throw new Error(code);
352
+ return parsed as T;
353
+ }
354
+
355
+ export function compileGateAReceipt(input: {
356
+ change: string;
357
+ schema: string;
358
+ approval: GateApprovalProof;
359
+ artifacts: readonly DeliveryArtifactBinding[];
360
+ }): { receipt: GateAReceipt; bytes: Uint8Array; rawSha256: string } {
361
+ requireReceiptIdentity(input.change, input.schema);
362
+ const receipt: GateAReceipt = {
363
+ receiptVersion: DELIVERY_RECEIPT_VERSION,
364
+ change: input.change,
365
+ schema: input.schema,
366
+ approval: normalizeGateProof(input.approval),
367
+ artifacts: normalizeArtifactBindings(input.artifacts),
368
+ };
369
+ const bytes = canonicalBytes(receipt);
370
+ return {
371
+ receipt,
372
+ bytes,
373
+ rawSha256: createHash("sha256").update(bytes).digest("hex"),
374
+ };
375
+ }
376
+
377
+ export function parseGateAReceipt(bytes: Uint8Array): GateAReceipt {
378
+ const receipt = parseCanonicalReceipt<GateAReceipt>(
379
+ bytes,
380
+ "gate-a-receipt-invalid",
381
+ );
382
+ if (
383
+ !isRecord(receipt) ||
384
+ !exactKeys(receipt, [
385
+ "receiptVersion",
386
+ "change",
387
+ "schema",
388
+ "approval",
389
+ "artifacts",
390
+ ]) ||
391
+ receipt.receiptVersion !== DELIVERY_RECEIPT_VERSION ||
392
+ !isRecord(receipt.approval) ||
393
+ !exactKeys(receipt.approval, ["revision", "contractHash", "recordHash"])
394
+ ) {
395
+ throw new Error("gate-a-receipt-invalid");
396
+ }
397
+ requireReceiptIdentity(receipt.change, receipt.schema);
398
+ const approval = normalizeGateProof(receipt.approval);
399
+ const artifacts = normalizeArtifactBindings(receipt.artifacts);
400
+ if (
401
+ canonicalJson(approval) !== canonicalJson(receipt.approval) ||
402
+ canonicalJson(artifacts) !== canonicalJson(receipt.artifacts)
403
+ ) {
404
+ throw new Error("gate-a-receipt-invalid");
405
+ }
406
+ return structuredClone(receipt);
407
+ }
408
+
409
+ function normalizeTraceability(
410
+ value: DeliveryTraceability,
411
+ ): DeliveryTraceability {
412
+ if (
413
+ !isRecord(value) ||
414
+ !exactKeys(value, [
415
+ "taskPath",
416
+ "referenceCount",
417
+ "taskCount",
418
+ "verificationCount",
419
+ "canonicalHash",
420
+ ]) ||
421
+ value.taskPath !== "tasks.md" ||
422
+ !Number.isSafeInteger(value.referenceCount) ||
423
+ value.referenceCount < 1 ||
424
+ !Number.isSafeInteger(value.taskCount) ||
425
+ value.taskCount < 1 ||
426
+ !Number.isSafeInteger(value.verificationCount) ||
427
+ value.verificationCount < 1 ||
428
+ typeof value.canonicalHash !== "string" ||
429
+ !SHA256.test(value.canonicalHash)
430
+ ) {
431
+ throw new Error("delivery-traceability-invalid");
432
+ }
433
+ return structuredClone(value);
434
+ }
435
+
436
+ export function compileReadyReceipt(input: {
437
+ change: string;
438
+ schema: string;
439
+ deliveryRevision: number;
440
+ gateA: { rawSha256: string };
441
+ gateB: GateApprovalProof;
442
+ artifacts: readonly DeliveryArtifactBinding[];
443
+ compiledPlan: CompiledDelivery;
444
+ traceability: DeliveryTraceability;
445
+ }): { receipt: ReadyReceipt; bytes: Uint8Array; rawSha256: string } {
446
+ requireReceiptIdentity(input.change, input.schema);
447
+ if (
448
+ !Number.isSafeInteger(input.deliveryRevision) ||
449
+ input.deliveryRevision < 1 ||
450
+ !SHA256.test(input.gateA.rawSha256) ||
451
+ input.compiledPlan.plan.changeId !== input.change ||
452
+ input.gateB.contractHash !== input.compiledPlan.planHash
453
+ ) {
454
+ throw new Error("delivery-receipt-invalid");
455
+ }
456
+ const receipt: ReadyReceipt = {
457
+ receiptVersion: DELIVERY_RECEIPT_VERSION,
458
+ change: input.change,
459
+ schema: input.schema,
460
+ deliveryRevision: input.deliveryRevision,
461
+ approvals: {
462
+ gateA: {
463
+ path: GATE_A_RECEIPT_PATH,
464
+ rawSha256: input.gateA.rawSha256,
465
+ },
466
+ gateB: normalizeGateProof(input.gateB),
467
+ },
468
+ artifacts: normalizeArtifactBindings(input.artifacts),
469
+ plan: structuredClone(input.compiledPlan.receipt.plan),
470
+ verificationClosure: structuredClone(
471
+ input.compiledPlan.receipt.verificationClosure,
472
+ ),
473
+ traceability: normalizeTraceability(input.traceability),
474
+ openspec: { strict: true, planningComplete: true },
475
+ };
476
+ const bytes = canonicalBytes(receipt);
477
+ return {
478
+ receipt,
479
+ bytes,
480
+ rawSha256: createHash("sha256").update(bytes).digest("hex"),
481
+ };
482
+ }
483
+
484
+ export function parseReadyReceipt(bytes: Uint8Array): ReadyReceipt {
485
+ const receipt = parseCanonicalReceipt<ReadyReceipt>(
486
+ bytes,
487
+ "delivery-receipt-invalid",
488
+ );
489
+ if (
490
+ !isRecord(receipt) ||
491
+ !exactKeys(receipt, [
492
+ "receiptVersion",
493
+ "change",
494
+ "schema",
495
+ "deliveryRevision",
496
+ "approvals",
497
+ "artifacts",
498
+ "plan",
499
+ "verificationClosure",
500
+ "traceability",
501
+ "openspec",
502
+ ]) ||
503
+ receipt.receiptVersion !== DELIVERY_RECEIPT_VERSION ||
504
+ !Number.isSafeInteger(receipt.deliveryRevision) ||
505
+ receipt.deliveryRevision < 1 ||
506
+ !isRecord(receipt.approvals) ||
507
+ !exactKeys(receipt.approvals, ["gateA", "gateB"]) ||
508
+ !isRecord(receipt.approvals.gateA) ||
509
+ !exactKeys(receipt.approvals.gateA, ["path", "rawSha256"]) ||
510
+ receipt.approvals.gateA.path !== GATE_A_RECEIPT_PATH ||
511
+ !SHA256.test(receipt.approvals.gateA.rawSha256) ||
512
+ !isRecord(receipt.approvals.gateB) ||
513
+ !exactKeys(receipt.approvals.gateB, [
514
+ "revision",
515
+ "contractHash",
516
+ "recordHash",
517
+ ]) ||
518
+ !isRecord(receipt.plan) ||
519
+ !exactKeys(receipt.plan, [
520
+ "path",
521
+ "schemaVersion",
522
+ "rawSha256",
523
+ "canonicalHash",
524
+ ]) ||
525
+ receipt.plan.path !== IMPLEMENT_PLAN_PATH ||
526
+ receipt.plan.schemaVersion !== IMPLEMENT_PLAN_SCHEMA_VERSION ||
527
+ !SHA256.test(receipt.plan.rawSha256) ||
528
+ !SHA256.test(receipt.plan.canonicalHash) ||
529
+ !isRecord(receipt.verificationClosure) ||
530
+ !exactKeys(receipt.verificationClosure, ["executable", "diagnostics"]) ||
531
+ receipt.verificationClosure.executable !== true ||
532
+ !Array.isArray(receipt.verificationClosure.diagnostics) ||
533
+ receipt.verificationClosure.diagnostics.length !== 0 ||
534
+ !isRecord(receipt.openspec) ||
535
+ !exactKeys(receipt.openspec, ["strict", "planningComplete"]) ||
536
+ receipt.openspec.strict !== true ||
537
+ receipt.openspec.planningComplete !== true
538
+ ) {
539
+ throw new Error("delivery-receipt-invalid");
540
+ }
541
+ requireReceiptIdentity(receipt.change, receipt.schema);
542
+ const artifacts = normalizeArtifactBindings(receipt.artifacts);
543
+ const gateB = normalizeGateProof(receipt.approvals.gateB);
544
+ const traceability = normalizeTraceability(receipt.traceability);
545
+ if (
546
+ gateB.contractHash !== receipt.plan.canonicalHash ||
547
+ canonicalJson(gateB) !== canonicalJson(receipt.approvals.gateB) ||
548
+ canonicalJson(artifacts) !== canonicalJson(receipt.artifacts) ||
549
+ canonicalJson(traceability) !== canonicalJson(receipt.traceability)
550
+ ) {
551
+ throw new Error("delivery-receipt-invalid");
552
+ }
553
+ return structuredClone(receipt);
554
+ }
555
+
556
+ function sortStrings(values: readonly string[]): string[] {
557
+ return [...values].sort((left, right) => left.localeCompare(right));
558
+ }
559
+
560
+ function sortCanonical<T>(values: readonly T[]): T[] {
561
+ return [...values].sort((left, right) =>
562
+ canonicalJson(left).localeCompare(canonicalJson(right)),
563
+ );
564
+ }
565
+
566
+ function normalizeVerification(
567
+ verification: unknown,
568
+ ): StructuredVerificationContract {
569
+ const validation = validateVerificationContract(verification);
570
+ if (!validation.ok) throw new Error("delivery-verification-invalid");
571
+ const cloned = validation.value;
572
+ if (cloned.kind === "vitest") {
573
+ cloned.testFiles = sortStrings(cloned.testFiles);
574
+ } else if (cloned.kind === "steps") {
575
+ cloned.steps = cloned.steps.map((step) => {
576
+ if (step.kind !== "vitest") return step;
577
+ return { ...step, testFiles: sortStrings(step.testFiles) };
578
+ });
579
+ }
580
+ return cloned;
581
+ }
582
+
583
+ function normalizePhase(value: unknown): PlanPhaseDraft {
584
+ if (
585
+ !isRecord(value) ||
586
+ !exactKeys(value, [
587
+ "read",
588
+ "write",
589
+ "delete",
590
+ "verification",
591
+ "verificationInputs",
592
+ ...(Object.hasOwn(value, "verificationLock") ? ["verificationLock"] : []),
593
+ ]) ||
594
+ !Array.isArray(value.read) ||
595
+ !Array.isArray(value.write) ||
596
+ !Array.isArray(value.delete) ||
597
+ !Array.isArray(value.verificationInputs) ||
598
+ !value.read.every(isValidRelativePath) ||
599
+ !value.write.every(isValidRelativePath) ||
600
+ !value.delete.every(isValidRelativePath)
601
+ ) {
602
+ throw new Error("delivery-plan-invalid-phase");
603
+ }
604
+ const write = sortStrings(value.write as string[]);
605
+ const deletions = sortStrings(value.delete as string[]);
606
+ if (write.some((candidate) => deletions.includes(candidate))) {
607
+ throw new Error("delivery-plan-write-delete-overlap");
608
+ }
609
+ return {
610
+ read: sortStrings(value.read as string[]),
611
+ write,
612
+ delete: deletions,
613
+ verification: normalizeVerification(
614
+ value.verification as StructuredVerificationContract,
615
+ ),
616
+ verificationInputs: sortCanonical(
617
+ structuredClone(
618
+ value.verificationInputs,
619
+ ) as PhaseBoundary["verificationInputs"],
620
+ ),
621
+ ...(typeof value.verificationLock === "string"
622
+ ? { verificationLock: value.verificationLock }
623
+ : {}),
624
+ };
625
+ }
626
+
627
+ function normalizeTask(value: unknown): PlanTaskDraft {
628
+ if (
629
+ !isRecord(value) ||
630
+ !isRecord(value.phases) ||
631
+ !isRecord(value.affectedVerification) ||
632
+ !isRecord(value.repairVerification)
633
+ ) {
634
+ throw new Error("delivery-plan-invalid-task");
635
+ }
636
+ const phases = value.phases;
637
+ if (!Object.hasOwn(phases, "red") || !Object.hasOwn(phases, "green")) {
638
+ throw new Error("delivery-plan-invalid-task-phases");
639
+ }
640
+ const cloned = structuredClone(value) as unknown as PlanTaskDraft;
641
+ const affectedVerification = normalizeVerification(
642
+ value.affectedVerification,
643
+ );
644
+ const repairVerification = normalizeVerification(value.repairVerification);
645
+ if (
646
+ affectedVerification.classification !== "expected-green" ||
647
+ repairVerification.classification !== "expected-green"
648
+ ) {
649
+ throw new Error("delivery-task-verification-invalid");
650
+ }
651
+ return {
652
+ ...cloned,
653
+ dependsOn: sortStrings(cloned.dependsOn),
654
+ roots: sortStrings(cloned.roots),
655
+ phases: {
656
+ red: normalizePhase(phases.red),
657
+ green: normalizePhase(phases.green),
658
+ ...(phases.refactor !== undefined
659
+ ? { refactor: normalizePhase(phases.refactor) }
660
+ : {}),
661
+ },
662
+ scheduling: {
663
+ conflicts: sortStrings(cloned.scheduling.conflicts),
664
+ resources: sortStrings(cloned.scheduling.resources),
665
+ },
666
+ approvedDependencies: sortStrings(cloned.approvedDependencies),
667
+ affectedVerification,
668
+ repairVerification,
669
+ impactClosure: {
670
+ changedSurfaces: sortStrings(
671
+ cloned.impactClosure.changedSurfaces,
672
+ ) as PlanTaskDraft["impactClosure"]["changedSurfaces"],
673
+ searchEvidence: sortStrings(cloned.impactClosure.searchEvidence),
674
+ relatedTests: [...cloned.impactClosure.relatedTests].sort(
675
+ (left, right) =>
676
+ left.path.localeCompare(right.path) ||
677
+ left.disposition.localeCompare(right.disposition),
678
+ ),
679
+ affectedSuite: sortStrings(cloned.impactClosure.affectedSuite),
680
+ },
681
+ };
682
+ }
683
+
684
+ function graphFromPlan(plan: ImplementPlan): ImplementGraphBoundary {
685
+ const stripPhase = (phase: PlanPhaseDraft): PhaseBoundary => ({
686
+ read: phase.read,
687
+ write: phase.write,
688
+ delete: phase.delete,
689
+ verification: phase.verification,
690
+ verificationInputs: phase.verificationInputs,
691
+ ...(phase.verificationLock
692
+ ? { verificationLock: phase.verificationLock }
693
+ : {}),
694
+ });
695
+ return {
696
+ changeId: plan.changeId,
697
+ tasks: plan.tasks.map((task) => {
698
+ const {
699
+ affectedVerification: _affectedVerification,
700
+ repairVerification: _repairVerification,
701
+ ...boundary
702
+ } = task;
703
+ return {
704
+ ...boundary,
705
+ phases: {
706
+ red: stripPhase(task.phases.red),
707
+ green: stripPhase(task.phases.green),
708
+ ...(task.phases.refactor
709
+ ? { refactor: stripPhase(task.phases.refactor) }
710
+ : {}),
711
+ },
712
+ };
713
+ }),
714
+ outputs: structuredClone(plan.outputs),
715
+ };
716
+ }
717
+
718
+ function deliveryVerificationDiagnostics(
719
+ plan: ImplementPlan,
720
+ consumerRoot: string,
721
+ ): Array<Record<string, unknown>> {
722
+ const contracts: Array<{
723
+ owner: string;
724
+ verification: StructuredVerificationContract;
725
+ }> = plan.tasks.flatMap((task) => [
726
+ {
727
+ owner: `task:${task.taskId}:affected`,
728
+ verification: task.affectedVerification,
729
+ },
730
+ {
731
+ owner: `task:${task.taskId}:repair`,
732
+ verification: task.repairVerification,
733
+ },
734
+ ]);
735
+ contracts.push(
736
+ {
737
+ owner: "baseline:full-suite",
738
+ verification: plan.verification.baseline.fullSuite,
739
+ },
740
+ {
741
+ owner: "change:full-suite",
742
+ verification: plan.verification.change.fullSuite,
743
+ },
744
+ {
745
+ owner: "change:post-apply",
746
+ verification: plan.verification.change.postApply,
747
+ },
748
+ );
749
+ if (plan.verification.agentsCheckpoint.verification) {
750
+ contracts.push({
751
+ owner: "agents-checkpoint",
752
+ verification: plan.verification.agentsCheckpoint.verification,
753
+ });
754
+ }
755
+ return contracts.flatMap(({ owner, verification }) => {
756
+ const capability = validateVerificationAdapterCapability(
757
+ consumerRoot,
758
+ verification,
759
+ { dependencyOwner: consumerRoot },
760
+ );
761
+ if (capability.ok) return [];
762
+ const { message: _message, ...diagnostic } = capability.diagnostic;
763
+ return [{ owner, ...diagnostic }];
764
+ });
765
+ }
766
+
767
+ function normalizeExpectedGreenVerification(
768
+ value: unknown,
769
+ ): StructuredVerificationContract {
770
+ const verification = normalizeVerification(value);
771
+ if (verification.classification !== "expected-green") {
772
+ throw new Error("delivery-verification-classification-invalid");
773
+ }
774
+ return verification;
775
+ }
776
+
777
+ function normalizeAgentsManagedBlock(value: unknown): string {
778
+ if (
779
+ typeof value !== "string" ||
780
+ value.length === 0 ||
781
+ Buffer.byteLength(value) > 64 * 1024 ||
782
+ value.includes("\0") ||
783
+ value.includes("\r")
784
+ ) {
785
+ throw new Error("delivery-agents-checkpoint-invalid");
786
+ }
787
+ const lines = value.endsWith("\n")
788
+ ? value.slice(0, -1).split("\n")
789
+ : value.split("\n");
790
+ if (
791
+ lines[0] !== AGENTS_START ||
792
+ lines.at(-1) !== AGENTS_END ||
793
+ lines.filter((line) => line === AGENTS_START).length !== 1 ||
794
+ lines.filter((line) => line === AGENTS_END).length !== 1
795
+ ) {
796
+ throw new Error("delivery-agents-checkpoint-invalid");
797
+ }
798
+ return lines.join("\n");
799
+ }
800
+
801
+ function normalizeAgentsCheckpointOperations(
802
+ value: unknown,
803
+ ): PlanAgentsCheckpointOperation[] {
804
+ if (!Array.isArray(value) || value.length > 64) {
805
+ throw new Error("delivery-agents-checkpoint-invalid");
806
+ }
807
+ const targets = new Set<string>();
808
+ return value
809
+ .map((operation): PlanAgentsCheckpointOperation => {
810
+ if (
811
+ !isRecord(operation) ||
812
+ !exactKeys(operation, [
813
+ "target",
814
+ "impact",
815
+ "taskIds",
816
+ "managedBlock",
817
+ ]) ||
818
+ !isAgentsPath(operation.target) ||
819
+ !["update-existing", "create-index", "remove-index"].includes(
820
+ String(operation.impact),
821
+ ) ||
822
+ targets.has(operation.target) ||
823
+ !Array.isArray(operation.taskIds) ||
824
+ operation.taskIds.length === 0 ||
825
+ operation.taskIds.some(
826
+ (taskId) => typeof taskId !== "string" || taskId.length === 0,
827
+ ) ||
828
+ new Set(operation.taskIds).size !== operation.taskIds.length
829
+ ) {
830
+ throw new Error("delivery-agents-checkpoint-invalid");
831
+ }
832
+ targets.add(operation.target);
833
+ const impact = operation.impact as Exclude<AgentsImpact, "none">;
834
+ if (
835
+ (impact === "remove-index" && operation.managedBlock !== null) ||
836
+ (impact !== "remove-index" &&
837
+ typeof operation.managedBlock !== "string")
838
+ ) {
839
+ throw new Error("delivery-agents-checkpoint-invalid");
840
+ }
841
+ return {
842
+ target: operation.target,
843
+ impact,
844
+ taskIds: sortStrings(operation.taskIds as string[]),
845
+ managedBlock:
846
+ impact === "remove-index"
847
+ ? null
848
+ : normalizeAgentsManagedBlock(operation.managedBlock),
849
+ };
850
+ })
851
+ .sort((left, right) => left.target.localeCompare(right.target));
852
+ }
853
+
854
+ function normalizeVerificationPlan(value: unknown): PlanVerification {
855
+ if (
856
+ !isRecord(value) ||
857
+ !exactKeys(value, [
858
+ "baseline",
859
+ "change",
860
+ "artifactCorrection",
861
+ "repair",
862
+ "agentsCheckpoint",
863
+ ]) ||
864
+ !isRecord(value.baseline) ||
865
+ !exactKeys(value.baseline, [
866
+ "target",
867
+ "affected",
868
+ "fullSuite",
869
+ "failureIdentity",
870
+ ]) ||
871
+ value.baseline.target !== "task-red-contracts" ||
872
+ value.baseline.affected !== "task-affected-contracts" ||
873
+ value.baseline.failureIdentity !== "normalized-v1" ||
874
+ !isRecord(value.change) ||
875
+ !exactKeys(value.change, ["affected", "fullSuite", "postApply"]) ||
876
+ value.change.affected !== "task-affected-contracts" ||
877
+ !isRecord(value.artifactCorrection) ||
878
+ !exactKeys(value.artifactCorrection, ["maxAttempts"]) ||
879
+ !Number.isSafeInteger(value.artifactCorrection.maxAttempts) ||
880
+ (value.artifactCorrection.maxAttempts as number) < 2 ||
881
+ (value.artifactCorrection.maxAttempts as number) > 3 ||
882
+ !isRecord(value.repair) ||
883
+ !exactKeys(value.repair, [
884
+ "maxAttempts",
885
+ "inBoundaryOnly",
886
+ "approvalOnBoundaryExpansion",
887
+ "attribution",
888
+ ]) ||
889
+ typeof value.repair.maxAttempts !== "number" ||
890
+ !Number.isSafeInteger(value.repair.maxAttempts) ||
891
+ value.repair.maxAttempts < 1 ||
892
+ value.repair.maxAttempts > 3 ||
893
+ value.repair.inBoundaryOnly !== true ||
894
+ value.repair.approvalOnBoundaryExpansion !== true ||
895
+ canonicalJson(value.repair.attribution) !==
896
+ canonicalJson([
897
+ "pre-existing",
898
+ "introduced",
899
+ "unresolved",
900
+ "environment",
901
+ ]) ||
902
+ !isRecord(value.agentsCheckpoint) ||
903
+ !exactKeys(value.agentsCheckpoint, [
904
+ "required",
905
+ "verification",
906
+ "operations",
907
+ ]) ||
908
+ typeof value.agentsCheckpoint.required !== "boolean" ||
909
+ (value.agentsCheckpoint.required &&
910
+ !isRecord(value.agentsCheckpoint.verification)) ||
911
+ (!value.agentsCheckpoint.required &&
912
+ value.agentsCheckpoint.verification !== null) ||
913
+ !Array.isArray(value.agentsCheckpoint.operations)
914
+ ) {
915
+ throw new Error("delivery-verification-plan-invalid");
916
+ }
917
+ return {
918
+ baseline: {
919
+ target: "task-red-contracts",
920
+ affected: "task-affected-contracts",
921
+ fullSuite: normalizeExpectedGreenVerification(value.baseline.fullSuite),
922
+ failureIdentity: "normalized-v1",
923
+ },
924
+ change: {
925
+ affected: "task-affected-contracts",
926
+ fullSuite: normalizeExpectedGreenVerification(value.change.fullSuite),
927
+ postApply: normalizeExpectedGreenVerification(value.change.postApply),
928
+ },
929
+ artifactCorrection: {
930
+ maxAttempts: value.artifactCorrection.maxAttempts as number,
931
+ },
932
+ repair: {
933
+ maxAttempts: value.repair.maxAttempts as number,
934
+ inBoundaryOnly: true,
935
+ approvalOnBoundaryExpansion: true,
936
+ attribution: ["pre-existing", "introduced", "unresolved", "environment"],
937
+ },
938
+ agentsCheckpoint: {
939
+ required: value.agentsCheckpoint.required,
940
+ verification: value.agentsCheckpoint.required
941
+ ? normalizeExpectedGreenVerification(
942
+ value.agentsCheckpoint.verification,
943
+ )
944
+ : null,
945
+ operations: normalizeAgentsCheckpointOperations(
946
+ value.agentsCheckpoint.operations,
947
+ ),
948
+ },
949
+ };
950
+ }
951
+
952
+ function normalizeTracking(value: unknown): PlanTracking {
953
+ if (
954
+ !isRecord(value) ||
955
+ !exactKeys(value, ["path", "format", "taskIds", "completionOwner"]) ||
956
+ value.path !== "tasks.md" ||
957
+ value.format !== "markdown-checkbox" ||
958
+ !Array.isArray(value.taskIds) ||
959
+ !value.taskIds.every((taskId) => typeof taskId === "string") ||
960
+ new Set(value.taskIds).size !== value.taskIds.length ||
961
+ value.completionOwner !== "parent"
962
+ ) {
963
+ throw new Error("delivery-tracking-invalid");
964
+ }
965
+ return {
966
+ path: "tasks.md",
967
+ format: "markdown-checkbox",
968
+ taskIds: sortStrings(value.taskIds as string[]),
969
+ completionOwner: "parent",
970
+ };
971
+ }
972
+
973
+ function normalizeDraft(value: unknown): ImplementPlan {
974
+ if (
975
+ !isRecord(value) ||
976
+ !exactKeys(value, [
977
+ "schemaVersion",
978
+ "changeId",
979
+ "tasks",
980
+ "outputs",
981
+ "verification",
982
+ "tracking",
983
+ ]) ||
984
+ value.schemaVersion !== IMPLEMENT_PLAN_SCHEMA_VERSION ||
985
+ typeof value.changeId !== "string" ||
986
+ !CHANGE_NAME.test(value.changeId) ||
987
+ !Array.isArray(value.tasks) ||
988
+ !Array.isArray(value.outputs)
989
+ ) {
990
+ throw new Error("delivery-plan-invalid");
991
+ }
992
+ const plan: ImplementPlan = {
993
+ schemaVersion: IMPLEMENT_PLAN_SCHEMA_VERSION,
994
+ changeId: value.changeId,
995
+ tasks: value.tasks
996
+ .map(normalizeTask)
997
+ .sort((left, right) => left.taskId.localeCompare(right.taskId)),
998
+ outputs: (structuredClone(value.outputs) as ImplementGraphOutput[]).sort(
999
+ (left, right) => left.id.localeCompare(right.id),
1000
+ ),
1001
+ verification: normalizeVerificationPlan(value.verification),
1002
+ tracking: normalizeTracking(value.tracking),
1003
+ };
1004
+ if (
1005
+ canonicalJson(plan.tracking.taskIds) !==
1006
+ canonicalJson(plan.tasks.map((task) => task.taskId).sort())
1007
+ ) {
1008
+ throw new Error("delivery-tracking-task-mismatch");
1009
+ }
1010
+ const agentsTasks = plan.tasks.filter(
1011
+ (task) => task.agents.impact !== "none" && task.agents.target,
1012
+ );
1013
+ const agentsByTarget = new Map<string, PlanTaskDraft[]>();
1014
+ for (const task of agentsTasks) {
1015
+ const target = task.agents.target as string;
1016
+ const existing = agentsByTarget.get(target) ?? [];
1017
+ existing.push(task);
1018
+ agentsByTarget.set(target, existing);
1019
+ }
1020
+ const operations = plan.verification.agentsCheckpoint.operations;
1021
+ const operationsByTarget = new Map(
1022
+ operations.map((operation) => [operation.target, operation]),
1023
+ );
1024
+ const agentsRequired = agentsTasks.length > 0;
1025
+ if (
1026
+ plan.verification.agentsCheckpoint.required !== agentsRequired ||
1027
+ operations.length !== agentsByTarget.size ||
1028
+ [...agentsByTarget].some(([target, tasks]) => {
1029
+ const operation = operationsByTarget.get(target);
1030
+ return (
1031
+ !operation ||
1032
+ tasks.some((task) => task.agents.impact !== operation.impact) ||
1033
+ canonicalJson(operation.taskIds) !==
1034
+ canonicalJson(tasks.map((task) => task.taskId).sort())
1035
+ );
1036
+ })
1037
+ ) {
1038
+ throw new Error("delivery-agents-checkpoint-mismatch");
1039
+ }
1040
+ const graphValidation = validateImplementGraphBoundary(graphFromPlan(plan));
1041
+ if (!graphValidation.ok) {
1042
+ throw new Error(`delivery-plan-invalid:${graphValidation.reason}`);
1043
+ }
1044
+ return plan;
1045
+ }
1046
+
1047
+ function renderTasks(plan: ImplementPlan): string {
1048
+ const lines = ["# Implementation tasks", ""];
1049
+ for (const task of plan.tasks) {
1050
+ lines.push(`- [ ] ${task.taskId} — ${task.objective}`);
1051
+ lines.push(
1052
+ ` - Depends on: ${task.dependsOn.length > 0 ? task.dependsOn.join(", ") : "[]"}`,
1053
+ );
1054
+ lines.push(` - Red verification: ${task.phases.red.verification.id}`);
1055
+ lines.push(` - Green verification: ${task.phases.green.verification.id}`);
1056
+ }
1057
+ lines.push("");
1058
+ return lines.join("\n");
1059
+ }
1060
+
1061
+ export function compileImplementPlan(
1062
+ draft: unknown,
1063
+ options: { consumerRoot: string },
1064
+ ): CompiledDelivery {
1065
+ const plan = normalizeDraft(draft);
1066
+ const graph = graphFromPlan(plan);
1067
+ const readiness = assessImplementGraphReadiness(options.consumerRoot, graph, {
1068
+ completedTasks: [],
1069
+ blockedTasks: [],
1070
+ appliedPhases: [],
1071
+ });
1072
+ const diagnostics = [
1073
+ ...readiness.closure.diagnostics,
1074
+ ...deliveryVerificationDiagnostics(plan, options.consumerRoot),
1075
+ ];
1076
+ if (diagnostics.length > 0) {
1077
+ throw new Error(
1078
+ `delivery-plan-not-executable:${canonicalJson(diagnostics)}`,
1079
+ );
1080
+ }
1081
+ const canonical = canonicalJson(plan);
1082
+ const bytes = new TextEncoder().encode(`${canonical}\n`);
1083
+ const rawSha256 = createHash("sha256").update(bytes).digest("hex");
1084
+ const planHash = hashCanonicalValue(plan);
1085
+ const closure = structuredClone(readiness.closure);
1086
+ return {
1087
+ plan,
1088
+ bytes,
1089
+ rawSha256,
1090
+ planHash,
1091
+ closure,
1092
+ receipt: {
1093
+ plan: {
1094
+ path: IMPLEMENT_PLAN_PATH,
1095
+ schemaVersion: IMPLEMENT_PLAN_SCHEMA_VERSION,
1096
+ rawSha256,
1097
+ canonicalHash: planHash,
1098
+ },
1099
+ verificationClosure: structuredClone(closure),
1100
+ },
1101
+ tasksMarkdown: renderTasks(plan),
1102
+ };
1103
+ }
1104
+
1105
+ export function parseImplementPlan(bytes: Uint8Array): ImplementPlan {
1106
+ const text = new TextDecoder().decode(bytes);
1107
+ let parsed: unknown;
1108
+ try {
1109
+ parsed = JSON.parse(text);
1110
+ } catch {
1111
+ throw new Error("delivery-plan-json-invalid");
1112
+ }
1113
+ const normalized = normalizeDraft(parsed);
1114
+ if (`${canonicalJson(normalized)}\n` !== text) {
1115
+ throw new Error("delivery-plan-not-canonical");
1116
+ }
1117
+ return normalized;
1118
+ }