@abelxiaoxing/cadence 1.8.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +324 -111
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -26
  4. package/agents/diagnosis-worker.md +14 -25
  5. package/agents/implementation-worker.md +16 -35
  6. package/config/.env.example +2 -5
  7. package/config/workflow-task.example.json +7 -0
  8. package/docs/remote-access.md +60 -0
  9. package/docs/reuse-and-distribution.md +44 -0
  10. package/docs/service-management.md +108 -0
  11. package/licenses/bundled-web.txt +6245 -0
  12. package/licenses/pi-web-ui-MIT.txt +21 -0
  13. package/licenses/pi-web-ui-reuse.md +22 -0
  14. package/package.json +94 -21
  15. package/src/index.ts +22 -1807
  16. package/src/native-build.ts +6 -0
  17. package/src/native-client.ts +103 -0
  18. package/src/native-export.ts +58 -0
  19. package/src/native-history.ts +359 -0
  20. package/src/operator-cli.mjs +453 -1937
  21. package/src/plan-proposal.ts +87 -0
  22. package/src/sdk-worker.mjs +549 -0
  23. package/src/service/contracts/active.ts +82 -0
  24. package/src/service/contracts/budget.ts +148 -0
  25. package/src/service/contracts/build.ts +67 -0
  26. package/src/service/contracts/capture.ts +48 -0
  27. package/src/service/contracts/core.ts +33 -0
  28. package/src/service/contracts/current.ts +261 -0
  29. package/src/service/contracts/domain.ts +68 -0
  30. package/src/service/contracts/history.ts +2245 -0
  31. package/src/service/contracts/models.ts +139 -0
  32. package/src/service/coordination.ts +862 -0
  33. package/src/service/private-files.ts +204 -0
  34. package/src/service-cli.mjs +5592 -0
  35. package/src/service-client.ts +747 -0
  36. package/src/service-contract.ts +245 -0
  37. package/src/service-host.mjs +22382 -0
  38. package/src/service-http-client.ts +425 -0
  39. package/src/service-wire.ts +372 -0
  40. package/src/task-shell.ts +1045 -0
  41. package/src/task-store.ts +479 -0
  42. package/src/task-types.ts +184 -0
  43. package/src/verification-diagnostics.ts +29 -72
  44. package/src/worker-capture.mjs +1197 -0
  45. package/src/worker-public.mjs +1753 -0
  46. package/src/worker-redaction.mjs +608 -0
  47. package/src/worker-tools.mjs +202 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  50. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  56. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  62. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  65. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  68. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  71. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  74. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  77. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  80. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  89. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  92. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  95. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  98. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  101. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  104. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  107. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  108. package/web/dist/assets/cadence.css +2 -0
  109. package/web/dist/assets/cadence.js +7 -0
  110. package/web/dist/assets/markdown.js +296 -0
  111. package/web/dist/assets/react.js +9 -0
  112. package/web/dist/assets/rolldown-runtime.js +1 -0
  113. package/web/dist/assets/timeline-client.js +41 -0
  114. package/web/dist/assets/vis-timeline.css +1274 -0
  115. package/web/dist/assets/vis-timeline.js +32 -0
  116. package/web/dist/index.html +18 -0
  117. package/web/dist/manifest.json +505 -0
  118. package/config/plan-draft.example.json +0 -125
  119. package/config/plan-draft.multiple-tasks.example.json +0 -375
  120. package/config/plan-draft.quick.example.json +0 -78
  121. package/config/routes.example.json +0 -39
  122. package/prompts/abel-design.md +0 -382
  123. package/prompts/abel-diagnose.md +0 -71
  124. package/prompts/abel-implement.md +0 -284
  125. package/prompts/abel-init.md +0 -52
  126. package/src/activation.ts +0 -44
  127. package/src/activity-contracts.ts +0 -82
  128. package/src/agent-registry.ts +0 -53
  129. package/src/apply-transaction.ts +0 -1437
  130. package/src/artifact-store.ts +0 -213
  131. package/src/build-windows-job.mjs +0 -95
  132. package/src/candidate-artifact.ts +0 -143
  133. package/src/candidate-patch.ts +0 -271
  134. package/src/candidate-workspace.ts +0 -80
  135. package/src/canonical.ts +0 -16
  136. package/src/change-contract-schema.ts +0 -191
  137. package/src/change-contract.ts +0 -324
  138. package/src/change-verification.ts +0 -1617
  139. package/src/child-budget.ts +0 -58
  140. package/src/child-model.ts +0 -142
  141. package/src/child-session.ts +0 -640
  142. package/src/contracts.ts +0 -2476
  143. package/src/control-contracts.ts +0 -382
  144. package/src/delivery-compiler.ts +0 -1805
  145. package/src/delivery-revision.ts +0 -42
  146. package/src/design-control.ts +0 -1299
  147. package/src/design-diagnostics.ts +0 -181
  148. package/src/design-journal.ts +0 -1773
  149. package/src/directory-sync.ts +0 -36
  150. package/src/durable-contracts.ts +0 -231
  151. package/src/durable-workflow.ts +0 -978
  152. package/src/evidence-draft.ts +0 -122
  153. package/src/execution-profile.ts +0 -64
  154. package/src/execution-retention.ts +0 -77
  155. package/src/file-snapshot.ts +0 -163
  156. package/src/implement-continuation.ts +0 -559
  157. package/src/implement-graph.ts +0 -690
  158. package/src/implement-plan.ts +0 -72
  159. package/src/install-config.d.mts +0 -6
  160. package/src/isolation-backend.ts +0 -353
  161. package/src/model-source.ts +0 -22
  162. package/src/openspec-cli.ts +0 -494
  163. package/src/operator-cli-entry.mjs +0 -24
  164. package/src/operator-tools.ts +0 -309
  165. package/src/package-candidate.ts +0 -227
  166. package/src/package-delivery.ts +0 -486
  167. package/src/package-state.ts +0 -320
  168. package/src/package-verification.ts +0 -635
  169. package/src/package-workflow.ts +0 -342
  170. package/src/packet-runtime.ts +0 -570
  171. package/src/parent-provider.ts +0 -375
  172. package/src/phase-execution.ts +0 -2259
  173. package/src/pi-adapter.ts +0 -19
  174. package/src/plan-draft-summary.ts +0 -527
  175. package/src/plan-draft.ts +0 -502
  176. package/src/remove-path.ts +0 -32
  177. package/src/route-policy.ts +0 -545
  178. package/src/run-state.ts +0 -420
  179. package/src/run-store.ts +0 -1179
  180. package/src/safe-path.ts +0 -77
  181. package/src/scoped-grep-worker.mjs +0 -23
  182. package/src/scoped-tools.ts +0 -775
  183. package/src/single-task-draft.ts +0 -142
  184. package/src/sqlite-schema.ts +0 -171
  185. package/src/state-root.ts +0 -215
  186. package/src/storage-schema.ts +0 -360
  187. package/src/subagent-activity.ts +0 -1417
  188. package/src/submit-tool.ts +0 -950
  189. package/src/task-ledger.ts +0 -1338
  190. package/src/transport-budget.ts +0 -52
  191. package/src/verification-capability.ts +0 -1191
  192. package/src/verification-environment-io.ts +0 -389
  193. package/src/verification-environment.ts +0 -162
  194. package/src/verification-identity.ts +0 -77
  195. package/src/verification-scan.ts +0 -64
  196. package/src/windows-job-backend.ts +0 -419
  197. package/src/windows-job.c +0 -149
  198. package/src/worker-broker.ts +0 -806
  199. package/src/workflow-engine.ts +0 -28
  200. package/src/workflow-policy.ts +0 -1679
  201. package/src/workflow-recovery-policy.ts +0 -97
  202. package/src/workflow-scheduling.ts +0 -105
  203. package/src/workflow-state-machine.ts +0 -4355
  204. package/src/workflow-status.ts +0 -576
  205. package/src/workspace-io-entry.mjs +0 -76
  206. package/src/workspace-io-worker.mjs +0 -1312
  207. package/src/workspace-io.ts +0 -89
  208. 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
- }