@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,1805 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { readFileSync } from "node:fs";
3
- import path from "node:path";
4
- import { compareCanonicalStrings } from "./canonical.ts";
5
- import {
6
- assertPlanWithinChangeContract,
7
- normalizeChangeContract,
8
- } from "./change-contract.ts";
9
- import {
10
- type AgentsImpact,
11
- type ImplementGraphBoundary,
12
- type ImplementGraphOutput,
13
- isAgentsPath,
14
- isValidRelativePath,
15
- type PhaseBoundary,
16
- type StructuredVerificationContract,
17
- validateImplementGraphBoundary,
18
- validateVerificationContract,
19
- verificationInputPaths,
20
- verificationSteps,
21
- } from "./contracts.ts";
22
- import {
23
- type DesignPlanDiagnostic,
24
- DesignPlanValidationError,
25
- } from "./design-diagnostics.ts";
26
- import {
27
- assessImplementGraphReadiness,
28
- canonicalJson,
29
- hashCanonicalValue,
30
- type ImplementGraphReadiness,
31
- } from "./implement-graph.ts";
32
- import type {
33
- ImplementPlan,
34
- PlanAgentsCheckpointOperation,
35
- PlanTaskDraft,
36
- PlanTracking,
37
- PlanVerification,
38
- } from "./implement-plan.ts";
39
- import { expandPlanDraft, preparePlanDraft } from "./plan-draft.ts";
40
- import { isSafeRegularFile, observeSafePath } from "./safe-path.ts";
41
- import { expandSingleTaskDraft } from "./single-task-draft.ts";
42
- import {
43
- bindDraftVerificationInputs,
44
- validateVerificationAdapterCapability,
45
- } from "./verification-capability.ts";
46
-
47
- export const IMPLEMENT_PLAN_PATH = "implement-plan.json" as const;
48
- export const GATE_A_RECEIPT_PATH = "gate-a.yaml" as const;
49
- export const READY_RECEIPT_PATH = "ready.yaml" as const;
50
-
51
- const SHA256 = /^[a-f0-9]{64}$/u;
52
- const CHANGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,126}[a-z0-9])?$/u;
53
- const AGENTS_START = "<!-- ABEL:AGENTS-INDEX:START -->";
54
- const AGENTS_END = "<!-- ABEL:AGENTS-INDEX:END -->";
55
-
56
- export interface DeliveryArtifactBinding {
57
- path: string;
58
- rawSha256: string;
59
- }
60
-
61
- export interface GateApprovalProof {
62
- revision: number;
63
- contractHash: string;
64
- recordHash: string;
65
- }
66
-
67
- export interface GateAReceipt {
68
- change: string;
69
- schema: string;
70
- approval: GateApprovalProof;
71
- artifacts: DeliveryArtifactBinding[];
72
- }
73
-
74
- export interface DeliveryTraceability {
75
- taskPath: "tasks.md";
76
- referenceCount: number;
77
- taskCount: number;
78
- verificationCount: number;
79
- canonicalHash: string;
80
- }
81
-
82
- export interface ReadyReceipt {
83
- change: string;
84
- schema: string;
85
- deliveryRevision: number;
86
- approvals: {
87
- gateA: {
88
- path: typeof GATE_A_RECEIPT_PATH;
89
- rawSha256: string;
90
- };
91
- gateB: {
92
- revision: number;
93
- contractHash: string;
94
- recordHash: string;
95
- };
96
- };
97
- artifacts: DeliveryArtifactBinding[];
98
- plan: CompiledDelivery["receipt"]["plan"];
99
- verificationClosure: ImplementGraphReadiness["closure"];
100
- traceability: DeliveryTraceability;
101
- openspec: {
102
- strict: true;
103
- planningComplete: true;
104
- };
105
- }
106
-
107
- export type DeliveryTraceabilityAssessment =
108
- | { ok: true; value: DeliveryTraceability }
109
- | { ok: false; diagnostics: string[] };
110
-
111
- export class DeliveryValidationError extends Error {
112
- readonly diagnostics: readonly string[];
113
-
114
- constructor(diagnostics: readonly string[]) {
115
- const normalized = [...new Set(diagnostics)].sort();
116
- super("delivery-invalid");
117
- this.name = "DeliveryValidationError";
118
- this.diagnostics = Object.freeze(normalized);
119
- }
120
- }
121
-
122
- const SAFE_PLAN_ERROR_CODE = /^[a-z0-9](?:[a-z0-9._-]{0,126}[a-z0-9])?$/u;
123
-
124
- function planErrorCode(error: unknown): string {
125
- const message = error instanceof Error ? error.message : "";
126
- return SAFE_PLAN_ERROR_CODE.test(message) ? message : "delivery-plan-invalid";
127
- }
128
-
129
- function planDiagnosticField(code: string): string | undefined {
130
- const fields: Record<string, string> = {
131
- "delivery-plan-invalid-task": "task",
132
- "delivery-plan-invalid-task-phases": "phases",
133
- "delivery-plan-invalid-phase": "phases",
134
- "delivery-plan-empty-phase-boundary": "phases",
135
- "delivery-plan-write-delete-overlap": "phases",
136
- "delivery-verification-invalid": "verification",
137
- "delivery-task-verification-invalid": "affectedVerification",
138
- "delivery-agents-checkpoint-mismatch": "verification.agentsCheckpoint",
139
- };
140
- return fields[code];
141
- }
142
-
143
- function asPlanValidationError(
144
- error: unknown,
145
- context: Omit<DesignPlanDiagnostic, "code"> = {},
146
- ): DesignPlanValidationError {
147
- if (error instanceof DesignPlanValidationError) {
148
- const diagnostics = error.diagnostics.map((diagnostic) => ({
149
- ...context,
150
- ...diagnostic,
151
- }));
152
- return new DesignPlanValidationError(error.message, diagnostics);
153
- }
154
- const code = planErrorCode(error);
155
- const field = planDiagnosticField(code);
156
- return new DesignPlanValidationError(code, [
157
- { code, ...context, ...(field ? { field } : {}) },
158
- ]);
159
- }
160
-
161
- export function assessDeliveryTraceability(input: {
162
- tasksMarkdown: string;
163
- specs: readonly { path: string; text: string }[];
164
- plan: ImplementPlan;
165
- }): DeliveryTraceabilityAssessment {
166
- const diagnostics = new Set<string>();
167
- let authorMarkdown = input.tasksMarkdown;
168
- let bindings: string | undefined;
169
- try {
170
- const region = taskVerificationRegion(input.tasksMarkdown);
171
- if (region) {
172
- authorMarkdown =
173
- input.tasksMarkdown.slice(0, region.start) +
174
- input.tasksMarkdown.slice(region.end);
175
- bindings = region.text;
176
- if (bindings !== renderTasks(input.plan)) {
177
- diagnostics.add("traceability-managed-region-stale");
178
- }
179
- }
180
- } catch {
181
- return { ok: false, diagnostics: ["traceability-managed-region-invalid"] };
182
- }
183
- const headings = new Set<string>();
184
- for (const spec of input.specs) {
185
- if (!isValidRelativePath(spec.path) || !spec.path.endsWith("/spec.md")) {
186
- diagnostics.add("traceability-spec-path-invalid");
187
- continue;
188
- }
189
- let requirement = "";
190
- for (const line of spec.text.split(/\r?\n/u)) {
191
- const requirementMatch = /^### Requirement: (.+)$/u.exec(line);
192
- if (requirementMatch?.[1]) {
193
- requirement = requirementMatch[1].trim();
194
- continue;
195
- }
196
- const scenarioMatch = /^#### Scenario: (.+)$/u.exec(line);
197
- if (scenarioMatch?.[1] && requirement) {
198
- const heading = `${spec.path}#${requirement}/${scenarioMatch[1].trim()}`;
199
- if (headings.has(heading)) {
200
- diagnostics.add("traceability-heading-duplicate");
201
- }
202
- headings.add(heading);
203
- }
204
- }
205
- }
206
- const references = [
207
- ...authorMarkdown.matchAll(/`(specs\/[^`\n]+\/spec\.md#[^`\n]+)`/gu),
208
- ].flatMap((match) => (match[1] ? [match[1].trim()] : []));
209
- if (references.length === 0) diagnostics.add("traceability-reference-empty");
210
- const referenceSet = new Set(references);
211
- if (referenceSet.size !== references.length) {
212
- diagnostics.add("traceability-reference-duplicate");
213
- }
214
- if (references.some((reference) => !headings.has(reference))) {
215
- diagnostics.add("traceability-reference-unresolved");
216
- }
217
- if ([...headings].some((heading) => !referenceSet.has(heading))) {
218
- diagnostics.add("traceability-scenario-unowned");
219
- }
220
- const taskIds = input.plan.tasks.map((task) => task.taskId).sort();
221
- if (taskIds.some((taskId) => !authorMarkdown.includes(`\`${taskId}\``))) {
222
- diagnostics.add("traceability-task-unmapped");
223
- }
224
- const verificationIds = input.plan.tasks
225
- .flatMap((task) => [
226
- ...(task.verificationMode && task.verificationMode !== "behavior"
227
- ? []
228
- : [task.phases.red.verification.id]),
229
- task.phases.green.verification.id,
230
- ...(task.phases.refactor ? [task.phases.refactor.verification.id] : []),
231
- ])
232
- .sort();
233
- if (
234
- verificationIds.some(
235
- (verificationId) =>
236
- !(bindings ?? authorMarkdown).includes(verificationId),
237
- )
238
- ) {
239
- diagnostics.add("traceability-verification-unmapped");
240
- }
241
- if (diagnostics.size > 0) {
242
- return { ok: false, diagnostics: [...diagnostics].sort() };
243
- }
244
- return {
245
- ok: true,
246
- value: {
247
- taskPath: "tasks.md",
248
- referenceCount: references.length,
249
- taskCount: taskIds.length,
250
- verificationCount: verificationIds.length,
251
- canonicalHash: hashCanonicalValue({
252
- references: [...referenceSet].sort(),
253
- taskIds,
254
- verificationIds,
255
- }),
256
- },
257
- };
258
- }
259
-
260
- export type { DesignPlanDiagnostic } from "./design-diagnostics.ts";
261
- export { DesignPlanValidationError } from "./design-diagnostics.ts";
262
- export type {
263
- ImplementPlan,
264
- PlanAgentsCheckpointOperation,
265
- PlanTaskDraft,
266
- PlanTracking,
267
- PlanVerification,
268
- } from "./implement-plan.ts";
269
- export type { PlanDraft, PlanPhaseDraft, PlanTaskInput } from "./plan-draft.ts";
270
-
271
- export interface CompiledDelivery {
272
- /** Static checks only: neither compilation nor preflight seals a delivery. */
273
- checks: {
274
- structure: "passed";
275
- verificationCapability: "passed";
276
- contractCoverage: "passed" | "not-checked";
277
- sealing: "not-performed";
278
- };
279
- plan: ImplementPlan;
280
- bytes: Uint8Array;
281
- rawSha256: string;
282
- planHash: string;
283
- closure: ImplementGraphReadiness["closure"];
284
- receipt: {
285
- plan: {
286
- path: typeof IMPLEMENT_PLAN_PATH;
287
- rawSha256: string;
288
- canonicalHash: string;
289
- };
290
- verificationClosure: ImplementGraphReadiness["closure"];
291
- };
292
- tasksMarkdown: string;
293
- }
294
-
295
- function isRecord(value: unknown): value is Record<string, unknown> {
296
- return value !== null && typeof value === "object" && !Array.isArray(value);
297
- }
298
-
299
- function exactKeys(
300
- value: Record<string, unknown>,
301
- keys: readonly string[],
302
- ): boolean {
303
- const actual = Object.keys(value);
304
- return (
305
- actual.length === keys.length && actual.every((key) => keys.includes(key))
306
- );
307
- }
308
-
309
- function canonicalBytes(value: unknown): Uint8Array {
310
- return new TextEncoder().encode(`${canonicalJson(value)}\n`);
311
- }
312
-
313
- function normalizeArtifactBindings(
314
- value: readonly DeliveryArtifactBinding[],
315
- ): DeliveryArtifactBinding[] {
316
- if (!Array.isArray(value) || value.length === 0) {
317
- throw new Error("delivery-artifacts-invalid");
318
- }
319
- const paths = new Set<string>();
320
- const normalized = value.map((binding) => {
321
- if (
322
- !isRecord(binding) ||
323
- !exactKeys(binding, ["path", "rawSha256"]) ||
324
- typeof binding.path !== "string" ||
325
- !isValidRelativePath(binding.path) ||
326
- binding.path === READY_RECEIPT_PATH ||
327
- typeof binding.rawSha256 !== "string" ||
328
- !SHA256.test(binding.rawSha256) ||
329
- paths.has(binding.path)
330
- ) {
331
- throw new Error("delivery-artifacts-invalid");
332
- }
333
- paths.add(binding.path);
334
- return { path: binding.path, rawSha256: binding.rawSha256 };
335
- });
336
- return normalized.sort((left, right) =>
337
- compareCanonicalStrings(left.path, right.path),
338
- );
339
- }
340
-
341
- function requireReceiptIdentity(
342
- change: unknown,
343
- schema: unknown,
344
- ): asserts change is string {
345
- if (
346
- typeof change !== "string" ||
347
- !CHANGE_NAME.test(change) ||
348
- typeof schema !== "string" ||
349
- schema.length === 0 ||
350
- schema.length > 128
351
- ) {
352
- throw new Error("delivery-receipt-identity-invalid");
353
- }
354
- }
355
-
356
- function normalizeGateProof(value: GateApprovalProof): GateApprovalProof {
357
- if (
358
- !isRecord(value) ||
359
- !exactKeys(value, ["revision", "contractHash", "recordHash"]) ||
360
- !Number.isSafeInteger(value.revision) ||
361
- value.revision < 1 ||
362
- typeof value.contractHash !== "string" ||
363
- !SHA256.test(value.contractHash) ||
364
- typeof value.recordHash !== "string" ||
365
- !SHA256.test(value.recordHash)
366
- ) {
367
- throw new Error("delivery-gate-proof-invalid");
368
- }
369
- return structuredClone(value);
370
- }
371
-
372
- export interface DeliveryReadOptions {
373
- /** Only the proof-bound delivery loader may admit historical collection ordering. */
374
- allowLegacyOrder?: boolean;
375
- }
376
-
377
- const legacyPlans = new WeakMap<object, string>();
378
-
379
- function unorderedIdentity(value: unknown): string {
380
- if (Array.isArray(value))
381
- return `[${value.map(unorderedIdentity).sort().join(",")}]`;
382
- if (isRecord(value))
383
- return `{${Object.entries(value)
384
- .sort(([a], [b]) => compareCanonicalStrings(a, b))
385
- .map(
386
- ([key, entry]) => `${JSON.stringify(key)}:${unorderedIdentity(entry)}`,
387
- )
388
- .join(",")}}`;
389
- return JSON.stringify(value) ?? "null";
390
- }
391
-
392
- function sameNormalized(
393
- left: unknown,
394
- right: unknown,
395
- options: DeliveryReadOptions,
396
- ): boolean {
397
- return options.allowLegacyOrder
398
- ? unorderedIdentity(left) === unorderedIdentity(right)
399
- : canonicalJson(left) === canonicalJson(right);
400
- }
401
-
402
- function parseCanonicalReceipt<T>(
403
- bytes: Uint8Array,
404
- code: string,
405
- options: DeliveryReadOptions = {},
406
- ): T {
407
- const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
408
- let parsed: unknown;
409
- try {
410
- parsed = JSON.parse(text);
411
- } catch {
412
- throw new Error(code);
413
- }
414
- if (
415
- `${canonicalJson(parsed)}\n` !== text &&
416
- (!options.allowLegacyOrder || `${JSON.stringify(parsed)}\n` !== text)
417
- )
418
- throw new Error(code);
419
- return parsed as T;
420
- }
421
-
422
- export function compileGateAReceipt(input: {
423
- change: string;
424
- schema: string;
425
- approval: GateApprovalProof;
426
- artifacts: readonly DeliveryArtifactBinding[];
427
- }): { receipt: GateAReceipt; bytes: Uint8Array; rawSha256: string } {
428
- requireReceiptIdentity(input.change, input.schema);
429
- const receipt: GateAReceipt = {
430
- change: input.change,
431
- schema: input.schema,
432
- approval: normalizeGateProof(input.approval),
433
- artifacts: normalizeArtifactBindings(input.artifacts),
434
- };
435
- const bytes = canonicalBytes(receipt);
436
- return {
437
- receipt,
438
- bytes,
439
- rawSha256: createHash("sha256").update(bytes).digest("hex"),
440
- };
441
- }
442
-
443
- export function parseGateAReceipt(
444
- bytes: Uint8Array,
445
- options: DeliveryReadOptions = {},
446
- ): GateAReceipt {
447
- const receipt = parseCanonicalReceipt<GateAReceipt>(
448
- bytes,
449
- "gate-a-receipt-invalid",
450
- options,
451
- );
452
- if (
453
- !isRecord(receipt) ||
454
- !exactKeys(receipt, ["change", "schema", "approval", "artifacts"]) ||
455
- !isRecord(receipt.approval) ||
456
- !exactKeys(receipt.approval, ["revision", "contractHash", "recordHash"])
457
- ) {
458
- throw new Error("gate-a-receipt-invalid");
459
- }
460
- requireReceiptIdentity(receipt.change, receipt.schema);
461
- const approval = normalizeGateProof(receipt.approval);
462
- const artifacts = normalizeArtifactBindings(receipt.artifacts);
463
- if (
464
- !sameNormalized(approval, receipt.approval, options) ||
465
- !sameNormalized(artifacts, receipt.artifacts, options)
466
- ) {
467
- throw new Error("gate-a-receipt-invalid");
468
- }
469
- return structuredClone(receipt);
470
- }
471
-
472
- function normalizeTraceability(
473
- value: DeliveryTraceability,
474
- ): DeliveryTraceability {
475
- if (
476
- !isRecord(value) ||
477
- !exactKeys(value, [
478
- "taskPath",
479
- "referenceCount",
480
- "taskCount",
481
- "verificationCount",
482
- "canonicalHash",
483
- ]) ||
484
- value.taskPath !== "tasks.md" ||
485
- !Number.isSafeInteger(value.referenceCount) ||
486
- value.referenceCount < 1 ||
487
- !Number.isSafeInteger(value.taskCount) ||
488
- value.taskCount < 1 ||
489
- !Number.isSafeInteger(value.verificationCount) ||
490
- value.verificationCount < 1 ||
491
- typeof value.canonicalHash !== "string" ||
492
- !SHA256.test(value.canonicalHash)
493
- ) {
494
- throw new Error("delivery-traceability-invalid");
495
- }
496
- return structuredClone(value);
497
- }
498
-
499
- export function compileReadyReceipt(input: {
500
- change: string;
501
- schema: string;
502
- deliveryRevision: number;
503
- gateA: { rawSha256: string };
504
- gateB: GateApprovalProof;
505
- artifacts: readonly DeliveryArtifactBinding[];
506
- compiledPlan: CompiledDelivery;
507
- traceability: DeliveryTraceability;
508
- }): { receipt: ReadyReceipt; bytes: Uint8Array; rawSha256: string } {
509
- requireReceiptIdentity(input.change, input.schema);
510
- if (
511
- !Number.isSafeInteger(input.deliveryRevision) ||
512
- input.deliveryRevision < 1 ||
513
- !SHA256.test(input.gateA.rawSha256) ||
514
- input.compiledPlan.plan.changeId !== input.change ||
515
- input.gateB.contractHash !== input.compiledPlan.planHash
516
- ) {
517
- throw new Error("delivery-receipt-invalid");
518
- }
519
- const receipt: ReadyReceipt = {
520
- change: input.change,
521
- schema: input.schema,
522
- deliveryRevision: input.deliveryRevision,
523
- approvals: {
524
- gateA: {
525
- path: GATE_A_RECEIPT_PATH,
526
- rawSha256: input.gateA.rawSha256,
527
- },
528
- gateB: normalizeGateProof(input.gateB),
529
- },
530
- artifacts: normalizeArtifactBindings(input.artifacts),
531
- plan: structuredClone(input.compiledPlan.receipt.plan),
532
- verificationClosure: structuredClone(
533
- input.compiledPlan.receipt.verificationClosure,
534
- ),
535
- traceability: normalizeTraceability(input.traceability),
536
- openspec: { strict: true, planningComplete: true },
537
- };
538
- const bytes = canonicalBytes(receipt);
539
- return {
540
- receipt,
541
- bytes,
542
- rawSha256: createHash("sha256").update(bytes).digest("hex"),
543
- };
544
- }
545
-
546
- export function parseReadyReceipt(
547
- bytes: Uint8Array,
548
- options: DeliveryReadOptions = {},
549
- ): ReadyReceipt {
550
- const receipt = parseCanonicalReceipt<ReadyReceipt>(
551
- bytes,
552
- "delivery-receipt-invalid",
553
- options,
554
- );
555
- if (
556
- !isRecord(receipt) ||
557
- !exactKeys(receipt, [
558
- "change",
559
- "schema",
560
- "deliveryRevision",
561
- "approvals",
562
- "artifacts",
563
- "plan",
564
- "verificationClosure",
565
- "traceability",
566
- "openspec",
567
- ]) ||
568
- !Number.isSafeInteger(receipt.deliveryRevision) ||
569
- receipt.deliveryRevision < 1 ||
570
- !isRecord(receipt.approvals) ||
571
- !exactKeys(receipt.approvals, ["gateA", "gateB"]) ||
572
- !isRecord(receipt.approvals.gateA) ||
573
- !exactKeys(receipt.approvals.gateA, ["path", "rawSha256"]) ||
574
- receipt.approvals.gateA.path !== GATE_A_RECEIPT_PATH ||
575
- !SHA256.test(receipt.approvals.gateA.rawSha256) ||
576
- !isRecord(receipt.approvals.gateB) ||
577
- !exactKeys(receipt.approvals.gateB, [
578
- "revision",
579
- "contractHash",
580
- "recordHash",
581
- ]) ||
582
- !isRecord(receipt.plan) ||
583
- !exactKeys(receipt.plan, ["path", "rawSha256", "canonicalHash"]) ||
584
- receipt.plan.path !== IMPLEMENT_PLAN_PATH ||
585
- !SHA256.test(receipt.plan.rawSha256) ||
586
- !SHA256.test(receipt.plan.canonicalHash) ||
587
- !isRecord(receipt.verificationClosure) ||
588
- !exactKeys(receipt.verificationClosure, ["executable", "diagnostics"]) ||
589
- receipt.verificationClosure.executable !== true ||
590
- !Array.isArray(receipt.verificationClosure.diagnostics) ||
591
- receipt.verificationClosure.diagnostics.length !== 0 ||
592
- !isRecord(receipt.openspec) ||
593
- !exactKeys(receipt.openspec, ["strict", "planningComplete"]) ||
594
- receipt.openspec.strict !== true ||
595
- receipt.openspec.planningComplete !== true
596
- ) {
597
- throw new Error("delivery-receipt-invalid");
598
- }
599
- requireReceiptIdentity(receipt.change, receipt.schema);
600
- const artifacts = normalizeArtifactBindings(receipt.artifacts);
601
- const gateB = normalizeGateProof(receipt.approvals.gateB);
602
- const traceability = normalizeTraceability(receipt.traceability);
603
- if (
604
- gateB.contractHash !== receipt.plan.canonicalHash ||
605
- !sameNormalized(gateB, receipt.approvals.gateB, options) ||
606
- !sameNormalized(artifacts, receipt.artifacts, options) ||
607
- !sameNormalized(traceability, receipt.traceability, options)
608
- ) {
609
- throw new Error("delivery-receipt-invalid");
610
- }
611
- return structuredClone(receipt);
612
- }
613
-
614
- function sortStrings(values: readonly string[]): string[] {
615
- return [...values].sort((left, right) =>
616
- compareCanonicalStrings(left, right),
617
- );
618
- }
619
-
620
- function sortCanonical<T>(values: readonly T[]): T[] {
621
- return [...values].sort((left, right) =>
622
- compareCanonicalStrings(canonicalJson(left), canonicalJson(right)),
623
- );
624
- }
625
-
626
- function normalizeVerification(
627
- verification: unknown,
628
- ): StructuredVerificationContract {
629
- const validation = validateVerificationContract(verification);
630
- if (!validation.ok) throw new Error("delivery-verification-invalid");
631
- const cloned = validation.value;
632
- if (cloned.kind === "vitest") {
633
- cloned.testFiles = sortStrings(cloned.testFiles);
634
- } else if (cloned.kind === "steps") {
635
- cloned.steps = cloned.steps.map((step) => {
636
- if (step.kind !== "vitest") return step;
637
- return { ...step, testFiles: sortStrings(step.testFiles) };
638
- });
639
- }
640
- return cloned;
641
- }
642
-
643
- function normalizePhase(value: unknown, readOnly = false): PhaseBoundary {
644
- if (
645
- !isRecord(value) ||
646
- !exactKeys(value, [
647
- "read",
648
- "write",
649
- "delete",
650
- "verification",
651
- "verificationInputs",
652
- ...(Object.hasOwn(value, "verificationLock") ? ["verificationLock"] : []),
653
- ]) ||
654
- !Array.isArray(value.read) ||
655
- !Array.isArray(value.write) ||
656
- !Array.isArray(value.delete) ||
657
- !Array.isArray(value.verificationInputs) ||
658
- !value.read.every(isValidRelativePath) ||
659
- !value.write.every(isValidRelativePath) ||
660
- !value.delete.every(isValidRelativePath)
661
- ) {
662
- throw new Error("delivery-plan-invalid-phase");
663
- }
664
- const write = sortStrings(value.write as string[]);
665
- const deletions = sortStrings(value.delete as string[]);
666
- if (!readOnly && write.length + deletions.length === 0) {
667
- throw new Error("delivery-plan-empty-phase-boundary");
668
- }
669
- if (write.some((candidate) => deletions.includes(candidate))) {
670
- throw new Error("delivery-plan-write-delete-overlap");
671
- }
672
- return {
673
- read: sortStrings(value.read as string[]),
674
- write,
675
- delete: deletions,
676
- verification: normalizeVerification(
677
- value.verification as StructuredVerificationContract,
678
- ),
679
- verificationInputs: sortCanonical(
680
- structuredClone(
681
- value.verificationInputs,
682
- ) as PhaseBoundary["verificationInputs"],
683
- ),
684
- ...(typeof value.verificationLock === "string"
685
- ? { verificationLock: value.verificationLock }
686
- : {}),
687
- };
688
- }
689
-
690
- function normalizeTask(
691
- value: unknown,
692
- options: {
693
- consumerRoot?: string;
694
- preserveMissingBaseline?: boolean;
695
- } = {},
696
- ): PlanTaskDraft {
697
- const taskId =
698
- isRecord(value) && typeof value.taskId === "string"
699
- ? value.taskId
700
- : undefined;
701
- const context = taskId ? { taskId } : {};
702
- try {
703
- if (
704
- !isRecord(value) ||
705
- !isRecord(value.phases) ||
706
- !isRecord(value.affectedVerification) ||
707
- !isRecord(value.repairVerification)
708
- ) {
709
- throw new Error("delivery-plan-invalid-task");
710
- }
711
- const phases = value.phases;
712
- if (!Object.hasOwn(phases, "red") || !Object.hasOwn(phases, "green")) {
713
- throw new Error("delivery-plan-invalid-task-phases");
714
- }
715
- const cloned = structuredClone(value) as unknown as PlanTaskDraft;
716
- let affectedVerification: StructuredVerificationContract;
717
- try {
718
- affectedVerification = normalizeVerification(value.affectedVerification);
719
- } catch (error) {
720
- throw asPlanValidationError(error, {
721
- ...context,
722
- field: "affectedVerification",
723
- });
724
- }
725
- let repairVerification: StructuredVerificationContract;
726
- try {
727
- repairVerification = normalizeVerification(value.repairVerification);
728
- } catch (error) {
729
- throw asPlanValidationError(error, {
730
- ...context,
731
- field: "repairVerification",
732
- });
733
- }
734
- let baselineVerification: StructuredVerificationContract | undefined;
735
- if (value.baselineVerification !== undefined) {
736
- try {
737
- baselineVerification = normalizeVerification(
738
- value.baselineVerification,
739
- );
740
- } catch (error) {
741
- throw asPlanValidationError(error, {
742
- ...context,
743
- field: "baselineVerification",
744
- });
745
- }
746
- } else if (!options.preserveMissingBaseline) {
747
- const unavailable = verificationInputPaths(affectedVerification).filter(
748
- (input) =>
749
- !options.consumerRoot ||
750
- !isSafeRegularFile(options.consumerRoot, input),
751
- );
752
- if (unavailable.length > 0) {
753
- throw new DesignPlanValidationError(
754
- "delivery-task-baseline-unavailable",
755
- unavailable.map((input) => ({
756
- code: "workspace-input-unavailable",
757
- ...context,
758
- field: "baselineVerification",
759
- verificationId: affectedVerification.id,
760
- path: input,
761
- })),
762
- );
763
- }
764
- baselineVerification = structuredClone(affectedVerification);
765
- }
766
- if (
767
- (baselineVerification &&
768
- baselineVerification.classification !== "expected-green") ||
769
- affectedVerification.classification !== "expected-green" ||
770
- repairVerification.classification !== "expected-green"
771
- ) {
772
- throw new Error("delivery-task-verification-invalid");
773
- }
774
- const normalizeTaskPhase = (
775
- phase: "red" | "green" | "refactor",
776
- candidate: unknown,
777
- ) => {
778
- try {
779
- return normalizePhase(
780
- candidate,
781
- phase === "red" &&
782
- value.verificationMode !== undefined &&
783
- value.verificationMode !== "behavior",
784
- );
785
- } catch (error) {
786
- throw asPlanValidationError(error, {
787
- ...context,
788
- phase,
789
- field: `phases.${phase}`,
790
- });
791
- }
792
- };
793
- return {
794
- ...cloned,
795
- dependsOn: sortStrings(cloned.dependsOn),
796
- roots: sortStrings(cloned.roots),
797
- phases: {
798
- red: normalizeTaskPhase("red", phases.red),
799
- green: normalizeTaskPhase("green", phases.green),
800
- ...(phases.refactor !== undefined
801
- ? { refactor: normalizeTaskPhase("refactor", phases.refactor) }
802
- : {}),
803
- },
804
- scheduling: {
805
- conflicts: sortStrings(cloned.scheduling.conflicts),
806
- resources: sortStrings(cloned.scheduling.resources),
807
- },
808
- approvedDependencies: sortStrings(cloned.approvedDependencies),
809
- ...(baselineVerification ? { baselineVerification } : {}),
810
- affectedVerification,
811
- repairVerification,
812
- impactClosure: {
813
- changedSurfaces: sortStrings(
814
- cloned.impactClosure.changedSurfaces,
815
- ) as PlanTaskDraft["impactClosure"]["changedSurfaces"],
816
- searchEvidence: sortStrings(cloned.impactClosure.searchEvidence),
817
- relatedTests: [...cloned.impactClosure.relatedTests].sort(
818
- (left, right) =>
819
- compareCanonicalStrings(left.path, right.path) ||
820
- compareCanonicalStrings(left.disposition, right.disposition),
821
- ),
822
- affectedSuite: sortStrings(cloned.impactClosure.affectedSuite),
823
- },
824
- };
825
- } catch (error) {
826
- throw asPlanValidationError(error, context);
827
- }
828
- }
829
-
830
- function graphFromPlan(plan: ImplementPlan): ImplementGraphBoundary {
831
- const stripPhase = (phase: PhaseBoundary): PhaseBoundary => ({
832
- read: phase.read,
833
- write: phase.write,
834
- delete: phase.delete,
835
- verification: phase.verification,
836
- verificationInputs: phase.verificationInputs,
837
- ...(phase.verificationLock
838
- ? { verificationLock: phase.verificationLock }
839
- : {}),
840
- });
841
- return {
842
- changeId: plan.changeId,
843
- tasks: plan.tasks.map((task) => {
844
- const {
845
- baselineVerification: _baselineVerification,
846
- affectedVerification: _affectedVerification,
847
- repairVerification: _repairVerification,
848
- ...boundary
849
- } = task;
850
- return {
851
- ...boundary,
852
- phases: {
853
- red: stripPhase(task.phases.red),
854
- green: stripPhase(task.phases.green),
855
- ...(task.phases.refactor
856
- ? { refactor: stripPhase(task.phases.refactor) }
857
- : {}),
858
- },
859
- };
860
- }),
861
- outputs: structuredClone(plan.outputs),
862
- };
863
- }
864
-
865
- type PathOperation = {
866
- taskId: string;
867
- phase: "red" | "green" | "refactor";
868
- kind: "write" | "delete";
869
- };
870
-
871
- const PLAN_PHASE_INDEX = { red: 0, green: 1, refactor: 2 } as const;
872
-
873
- function taskAncestors(
874
- taskId: string,
875
- tasks: ReadonlyMap<string, PlanTaskDraft>,
876
- ): Set<string> {
877
- const ancestors = new Set<string>();
878
- const pending = [...(tasks.get(taskId)?.dependsOn ?? [])];
879
- while (pending.length > 0) {
880
- const dependency = pending.pop() as string;
881
- if (ancestors.has(dependency)) continue;
882
- ancestors.add(dependency);
883
- pending.push(...(tasks.get(dependency)?.dependsOn ?? []));
884
- }
885
- return ancestors;
886
- }
887
-
888
- function pathOperations(plan: ImplementPlan, input: string): PathOperation[] {
889
- return plan.tasks.flatMap((task) =>
890
- (["red", "green", "refactor"] as const).flatMap((phase) => {
891
- const boundary = task.phases[phase];
892
- if (!boundary) return [];
893
- return [
894
- ...(boundary.write.includes(input)
895
- ? [{ taskId: task.taskId, phase, kind: "write" as const }]
896
- : []),
897
- ...(boundary.delete.includes(input)
898
- ? [{ taskId: task.taskId, phase, kind: "delete" as const }]
899
- : []),
900
- ];
901
- }),
902
- );
903
- }
904
-
905
- function operationBefore(
906
- left: PathOperation,
907
- right: PathOperation,
908
- ancestors: ReadonlyMap<string, ReadonlySet<string>>,
909
- ): boolean {
910
- return left.taskId === right.taskId
911
- ? PLAN_PHASE_INDEX[left.phase] < PLAN_PHASE_INDEX[right.phase]
912
- : (ancestors.get(right.taskId)?.has(left.taskId) ?? false);
913
- }
914
-
915
- function pathSurvives(
916
- operations: readonly PathOperation[],
917
- ancestors: ReadonlyMap<string, ReadonlySet<string>>,
918
- ): boolean {
919
- return operations
920
- .filter((operation) => operation.kind === "delete")
921
- .every((deletion) =>
922
- operations.some(
923
- (operation) =>
924
- operation.kind === "write" &&
925
- operationBefore(deletion, operation, ancestors),
926
- ),
927
- );
928
- }
929
-
930
- /**
931
- * File-level acceptance entrypoints named directly by a verification contract.
932
- * Vitest identifies its test files and a Node static check identifies its
933
- * executable script. Package scripts do not expose selected files, and an
934
- * unrelated helper or data output is not promoted merely because of its path.
935
- */
936
- function verificationAcceptanceEntrypoints(
937
- verification: StructuredVerificationContract,
938
- ): string[] {
939
- return verificationSteps(verification).flatMap((step) => {
940
- if (step.kind === "vitest") return step.testFiles;
941
- if (step.kind === "static-check" && step.runner.kind === "node") {
942
- return [step.runner.script];
943
- }
944
- return [];
945
- });
946
- }
947
-
948
- function agentsTargetAvailable(
949
- root: string,
950
- operation: PlanAgentsCheckpointOperation,
951
- ): boolean {
952
- if (operation.impact === "create-index") {
953
- return ["absent", "file"].includes(
954
- observeSafePath(root, operation.target).kind,
955
- );
956
- }
957
- if (!isSafeRegularFile(root, operation.target)) return false;
958
- if (operation.impact === "update-existing") return true;
959
- try {
960
- const text = new TextDecoder("utf-8", { fatal: true }).decode(
961
- readFileSync(path.join(root, operation.target)),
962
- );
963
- const start = text.indexOf(AGENTS_START);
964
- const end = text.indexOf(AGENTS_END);
965
- if (
966
- start < 0 ||
967
- end <= start ||
968
- text.indexOf(AGENTS_START, start + AGENTS_START.length) >= 0 ||
969
- text.indexOf(AGENTS_END, end + AGENTS_END.length) >= 0
970
- ) {
971
- return false;
972
- }
973
- return (
974
- `${text.slice(0, start)}${text.slice(end + AGENTS_END.length)}`.trim()
975
- .length > 0
976
- );
977
- } catch {
978
- return false;
979
- }
980
- }
981
-
982
- function inputTimingDiagnostics(
983
- plan: ImplementPlan,
984
- consumerRoot: string,
985
- ): DesignPlanDiagnostic[] {
986
- const diagnostics: DesignPlanDiagnostic[] = [];
987
- const tasks = new Map(plan.tasks.map((task) => [task.taskId, task]));
988
- const ancestors = new Map(
989
- plan.tasks.map((task) => [task.taskId, taskAncestors(task.taskId, tasks)]),
990
- );
991
- const outputByPath = new Map(
992
- plan.outputs.map((output) => [output.path, output]),
993
- );
994
- const agentsOperations = new Map(
995
- plan.verification.agentsCheckpoint.operations.map((operation) => [
996
- operation.target,
997
- operation,
998
- ]),
999
- );
1000
- const unavailable = (input: {
1001
- field: string;
1002
- verification: StructuredVerificationContract;
1003
- path: string;
1004
- task?: PlanTaskDraft;
1005
- code: "workspace-input-unavailable" | "producer-output-unavailable";
1006
- }) => {
1007
- const output = outputByPath.get(input.path);
1008
- diagnostics.push({
1009
- code: input.code,
1010
- ...(input.task ? { taskId: input.task.taskId } : {}),
1011
- field: input.field,
1012
- verificationId: input.verification.id,
1013
- path: input.path,
1014
- ...(output
1015
- ? {
1016
- outputId: output.id,
1017
- producerTaskId: output.producer.taskId,
1018
- producerPhase: output.producer.phase,
1019
- }
1020
- : {}),
1021
- });
1022
- };
1023
-
1024
- const requireOriginalInputs = (
1025
- field: string,
1026
- verification: StructuredVerificationContract,
1027
- task?: PlanTaskDraft,
1028
- ) => {
1029
- for (const input of verificationInputPaths(verification)) {
1030
- if (!isSafeRegularFile(consumerRoot, input)) {
1031
- unavailable({
1032
- field,
1033
- verification,
1034
- path: input,
1035
- task,
1036
- code: "workspace-input-unavailable",
1037
- });
1038
- }
1039
- }
1040
- };
1041
- const requireInput = (
1042
- field: string,
1043
- verification: StructuredVerificationContract,
1044
- input: string,
1045
- task?: PlanTaskDraft,
1046
- ) => {
1047
- const actualPaths = verificationInputPaths(verification).sort(
1048
- compareCanonicalStrings,
1049
- );
1050
- if (actualPaths.includes(input)) return;
1051
- diagnostics.push({
1052
- code: "verification-input-binding-mismatch",
1053
- ...(task ? { taskId: task.taskId } : {}),
1054
- field,
1055
- verificationId: verification.id,
1056
- path: input,
1057
- expectedPaths: [...new Set([...actualPaths, input])].sort(
1058
- compareCanonicalStrings,
1059
- ),
1060
- actualPaths,
1061
- });
1062
- };
1063
-
1064
- for (const task of plan.tasks) {
1065
- if (task.baselineVerification) {
1066
- requireOriginalInputs(
1067
- "baselineVerification",
1068
- task.baselineVerification,
1069
- task,
1070
- );
1071
- }
1072
- const permittedTasks = new Set([
1073
- task.taskId,
1074
- ...(ancestors.get(task.taskId) ?? []),
1075
- ]);
1076
- for (const [field, verification] of [
1077
- ["affectedVerification", task.affectedVerification],
1078
- ["repairVerification", task.repairVerification],
1079
- ] as const) {
1080
- for (const input of verificationInputPaths(verification)) {
1081
- const output = outputByPath.get(input);
1082
- if (output && !permittedTasks.has(output.producer.taskId)) {
1083
- diagnostics.push({
1084
- code: "producer-not-dependency",
1085
- taskId: task.taskId,
1086
- field,
1087
- verificationId: verification.id,
1088
- path: input,
1089
- outputId: output.id,
1090
- producerTaskId: output.producer.taskId,
1091
- producerPhase: output.producer.phase,
1092
- });
1093
- continue;
1094
- }
1095
- const operations = pathOperations(plan, input).filter((operation) =>
1096
- permittedTasks.has(operation.taskId),
1097
- );
1098
- if (
1099
- (!output && !isSafeRegularFile(consumerRoot, input)) ||
1100
- !pathSurvives(operations, ancestors)
1101
- ) {
1102
- unavailable({
1103
- field,
1104
- verification,
1105
- path: input,
1106
- task,
1107
- code: output
1108
- ? "producer-output-unavailable"
1109
- : "workspace-input-unavailable",
1110
- });
1111
- }
1112
- }
1113
- }
1114
- }
1115
-
1116
- requireOriginalInputs(
1117
- "verification.baseline.fullSuite",
1118
- plan.verification.baseline.fullSuite,
1119
- );
1120
- const deliveryAcceptanceEntrypoints = new Set([
1121
- ...verificationAcceptanceEntrypoints(plan.verification.change.fullSuite),
1122
- ...verificationAcceptanceEntrypoints(plan.verification.change.postApply),
1123
- ...(plan.verification.agentsCheckpoint.verification
1124
- ? verificationAcceptanceEntrypoints(
1125
- plan.verification.agentsCheckpoint.verification,
1126
- )
1127
- : []),
1128
- ]);
1129
- for (const output of plan.outputs) {
1130
- const producer = tasks.get(output.producer.taskId);
1131
- const target = producer?.phases[output.producer.phase];
1132
- if (
1133
- !producer ||
1134
- !target ||
1135
- observeSafePath(consumerRoot, output.path).kind !== "absent"
1136
- ) {
1137
- continue;
1138
- }
1139
- const declaredTests = new Set([
1140
- ...producer.impactClosure.relatedTests.map((test) => test.path),
1141
- ...producer.impactClosure.affectedSuite,
1142
- ...(["red", "green", "refactor"] as const).flatMap((phase) => {
1143
- const boundary = producer.phases[phase];
1144
- return boundary
1145
- ? verificationAcceptanceEntrypoints(boundary.verification)
1146
- : [];
1147
- }),
1148
- ...verificationAcceptanceEntrypoints(producer.affectedVerification),
1149
- ...verificationAcceptanceEntrypoints(producer.repairVerification),
1150
- ...deliveryAcceptanceEntrypoints,
1151
- ]);
1152
- if (!declaredTests.has(output.path)) continue;
1153
- for (const phase of ["red", "green", "refactor"] as const) {
1154
- const boundary = producer.phases[phase];
1155
- if (
1156
- !boundary ||
1157
- PLAN_PHASE_INDEX[phase] < PLAN_PHASE_INDEX[output.producer.phase]
1158
- ) {
1159
- continue;
1160
- }
1161
- requireInput(
1162
- `phases.${phase}.verification`,
1163
- boundary.verification,
1164
- output.path,
1165
- producer,
1166
- );
1167
- }
1168
- requireInput(
1169
- "affectedVerification",
1170
- producer.affectedVerification,
1171
- output.path,
1172
- producer,
1173
- );
1174
- requireInput(
1175
- "repairVerification",
1176
- producer.repairVerification,
1177
- output.path,
1178
- producer,
1179
- );
1180
- requireInput(
1181
- "verification.change.fullSuite",
1182
- plan.verification.change.fullSuite,
1183
- output.path,
1184
- );
1185
- requireInput(
1186
- "verification.change.postApply",
1187
- plan.verification.change.postApply,
1188
- output.path,
1189
- );
1190
- }
1191
- for (const [field, verification, afterAgents] of [
1192
- [
1193
- "verification.change.fullSuite",
1194
- plan.verification.change.fullSuite,
1195
- false,
1196
- ],
1197
- ["verification.change.postApply", plan.verification.change.postApply, true],
1198
- ...(plan.verification.agentsCheckpoint.verification
1199
- ? ([
1200
- [
1201
- "verification.agentsCheckpoint.verification",
1202
- plan.verification.agentsCheckpoint.verification,
1203
- true,
1204
- ],
1205
- ] as const)
1206
- : []),
1207
- ] as const) {
1208
- for (const input of verificationInputPaths(verification)) {
1209
- const output = outputByPath.get(input);
1210
- const operations = pathOperations(plan, input);
1211
- const agentsOperation = afterAgents
1212
- ? agentsOperations.get(input)
1213
- : undefined;
1214
- if (
1215
- agentsOperation
1216
- ? !agentsTargetAvailable(consumerRoot, agentsOperation)
1217
- : (!output && !isSafeRegularFile(consumerRoot, input)) ||
1218
- !pathSurvives(operations, ancestors)
1219
- ) {
1220
- unavailable({
1221
- field,
1222
- verification,
1223
- path: input,
1224
- code: output
1225
- ? "producer-output-unavailable"
1226
- : "workspace-input-unavailable",
1227
- });
1228
- }
1229
- }
1230
- }
1231
- return diagnostics;
1232
- }
1233
-
1234
- function deliveryVerificationDiagnostics(
1235
- plan: ImplementPlan,
1236
- consumerRoot: string,
1237
- ): Array<Record<string, unknown>> {
1238
- const contracts: Array<{
1239
- owner: string;
1240
- verification: StructuredVerificationContract;
1241
- }> = plan.tasks.flatMap((task) => [
1242
- ...(task.baselineVerification
1243
- ? [
1244
- {
1245
- owner: `task:${task.taskId}:baseline`,
1246
- verification: task.baselineVerification,
1247
- },
1248
- ]
1249
- : []),
1250
- {
1251
- owner: `task:${task.taskId}:affected`,
1252
- verification: task.affectedVerification,
1253
- },
1254
- {
1255
- owner: `task:${task.taskId}:repair`,
1256
- verification: task.repairVerification,
1257
- },
1258
- ]);
1259
- contracts.push(
1260
- {
1261
- owner: "baseline:full-suite",
1262
- verification: plan.verification.baseline.fullSuite,
1263
- },
1264
- {
1265
- owner: "change:full-suite",
1266
- verification: plan.verification.change.fullSuite,
1267
- },
1268
- {
1269
- owner: "change:post-apply",
1270
- verification: plan.verification.change.postApply,
1271
- },
1272
- );
1273
- if (plan.verification.agentsCheckpoint.verification) {
1274
- contracts.push({
1275
- owner: "agents-checkpoint",
1276
- verification: plan.verification.agentsCheckpoint.verification,
1277
- });
1278
- }
1279
- return contracts.flatMap(({ owner, verification }) => {
1280
- const capability = validateVerificationAdapterCapability(
1281
- consumerRoot,
1282
- verification,
1283
- { dependencyOwner: consumerRoot },
1284
- );
1285
- if (capability.ok) return [];
1286
- const { message: _message, ...diagnostic } = capability.diagnostic;
1287
- return [{ owner, ...diagnostic }];
1288
- });
1289
- }
1290
-
1291
- function normalizeExpectedGreenVerification(
1292
- value: unknown,
1293
- ): StructuredVerificationContract {
1294
- const verification = normalizeVerification(value);
1295
- if (verification.classification !== "expected-green") {
1296
- throw new Error("delivery-verification-classification-invalid");
1297
- }
1298
- return verification;
1299
- }
1300
-
1301
- function normalizeAgentsManagedBlock(value: unknown): string {
1302
- if (
1303
- typeof value !== "string" ||
1304
- value.length === 0 ||
1305
- Buffer.byteLength(value) > 64 * 1024 ||
1306
- value.includes("\0") ||
1307
- value.includes("\r")
1308
- ) {
1309
- throw new Error("delivery-agents-checkpoint-invalid");
1310
- }
1311
- const lines = value.endsWith("\n")
1312
- ? value.slice(0, -1).split("\n")
1313
- : value.split("\n");
1314
- if (
1315
- lines[0] !== AGENTS_START ||
1316
- lines.at(-1) !== AGENTS_END ||
1317
- lines.filter((line) => line === AGENTS_START).length !== 1 ||
1318
- lines.filter((line) => line === AGENTS_END).length !== 1
1319
- ) {
1320
- throw new Error("delivery-agents-checkpoint-invalid");
1321
- }
1322
- return lines.join("\n");
1323
- }
1324
-
1325
- function normalizeAgentsCheckpointOperations(
1326
- value: unknown,
1327
- ): PlanAgentsCheckpointOperation[] {
1328
- if (!Array.isArray(value) || value.length > 64) {
1329
- throw new Error("delivery-agents-checkpoint-invalid");
1330
- }
1331
- const targets = new Set<string>();
1332
- return value
1333
- .map((operation): PlanAgentsCheckpointOperation => {
1334
- if (
1335
- !isRecord(operation) ||
1336
- !exactKeys(operation, [
1337
- "target",
1338
- "impact",
1339
- "taskIds",
1340
- "managedBlock",
1341
- ]) ||
1342
- !isAgentsPath(operation.target) ||
1343
- !["update-existing", "create-index", "remove-index"].includes(
1344
- String(operation.impact),
1345
- ) ||
1346
- targets.has(operation.target) ||
1347
- !Array.isArray(operation.taskIds) ||
1348
- operation.taskIds.length === 0 ||
1349
- operation.taskIds.some(
1350
- (taskId) => typeof taskId !== "string" || taskId.length === 0,
1351
- ) ||
1352
- new Set(operation.taskIds).size !== operation.taskIds.length
1353
- ) {
1354
- throw new Error("delivery-agents-checkpoint-invalid");
1355
- }
1356
- targets.add(operation.target);
1357
- const impact = operation.impact as Exclude<AgentsImpact, "none">;
1358
- if (
1359
- (impact === "remove-index" && operation.managedBlock !== null) ||
1360
- (impact !== "remove-index" &&
1361
- typeof operation.managedBlock !== "string")
1362
- ) {
1363
- throw new Error("delivery-agents-checkpoint-invalid");
1364
- }
1365
- return {
1366
- target: operation.target,
1367
- impact,
1368
- taskIds: sortStrings(operation.taskIds as string[]),
1369
- managedBlock:
1370
- impact === "remove-index"
1371
- ? null
1372
- : normalizeAgentsManagedBlock(operation.managedBlock),
1373
- };
1374
- })
1375
- .sort((left, right) => compareCanonicalStrings(left.target, right.target));
1376
- }
1377
-
1378
- function normalizeVerificationPlan(value: unknown): PlanVerification {
1379
- if (
1380
- isRecord(value) &&
1381
- isRecord(value.change) &&
1382
- value.change.affected !== "task-affected-contracts"
1383
- ) {
1384
- throw new DesignPlanValidationError("delivery-verification-plan-invalid", [
1385
- {
1386
- code: "delivery-verification-plan-invalid",
1387
- field: "verification.change.affected",
1388
- category: "enum",
1389
- allowedValues: ["task-affected-contracts"],
1390
- },
1391
- ]);
1392
- }
1393
- if (
1394
- !isRecord(value) ||
1395
- !exactKeys(value, [
1396
- "baseline",
1397
- "change",
1398
- "artifactCorrection",
1399
- "repair",
1400
- "agentsCheckpoint",
1401
- ]) ||
1402
- !isRecord(value.baseline) ||
1403
- !exactKeys(value.baseline, [
1404
- "target",
1405
- "affected",
1406
- "fullSuite",
1407
- "failureIdentity",
1408
- ]) ||
1409
- value.baseline.target !== "task-red-contracts" ||
1410
- value.baseline.affected !== "task-affected-contracts" ||
1411
- value.baseline.failureIdentity !== "normalized" ||
1412
- !isRecord(value.change) ||
1413
- !exactKeys(value.change, ["affected", "fullSuite", "postApply"]) ||
1414
- value.change.affected !== "task-affected-contracts" ||
1415
- !isRecord(value.artifactCorrection) ||
1416
- !exactKeys(value.artifactCorrection, ["maxAttempts"]) ||
1417
- !Number.isSafeInteger(value.artifactCorrection.maxAttempts) ||
1418
- (value.artifactCorrection.maxAttempts as number) < 2 ||
1419
- (value.artifactCorrection.maxAttempts as number) > 3 ||
1420
- !isRecord(value.repair) ||
1421
- !exactKeys(value.repair, [
1422
- "maxAttempts",
1423
- "inBoundaryOnly",
1424
- "approvalOnBoundaryExpansion",
1425
- "attribution",
1426
- ]) ||
1427
- typeof value.repair.maxAttempts !== "number" ||
1428
- !Number.isSafeInteger(value.repair.maxAttempts) ||
1429
- value.repair.maxAttempts < 1 ||
1430
- value.repair.maxAttempts > 3 ||
1431
- value.repair.inBoundaryOnly !== true ||
1432
- value.repair.approvalOnBoundaryExpansion !== true ||
1433
- canonicalJson(value.repair.attribution) !==
1434
- canonicalJson([
1435
- "pre-existing",
1436
- "introduced",
1437
- "unresolved",
1438
- "environment",
1439
- ]) ||
1440
- !isRecord(value.agentsCheckpoint) ||
1441
- !exactKeys(value.agentsCheckpoint, [
1442
- "required",
1443
- "verification",
1444
- "operations",
1445
- ]) ||
1446
- typeof value.agentsCheckpoint.required !== "boolean" ||
1447
- (value.agentsCheckpoint.required &&
1448
- !isRecord(value.agentsCheckpoint.verification)) ||
1449
- (!value.agentsCheckpoint.required &&
1450
- value.agentsCheckpoint.verification !== null) ||
1451
- !Array.isArray(value.agentsCheckpoint.operations)
1452
- ) {
1453
- throw new Error("delivery-verification-plan-invalid");
1454
- }
1455
- return {
1456
- baseline: {
1457
- target: "task-red-contracts",
1458
- affected: "task-affected-contracts",
1459
- fullSuite: normalizeExpectedGreenVerification(value.baseline.fullSuite),
1460
- failureIdentity: "normalized",
1461
- },
1462
- change: {
1463
- affected: "task-affected-contracts",
1464
- fullSuite: normalizeExpectedGreenVerification(value.change.fullSuite),
1465
- postApply: normalizeExpectedGreenVerification(value.change.postApply),
1466
- },
1467
- artifactCorrection: {
1468
- maxAttempts: value.artifactCorrection.maxAttempts as number,
1469
- },
1470
- repair: {
1471
- maxAttempts: value.repair.maxAttempts as number,
1472
- inBoundaryOnly: true,
1473
- approvalOnBoundaryExpansion: true,
1474
- attribution: ["pre-existing", "introduced", "unresolved", "environment"],
1475
- },
1476
- agentsCheckpoint: {
1477
- required: value.agentsCheckpoint.required,
1478
- verification: value.agentsCheckpoint.required
1479
- ? normalizeExpectedGreenVerification(
1480
- value.agentsCheckpoint.verification,
1481
- )
1482
- : null,
1483
- operations: normalizeAgentsCheckpointOperations(
1484
- value.agentsCheckpoint.operations,
1485
- ),
1486
- },
1487
- };
1488
- }
1489
-
1490
- function normalizeTracking(value: unknown): PlanTracking {
1491
- if (
1492
- !isRecord(value) ||
1493
- !exactKeys(value, ["path", "format", "taskIds", "completionOwner"]) ||
1494
- value.path !== "tasks.md" ||
1495
- value.format !== "markdown-checkbox" ||
1496
- !Array.isArray(value.taskIds) ||
1497
- !value.taskIds.every((taskId) => typeof taskId === "string") ||
1498
- new Set(value.taskIds).size !== value.taskIds.length ||
1499
- value.completionOwner !== "parent"
1500
- ) {
1501
- throw new Error("delivery-tracking-invalid");
1502
- }
1503
- return {
1504
- path: "tasks.md",
1505
- format: "markdown-checkbox",
1506
- taskIds: sortStrings(value.taskIds as string[]),
1507
- completionOwner: "parent",
1508
- };
1509
- }
1510
-
1511
- function normalizeDraft(
1512
- value: unknown,
1513
- options: {
1514
- consumerRoot?: string;
1515
- preserveMissingTaskBaselines?: boolean;
1516
- } = {},
1517
- ): ImplementPlan {
1518
- value = structuredClone(value);
1519
- if (
1520
- !isRecord(value) ||
1521
- !exactKeys(value, [
1522
- "changeId",
1523
- "tasks",
1524
- "outputs",
1525
- "verification",
1526
- "tracking",
1527
- ...(value.changeContract === undefined ? [] : ["changeContract"]),
1528
- ]) ||
1529
- typeof value.changeId !== "string" ||
1530
- !CHANGE_NAME.test(value.changeId) ||
1531
- !Array.isArray(value.tasks) ||
1532
- !Array.isArray(value.outputs)
1533
- ) {
1534
- throw new Error("delivery-plan-invalid");
1535
- }
1536
- const source = value;
1537
- const taskDrafts = value.tasks.map((task: unknown) => {
1538
- if (
1539
- !isRecord(task) ||
1540
- !["mechanical", "refactor"].includes(String(task.verificationMode))
1541
- )
1542
- return task;
1543
- if (!isRecord(task.phases) || !isRecord(task.phases.green))
1544
- throw new Error("delivery-plan-invalid-task-phases");
1545
- if (!source.changeContract)
1546
- throw new Error("change-contract-verification-mode-required");
1547
- const green = task.phases.green;
1548
- return {
1549
- ...task,
1550
- phases: { ...task.phases, red: { ...green, write: [], delete: [] } },
1551
- };
1552
- });
1553
- const draftGraph = validateImplementGraphBoundary({
1554
- changeId: value.changeId,
1555
- tasks: taskDrafts.map((candidate) => {
1556
- if (!isRecord(candidate)) return candidate;
1557
- const {
1558
- baselineVerification: _baselineVerification,
1559
- affectedVerification: _affectedVerification,
1560
- repairVerification: _repairVerification,
1561
- ...boundary
1562
- } = candidate;
1563
- return boundary;
1564
- }),
1565
- outputs: value.outputs,
1566
- });
1567
- if (!draftGraph.ok) {
1568
- throw new DesignPlanValidationError(
1569
- `delivery-plan-invalid:${draftGraph.reason}`,
1570
- draftGraph.diagnostics ?? [draftGraph.diagnostic],
1571
- );
1572
- }
1573
- const plan: ImplementPlan = {
1574
- ...(value.changeContract === undefined
1575
- ? {}
1576
- : { changeContract: normalizeChangeContract(value.changeContract) }),
1577
- changeId: value.changeId,
1578
- tasks: taskDrafts
1579
- .map((task) =>
1580
- normalizeTask(task, {
1581
- consumerRoot: options.consumerRoot,
1582
- preserveMissingBaseline: options.preserveMissingTaskBaselines,
1583
- }),
1584
- )
1585
- .sort((left, right) =>
1586
- compareCanonicalStrings(left.taskId, right.taskId),
1587
- ),
1588
- outputs: (structuredClone(value.outputs) as ImplementGraphOutput[]).sort(
1589
- (left, right) => compareCanonicalStrings(left.id, right.id),
1590
- ),
1591
- verification: normalizeVerificationPlan(value.verification),
1592
- tracking: normalizeTracking(value.tracking),
1593
- };
1594
- if (
1595
- canonicalJson(plan.tracking.taskIds) !==
1596
- canonicalJson(plan.tasks.map((task) => task.taskId).sort())
1597
- ) {
1598
- throw new Error("delivery-tracking-task-mismatch");
1599
- }
1600
- const agentsTasks = plan.tasks.filter(
1601
- (task) => task.agents.impact !== "none" && task.agents.target,
1602
- );
1603
- const agentsByTarget = new Map<string, PlanTaskDraft[]>();
1604
- for (const task of agentsTasks) {
1605
- const target = task.agents.target as string;
1606
- const existing = agentsByTarget.get(target) ?? [];
1607
- existing.push(task);
1608
- agentsByTarget.set(target, existing);
1609
- }
1610
- const operations = plan.verification.agentsCheckpoint.operations;
1611
- const operationsByTarget = new Map(
1612
- operations.map((operation) => [operation.target, operation]),
1613
- );
1614
- const agentsRequired = agentsTasks.length > 0;
1615
- if (
1616
- plan.verification.agentsCheckpoint.required !== agentsRequired ||
1617
- operations.length !== agentsByTarget.size ||
1618
- [...agentsByTarget].some(([target, tasks]) => {
1619
- const operation = operationsByTarget.get(target);
1620
- return (
1621
- !operation ||
1622
- tasks.some((task) => task.agents.impact !== operation.impact) ||
1623
- canonicalJson(operation.taskIds) !==
1624
- canonicalJson(tasks.map((task) => task.taskId).sort())
1625
- );
1626
- })
1627
- ) {
1628
- throw new Error("delivery-agents-checkpoint-mismatch");
1629
- }
1630
- if (plan.changeContract)
1631
- assertPlanWithinChangeContract(plan.changeContract, plan.tasks, [
1632
- plan.verification.change.fullSuite,
1633
- plan.verification.change.postApply,
1634
- ]);
1635
- const graphValidation = validateImplementGraphBoundary(graphFromPlan(plan));
1636
- if (!graphValidation.ok) {
1637
- throw new DesignPlanValidationError(
1638
- `delivery-plan-invalid:${graphValidation.reason}`,
1639
- [graphValidation.diagnostic],
1640
- );
1641
- }
1642
- return plan;
1643
- }
1644
-
1645
- const TASK_BINDINGS_START = "<!-- ABEL:VERIFICATION-BINDINGS:START -->";
1646
- const TASK_BINDINGS_END = "<!-- ABEL:VERIFICATION-BINDINGS:END -->";
1647
-
1648
- function taskVerificationRegion(markdown: string) {
1649
- const markers = markdown.match(/<!-- ABEL:VERIFICATION-BINDINGS/gu) ?? [];
1650
- if (markers.length === 0) return undefined;
1651
- const match =
1652
- /^<!-- ABEL:VERIFICATION-BINDINGS:START -->\r?\n[\s\S]*?^<!-- ABEL:VERIFICATION-BINDINGS:END -->(?=\r?$)/mu.exec(
1653
- markdown,
1654
- );
1655
- if (markers.length !== 2 || !match) {
1656
- throw new DesignPlanValidationError("traceability-managed-region-invalid", [
1657
- { code: "traceability-managed-region-invalid", path: "tasks.md" },
1658
- ]);
1659
- }
1660
- return {
1661
- start: match.index,
1662
- end: match.index + match[0].length,
1663
- text: match[0],
1664
- };
1665
- }
1666
-
1667
- // Only identity bindings are generated. Author task checkboxes and Scenario
1668
- // ownership remain outside this region and are validated independently.
1669
- function renderTasks(plan: ImplementPlan): string {
1670
- const lines = [TASK_BINDINGS_START, "## Compiled verification bindings", ""];
1671
- for (const task of [...plan.tasks].sort((a, b) =>
1672
- compareCanonicalStrings(a.taskId, b.taskId),
1673
- )) {
1674
- lines.push(
1675
- `- Task \`${task.taskId}\` (${task.verificationMode ?? "behavior"})`,
1676
- );
1677
- if (!task.verificationMode || task.verificationMode === "behavior") {
1678
- lines.push(` - Red: \`${task.phases.red.verification.id}\``);
1679
- }
1680
- lines.push(` - Green: \`${task.phases.green.verification.id}\``);
1681
- if (task.phases.refactor) {
1682
- lines.push(` - Refactor: \`${task.phases.refactor.verification.id}\``);
1683
- }
1684
- }
1685
- lines.push(TASK_BINDINGS_END);
1686
- return lines.join("\n");
1687
- }
1688
-
1689
- export function bindTaskVerifications(
1690
- markdown: string,
1691
- plan: ImplementPlan,
1692
- ): string {
1693
- const region = taskVerificationRegion(markdown);
1694
- const bindings = renderTasks(plan);
1695
- if (region) {
1696
- return (
1697
- markdown.slice(0, region.start) + bindings + markdown.slice(region.end)
1698
- );
1699
- }
1700
- return `${markdown}${markdown.endsWith("\n") ? "\n" : "\n\n"}${bindings}\n`;
1701
- }
1702
-
1703
- export function compileImplementPlan(
1704
- draft: unknown,
1705
- options: { consumerRoot: string; bindExecutionInputs?: boolean },
1706
- ): CompiledDelivery {
1707
- try {
1708
- draft = expandSingleTaskDraft(draft);
1709
- // Resolve omitted manifest command bytes before named definitions are
1710
- // validated and purpose identities are hashed. The second pass below
1711
- // binds execution inputs on the expanded contracts with their final IDs.
1712
- if (options.bindExecutionInputs)
1713
- draft = bindDraftVerificationInputs(options.consumerRoot, draft);
1714
- draft = expandPlanDraft(draft);
1715
- if (options.bindExecutionInputs)
1716
- draft = bindDraftVerificationInputs(options.consumerRoot, draft);
1717
- draft = preparePlanDraft(draft);
1718
- const retainedLegacy =
1719
- isRecord(draft) && legacyPlans.get(draft) === JSON.stringify(draft);
1720
- const normalized = normalizeDraft(draft, {
1721
- consumerRoot: options.consumerRoot,
1722
- preserveMissingTaskBaselines: retainedLegacy,
1723
- });
1724
- const plan = retainedLegacy
1725
- ? (draft as unknown as ImplementPlan)
1726
- : normalized;
1727
- const graph = graphFromPlan(plan);
1728
- const readiness = assessImplementGraphReadiness(
1729
- options.consumerRoot,
1730
- graph,
1731
- {
1732
- completedTasks: [],
1733
- blockedTasks: [],
1734
- appliedPhases: [],
1735
- },
1736
- );
1737
- const diagnostics = [
1738
- ...readiness.closure.diagnostics,
1739
- ...inputTimingDiagnostics(plan, options.consumerRoot),
1740
- ...deliveryVerificationDiagnostics(plan, options.consumerRoot),
1741
- ] as DesignPlanDiagnostic[];
1742
- if (diagnostics.length > 0) {
1743
- throw new DesignPlanValidationError(
1744
- `delivery-plan-not-executable:${canonicalJson(diagnostics)}`,
1745
- diagnostics,
1746
- );
1747
- }
1748
- const canonical = canonicalJson(plan);
1749
- const bytes = new TextEncoder().encode(`${canonical}\n`);
1750
- const rawSha256 = createHash("sha256").update(bytes).digest("hex");
1751
- const planHash = hashCanonicalValue(plan);
1752
- const closure = structuredClone(readiness.closure);
1753
- return {
1754
- checks: {
1755
- structure: "passed",
1756
- verificationCapability: "passed",
1757
- contractCoverage: plan.changeContract ? "passed" : "not-checked",
1758
- sealing: "not-performed",
1759
- },
1760
- plan,
1761
- bytes,
1762
- rawSha256,
1763
- planHash,
1764
- closure,
1765
- receipt: {
1766
- plan: {
1767
- path: IMPLEMENT_PLAN_PATH,
1768
- rawSha256,
1769
- canonicalHash: planHash,
1770
- },
1771
- verificationClosure: structuredClone(closure),
1772
- },
1773
- tasksMarkdown: renderTasks(plan),
1774
- };
1775
- } catch (error) {
1776
- throw asPlanValidationError(error);
1777
- }
1778
- }
1779
-
1780
- export function parseImplementPlan(
1781
- bytes: Uint8Array,
1782
- options: DeliveryReadOptions = {},
1783
- ): ImplementPlan {
1784
- const text = new TextDecoder().decode(bytes);
1785
- let parsed: unknown;
1786
- try {
1787
- parsed = JSON.parse(text);
1788
- } catch {
1789
- throw new Error("delivery-plan-json-invalid");
1790
- }
1791
- const normalized = normalizeDraft(parsed, {
1792
- preserveMissingTaskBaselines: true,
1793
- });
1794
- if (`${canonicalJson(normalized)}\n` === text) return normalized;
1795
- if (
1796
- !options.allowLegacyOrder ||
1797
- `${canonicalJson(parsed)}\n` !== text ||
1798
- !sameNormalized(normalized, parsed, options)
1799
- ) {
1800
- throw new Error("delivery-plan-not-canonical");
1801
- }
1802
- // Keep proof-bound historical bytes unchanged; a mutation invalidates this association.
1803
- legacyPlans.set(parsed as object, JSON.stringify(parsed));
1804
- return parsed as ImplementPlan;
1805
- }