@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
package/src/contracts.ts DELETED
@@ -1,2476 +0,0 @@
1
- // Strict request/result contracts for the private orchestration kernel.
2
- // Structural validation only; no runtime dependency on a schema library.
3
-
4
- export type {
5
- ControlCommand,
6
- ControlCommandName,
7
- ControlCommandValidation,
8
- ControlStage,
9
- } from "./control-contracts.ts";
10
- export {
11
- assertControlCommand,
12
- CONTROL_COMMANDS,
13
- CONTROL_STAGES,
14
- controlRunKey,
15
- validateControlCommand,
16
- } from "./control-contracts.ts";
17
-
18
- export const STAGES = [
19
- "abel-design",
20
- "abel-implement",
21
- "abel-diagnose",
22
- ] as const;
23
- export const ROLES = [
24
- "design-explorer",
25
- "implementation-worker",
26
- "diagnosis-worker",
27
- ] as const;
28
- export const OUTPUT_KINDS = ["evidence", "diff"] as const;
29
- export const PHASES = ["evidence", "red", "green", "refactor"] as const;
30
-
31
- export const AGENTS_IMPACTS = [
32
- "none",
33
- "update-existing",
34
- "create-index",
35
- "remove-index",
36
- ] as const;
37
- export type AgentsImpact = (typeof AGENTS_IMPACTS)[number];
38
-
39
- export const IMPACT_SURFACES = [
40
- "none",
41
- "route-authorization",
42
- "page-state",
43
- "api-response",
44
- "public-html",
45
- ] as const;
46
- export type ImpactSurface = (typeof IMPACT_SURFACES)[number];
47
-
48
- export const LIMITS = {
49
- maxActiveChildSessions: 4,
50
- maxEnvelopeBytes: 64 * 1024,
51
- phaseTimeoutMs: 20 * 60 * 1000,
52
- maxCompleteResultBytes: 64 * 1024,
53
- } as const;
54
-
55
- /** Shared collection bounds for runtime verification and author-facing schemas. */
56
- export const VERIFICATION_LIMITS = Object.freeze({
57
- minSteps: 1,
58
- maxSteps: 8,
59
- minTestFiles: 1,
60
- maxTestFiles: 64,
61
- });
62
-
63
- export const ARTIFACT_FAILURE_CODES = [
64
- "agents-impact-mismatch",
65
- "agents-target-mismatch",
66
- "baseline-bound-mismatch",
67
- "baseline-deletion-kind",
68
- "baseline-directory-kind",
69
- "baseline-directory-mismatch",
70
- "baseline-directory-special-file",
71
- "baseline-directory-symlink",
72
- "baseline-file-kind",
73
- "baseline-shape",
74
- "candidate-check-failed",
75
- "checkout-special-file",
76
- "checkout-symlink",
77
- "dependency-bound-mismatch",
78
- "escaping-path",
79
- "git-apply-check-failed",
80
- "git-apply-failed",
81
- "git-numstat-rejected",
82
- "git-summary-rejected",
83
- "ignored-baseline-input",
84
- "incomplete-baseline",
85
- "invalid-approved-dependencies",
86
- "invalid-checkpoint-contract",
87
- "invalid-diff",
88
- "invalid-diff-bytes",
89
- "invalid-snapshot",
90
- "invalid-structural-result",
91
- "child-no-structural-submit",
92
- "structural-identity-mismatch",
93
- "invalid-verification-contract",
94
- "lockfile-bound-mismatch",
95
- "noncanonical-path",
96
- "nonregular-mode",
97
- "outside-managed-region",
98
- "package-bound-mismatch",
99
- "package-manifest-invalid",
100
- "parent-review-rejected",
101
- "producer-output-unavailable",
102
- "red-not-witnessed",
103
- "submodule",
104
- "target-baseline-missing",
105
- "verification-baseline-missing",
106
- "verification-input-unavailable",
107
- "verification-rejected",
108
- "verification-report-invalid",
109
- "verification-report-missing",
110
- "write-set-mismatch",
111
- ] as const;
112
- export const STALE_FAILURE_CODES = [
113
- "baseline-deletion-drift",
114
- "baseline-directory-drift",
115
- "baseline-file-drift",
116
- "git-apply-check-failed",
117
- "stale-snapshot",
118
- ] as const;
119
- export const ENVIRONMENT_FAILURE_CODES = [
120
- "child-provider-authentication-failed",
121
- "bubblewrap-launch-failed",
122
- "bubblewrap-or-dependency-unavailable",
123
- "bun-executable-unavailable",
124
- "checkpoint-unavailable",
125
- "checkout-cleanup-failed",
126
- "checkout-failed",
127
- "clone-alternates",
128
- "clone-failed",
129
- "dependency-path-unsafe",
130
- "git-apply-check-unavailable",
131
- "git-apply-failed",
132
- "git-apply-unavailable",
133
- "git-ignore-check-failed",
134
- "git-index-unavailable",
135
- "invalid-subagent-endpoint",
136
- "root-unavailable",
137
- "sandbox-runtime-unavailable",
138
- ] as const;
139
- export const APPROVAL_BOUNDARY_CODES = [
140
- "unapproved-dependency-change",
141
- "behavior-contract-insufficient",
142
- "architecture-contract-insufficient",
143
- "task-scope-insufficient",
144
- "conflict-resource-authority-insufficient",
145
- "verification-contract-insufficient",
146
- "agents-contract-insufficient",
147
- "irreversible-scope-insufficient",
148
- ] as const;
149
- export const VERIFICATION_ADAPTER_CODES = [
150
- "input-missing",
151
- "input-unsafe",
152
- "local-executable-missing",
153
- "runner-missing",
154
- "script-command-mismatch",
155
- "script-missing",
156
- "script-unsafe",
157
- "report-arguments-conflict",
158
- "verification-config-mismatch",
159
- "verification-config-unsafe",
160
- ] as const;
161
- export const CHILD_TRANSPORT_CODES = [
162
- "child-provider-rate-limited",
163
- "first-progress-timeout",
164
- "stream-idle-timeout",
165
- "attempt-timeout",
166
- "child-no-final-assistant",
167
- "child-provider-stream-aborted",
168
- "child-provider-stream-error",
169
- "child-timeout",
170
- "timeout",
171
- "transport-failure",
172
- ] as const;
173
- export const FAILURE_STAGES = [
174
- "child-session-create",
175
- "child-provider-stream",
176
- "child-timeout",
177
- "child-finalization",
178
- "structural-submit",
179
- "candidate-retention",
180
- "candidate-diff",
181
- "parent-review",
182
- "candidate-apply",
183
- "agents-checkpoint",
184
- "phase-runtime",
185
- ] as const;
186
-
187
- export const SUBMIT_FINAL_CATEGORIES = [
188
- "no-final-assistant",
189
- "text-only",
190
- "mixed",
191
- "multiple-submit",
192
- "single-submit-only",
193
- ] as const;
194
- export const SUBMIT_SCHEMA_STATES = [
195
- "not-submitted",
196
- "valid",
197
- "invalid",
198
- ] as const;
199
- export const IDENTITY_DIMENSIONS = [
200
- "request",
201
- "role",
202
- "task",
203
- "phase",
204
- ] as const;
205
-
206
- export type ArtifactFailureCode = (typeof ARTIFACT_FAILURE_CODES)[number];
207
- export type StaleFailureCode = (typeof STALE_FAILURE_CODES)[number];
208
- export type EnvironmentFailureCode = (typeof ENVIRONMENT_FAILURE_CODES)[number];
209
- export type ApprovalBoundaryCode = (typeof APPROVAL_BOUNDARY_CODES)[number];
210
- export type VerificationAdapterCode =
211
- (typeof VERIFICATION_ADAPTER_CODES)[number];
212
- export type ChildTransportCode = (typeof CHILD_TRANSPORT_CODES)[number];
213
- export type FailureStage = (typeof FAILURE_STAGES)[number];
214
- export type SubmitFinalCategory = (typeof SUBMIT_FINAL_CATEGORIES)[number];
215
- export type SubmitSchemaState = (typeof SUBMIT_SCHEMA_STATES)[number];
216
- export type IdentityDimension = (typeof IDENTITY_DIMENSIONS)[number];
217
-
218
- export interface SafeFailureDetails {
219
- finalCategory?: SubmitFinalCategory;
220
- submitAttempts?: number;
221
- schema?: SubmitSchemaState;
222
- identityMismatch?: IdentityDimension[];
223
- verificationId?: string;
224
- }
225
-
226
- export type EnvironmentFailure = {
227
- kind: "environment";
228
- code: EnvironmentFailureCode;
229
- message?: string;
230
- stage?: FailureStage;
231
- };
232
-
233
- export type CandidateFailure =
234
- | {
235
- kind: "artifact";
236
- code: ArtifactFailureCode;
237
- stage: FailureStage;
238
- details?: SafeFailureDetails;
239
- }
240
- | { kind: "stale"; code: StaleFailureCode; stage: FailureStage }
241
- | EnvironmentFailure
242
- | { kind: "verification-adapter"; code: VerificationAdapterCode }
243
- | { kind: "approval-boundary"; code: ApprovalBoundaryCode }
244
- | { kind: "cancelled"; code: "cancelled" }
245
- | { kind: "result-limit"; limitBytes: number };
246
-
247
- export type ChildFailure =
248
- | {
249
- kind: "execution-limit";
250
- code: "child-turn-limit" | "child-context-limit";
251
- }
252
- | CandidateFailure
253
- | {
254
- kind: "transport";
255
- code: ChildTransportCode;
256
- stage: FailureStage;
257
- details?: SafeFailureDetails;
258
- };
259
-
260
- const NONCANONICAL = /(^|\/)\.\.(\/|$)|(^|\/)\/|^\//;
261
-
262
- export type VerificationClassification =
263
- | "expected-red"
264
- | "expected-green"
265
- | "expected-refactor";
266
-
267
- export interface VerificationFailureSummary {
268
- verificationId: string;
269
- code: string;
270
- exitCode?: number;
271
- failures: string[];
272
- stdout: string;
273
- stderr: string;
274
- truncated: boolean;
275
- nextStep: string;
276
- }
277
-
278
- /** Runtime-owned evidence; unavailable checks never become product baselines. */
279
- export interface VerificationEvidence {
280
- id: string;
281
- exitCode: number;
282
- classification: VerificationClassification;
283
- tests?: number;
284
- failureIdentities: string[];
285
- policy: "report-file-v3";
286
- attributionReliable?: boolean;
287
- }
288
- export type VerificationObservation =
289
- | { kind: "accepted"; evidence: VerificationEvidence }
290
- | {
291
- kind: "rejected";
292
- code: "red-not-witnessed" | "verification-rejected";
293
- evidence: VerificationEvidence;
294
- diagnostic?: VerificationFailureSummary;
295
- }
296
- | {
297
- kind: "unavailable";
298
- category: "environment" | "adapter" | "resource";
299
- code: string;
300
- verificationId: string;
301
- diagnostic?: VerificationFailureSummary;
302
- inputObservation?: VerificationInputObservation;
303
- }
304
- | { kind: "cancelled" };
305
-
306
- /** Parent adapter observation, never a Worker assertion or an error-text guess. */
307
- export interface VerificationInputObservation {
308
- path: string;
309
- kind: "absent" | "unsafe";
310
- }
311
-
312
- interface VerificationBase {
313
- id: string;
314
- classification: VerificationClassification;
315
- expectedFailure?: string;
316
- executionBindings?: Record<string, string | null>;
317
- }
318
-
319
- export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
320
-
321
- export interface PackageScriptRunner {
322
- kind: "package-script";
323
- packageManager: PackageManager;
324
- script: string;
325
- command: string;
326
- }
327
-
328
- export type ExecutableRunner =
329
- | PackageScriptRunner
330
- | { kind: "local-binary"; executable: string }
331
- | { kind: "npx"; executable: string; noInstall: true };
332
-
333
- export interface VitestVerificationContract extends VerificationBase {
334
- kind: "vitest";
335
- runner: ExecutableRunner;
336
- testFiles: string[];
337
- args: string[];
338
- minTests: number;
339
- }
340
-
341
- export interface PackageScriptVerificationContract extends VerificationBase {
342
- kind: "package-script";
343
- packageManager: PackageManager;
344
- script: string;
345
- command: string;
346
- args: string[];
347
- }
348
-
349
- export interface StaticCheckVerificationContract extends VerificationBase {
350
- kind: "static-check";
351
- runner: ExecutableRunner | { kind: "node"; script: string };
352
- args: string[];
353
- }
354
-
355
- export type AtomicVerificationContract =
356
- | VitestVerificationContract
357
- | PackageScriptVerificationContract
358
- | StaticCheckVerificationContract;
359
-
360
- export interface VerificationStepsContract {
361
- kind: "steps";
362
- id: string;
363
- classification: VerificationClassification;
364
- steps: AtomicVerificationContract[];
365
- }
366
-
367
- export type StructuredVerificationContract =
368
- | AtomicVerificationContract
369
- | VerificationStepsContract;
370
-
371
- export type VerificationContract = StructuredVerificationContract;
372
-
373
- export interface ImpactClosureContract {
374
- changedSurfaces: ImpactSurface[];
375
- searchEvidence: string[];
376
- relatedTests: Array<{
377
- path: string;
378
- disposition: "current-task" | "regression-task" | "unaffected";
379
- evidence: string;
380
- regressionTaskId?: string;
381
- }>;
382
- affectedSuite: string[];
383
- }
384
-
385
- export const IMPLEMENTATION_PHASES = ["red", "green", "refactor"] as const;
386
- export type ImplementationPhase = (typeof IMPLEMENTATION_PHASES)[number];
387
-
388
- export type VerificationInputBinding =
389
- | { kind: "workspace"; path: string }
390
- | { kind: "output"; outputId: string };
391
-
392
- export interface PhaseBoundary {
393
- read: string[];
394
- write: string[];
395
- delete: string[];
396
- verification: VerificationContract;
397
- verificationInputs: VerificationInputBinding[];
398
- verificationLock?: string;
399
- }
400
-
401
- export interface TaskBoundary {
402
- verificationMode?: "behavior" | "mechanical" | "refactor";
403
- changeId: string;
404
- taskId: string;
405
- dependsOn: string[];
406
- objective: string;
407
- context: { agents: string; contract: string };
408
- roots: string[];
409
- phases: {
410
- red: PhaseBoundary;
411
- green: PhaseBoundary;
412
- refactor?: PhaseBoundary;
413
- };
414
- scheduling: { conflicts: string[]; resources: string[] };
415
- agents: {
416
- impact: AgentsImpact;
417
- target?: string;
418
- managedOnly: true;
419
- };
420
- approvedDependencies: string[];
421
- impactClosure: ImpactClosureContract;
422
- }
423
-
424
- export interface ImplementGraphOutput {
425
- id: string;
426
- path: string;
427
- producer: { taskId: string; phase: ImplementationPhase };
428
- postcondition: "regular-file";
429
- }
430
-
431
- export type ImplementTaskBoundary = Omit<TaskBoundary, "changeId">;
432
-
433
- export interface ImplementGraphBoundary {
434
- changeId: string;
435
- tasks: ImplementTaskBoundary[];
436
- outputs: ImplementGraphOutput[];
437
- }
438
-
439
- export const IMPLEMENT_GRAPH_READINESS_CODES = [
440
- "dependency-blocked",
441
- "dependency-cycle",
442
- "duplicate-output-id",
443
- "duplicate-task-id",
444
- "graph-hash-mismatch",
445
- "invalid-implement-graph",
446
- "invalid-output-path",
447
- "multiple-output-producers",
448
- "output-not-declared",
449
- "output-producer-invalid",
450
- "output-write-not-approved",
451
- "phase-blocked",
452
- "producer-not-dependency",
453
- "producer-output-unavailable",
454
- "producer-phase-after-consumer",
455
- "unknown-dependency",
456
- "verification-input-binding-mismatch",
457
- "workspace-input-has-producer",
458
- "workspace-input-unavailable",
459
- ] as const;
460
-
461
- export type ImplementGraphReadinessCode =
462
- (typeof IMPLEMENT_GRAPH_READINESS_CODES)[number];
463
-
464
- type ImplementGraphDiagnosticDetails = Partial<{
465
- taskId: string;
466
- phase: ImplementationPhase;
467
- verificationId: string;
468
- outputId: string;
469
- path: string;
470
- producerTaskId: string;
471
- producerPhase: ImplementationPhase;
472
- dependencyTaskId: string;
473
- field: string;
474
- expectedPaths: string[];
475
- actualPaths: string[];
476
- }>;
477
-
478
- export type ImplementGraphReadinessDiagnostic =
479
- | ({
480
- kind: "graph-readiness";
481
- code: ImplementGraphReadinessCode;
482
- } & ImplementGraphDiagnosticDetails)
483
- | ({
484
- kind: "verification-adapter";
485
- code: VerificationAdapterCode;
486
- taskId: string;
487
- phase: ImplementationPhase;
488
- verificationId: string;
489
- } & ImplementGraphDiagnosticDetails)
490
- | ({
491
- kind: "design-readiness";
492
- code: "verification-contract-unsupported";
493
- taskId: string;
494
- phase: ImplementationPhase;
495
- verificationId?: string;
496
- } & ImplementGraphDiagnosticDetails);
497
-
498
- export interface PacketEnvelope {
499
- stage: "abel-design" | "abel-diagnose";
500
- role: "design-explorer" | "diagnosis-worker";
501
- runId?: string;
502
- id: string;
503
- phase: string;
504
- objective: string;
505
- roots: string[];
506
- context: { agents: string; contract: string };
507
- declared: {
508
- read: string[];
509
- write: string[];
510
- conflicts: string[];
511
- resources: string[];
512
- verificationLock?: string;
513
- };
514
- output: "evidence" | "diff";
515
- }
516
-
517
- // JSON-schema counterpart for model-visible path arguments; keep its behavior
518
- // checked against isValidRelativePath rather than hiding constraints in prose.
519
- export const RELATIVE_PATH_PATTERN = String.raw`^(?:\.|(?!/)(?![a-zA-Z]:/)(?!\./)(?![\s\S]*\.\.)(?![\s\S]*[\\\u0000])(?![\s\S]*//)(?![\s\S]*/\.(?:/|$))(?![\s\S]*/$)[\s\S]+)$`;
520
-
521
- export function isValidRelativePath(p: unknown): p is string {
522
- return (
523
- typeof p === "string" &&
524
- p.length > 0 &&
525
- p.length <= 512 &&
526
- !NONCANONICAL.test(p) &&
527
- !p.startsWith("/") &&
528
- !/^[a-z]:\//iu.test(p) &&
529
- (p === "." ||
530
- (!p.startsWith("./") &&
531
- !p.endsWith("/") &&
532
- !p.split("/").includes("."))) &&
533
- !p.includes("..") &&
534
- !p.includes("\\") &&
535
- !p.includes("\u0000")
536
- );
537
- }
538
-
539
- export function isAgentsPath(p: unknown): p is string {
540
- return isValidRelativePath(p) && /(?:^|\/)AGENTS\.md$/u.test(p);
541
- }
542
-
543
- const UNSAFE_VERIFICATION_TOKEN = /[;&|`$<>\n\r\0]/u;
544
- const VERIFICATION_NAME = /^[a-z0-9][a-z0-9._:@/-]*$/iu;
545
- const EXECUTABLE_NAME = /^[a-z0-9][a-z0-9._-]*$/iu;
546
- const IDENTIFIER = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
547
-
548
- function validVerificationToken(value: unknown): value is string {
549
- const optionValue =
550
- typeof value === "string" && value.includes("=")
551
- ? value.slice(value.indexOf("=") + 1)
552
- : value;
553
- return (
554
- typeof value === "string" &&
555
- value.length > 0 &&
556
- value.length <= 512 &&
557
- value.trim() === value &&
558
- !UNSAFE_VERIFICATION_TOKEN.test(value) &&
559
- !value.includes("\\") &&
560
- !value.startsWith("/") &&
561
- !/^[a-z]:\//iu.test(value) &&
562
- !/(^|\/)\.\.(\/|$)/u.test(value) &&
563
- typeof optionValue === "string" &&
564
- !optionValue.startsWith("/") &&
565
- !/^[a-z]:\//iu.test(optionValue) &&
566
- !/(^|\/)\.\.(\/|$)/u.test(optionValue)
567
- );
568
- }
569
-
570
- function validVerificationArgs(value: unknown): value is string[] {
571
- return (
572
- Array.isArray(value) &&
573
- value.length <= 128 &&
574
- value.every(validVerificationToken)
575
- );
576
- }
577
-
578
- function validScriptCommand(value: unknown): value is string {
579
- return (
580
- typeof value === "string" &&
581
- value.length > 0 &&
582
- value.length <= 1024 &&
583
- value.trim() === value &&
584
- !value.includes("\0")
585
- );
586
- }
587
-
588
- function validVerificationIdentity(contract: Record<string, unknown>): boolean {
589
- if (
590
- !validIdentifier(contract.id) ||
591
- !["expected-red", "expected-green", "expected-refactor"].includes(
592
- String(contract.classification),
593
- )
594
- ) {
595
- return false;
596
- }
597
- return contract.classification === "expected-red"
598
- ? typeof contract.expectedFailure === "string" &&
599
- contract.expectedFailure.length > 0 &&
600
- contract.expectedFailure.length <= 512
601
- : contract.expectedFailure === undefined;
602
- }
603
-
604
- function validatePackageScriptRunner(
605
- value: unknown,
606
- ): value is PackageScriptRunner {
607
- if (
608
- !hasExactKeys(value, ["kind", "packageManager", "script", "command"]) ||
609
- value.kind !== "package-script" ||
610
- !["bun", "npm", "pnpm", "yarn"].includes(String(value.packageManager)) ||
611
- typeof value.script !== "string" ||
612
- !VERIFICATION_NAME.test(value.script) ||
613
- value.script.includes("/") ||
614
- !validScriptCommand(value.command)
615
- ) {
616
- return false;
617
- }
618
- return true;
619
- }
620
-
621
- function validateExecutableRunner(value: unknown): value is ExecutableRunner {
622
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
623
- const runner = value as Record<string, unknown>;
624
- if (runner.kind === "package-script") {
625
- return validatePackageScriptRunner(runner);
626
- }
627
- if (
628
- runner.kind === "local-binary" &&
629
- hasExactKeys(runner, ["kind", "executable"])
630
- ) {
631
- return (
632
- typeof runner.executable === "string" &&
633
- EXECUTABLE_NAME.test(runner.executable)
634
- );
635
- }
636
- return (
637
- runner.kind === "npx" &&
638
- hasExactKeys(runner, ["kind", "executable", "noInstall"]) &&
639
- typeof runner.executable === "string" &&
640
- EXECUTABLE_NAME.test(runner.executable) &&
641
- runner.noInstall === true
642
- );
643
- }
644
-
645
- function validateVitestRunner(value: ExecutableRunner): boolean {
646
- if (value.kind === "package-script") {
647
- if (UNSAFE_VERIFICATION_TOKEN.test(value.command)) return false;
648
- const tokens = value.command.split(/\s+/u);
649
- return (
650
- tokens[0] === "vitest" ||
651
- (tokens[0] === "npx" &&
652
- tokens[1] === "--no-install" &&
653
- tokens[2] === "vitest")
654
- );
655
- }
656
- return value.executable === "vitest";
657
- }
658
-
659
- function validateAtomicVerification(
660
- value: unknown,
661
- ): value is AtomicVerificationContract {
662
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
663
- const contract = value as Record<string, unknown>;
664
- if (
665
- contract.executionBindings !== undefined &&
666
- (!contract.executionBindings ||
667
- typeof contract.executionBindings !== "object" ||
668
- Array.isArray(contract.executionBindings) ||
669
- Object.entries(contract.executionBindings).some(
670
- ([key, hash]) =>
671
- !VERIFICATION_CONFIGURATION_PATHS.includes(key) ||
672
- (hash !== null &&
673
- (typeof hash !== "string" || !/^[a-f0-9]{64}$/u.test(hash))),
674
- ))
675
- )
676
- return false;
677
- const expectedFailure = [
678
- ...(contract.classification === "expected-red" ? ["expectedFailure"] : []),
679
- ...(contract.executionBindings === undefined ? [] : ["executionBindings"]),
680
- ];
681
- if (contract.kind === "vitest") {
682
- const executableRunner = contract.runner as ExecutableRunner;
683
- return (
684
- hasExactKeys(contract, [
685
- "kind",
686
- "id",
687
- "runner",
688
- "testFiles",
689
- "args",
690
- "classification",
691
- ...expectedFailure,
692
- "minTests",
693
- ]) &&
694
- validVerificationIdentity(contract) &&
695
- validateExecutableRunner(contract.runner) &&
696
- validateVitestRunner(executableRunner) &&
697
- Array.isArray(contract.testFiles) &&
698
- contract.testFiles.length >= VERIFICATION_LIMITS.minTestFiles &&
699
- contract.testFiles.length <= VERIFICATION_LIMITS.maxTestFiles &&
700
- contract.testFiles.every(isValidRelativePath) &&
701
- new Set(contract.testFiles).size === contract.testFiles.length &&
702
- validVerificationArgs(contract.args) &&
703
- Number.isSafeInteger(contract.minTests) &&
704
- (contract.minTests as number) >= 1
705
- );
706
- }
707
- if (contract.kind === "package-script") {
708
- const runner = {
709
- kind: "package-script",
710
- packageManager: contract.packageManager,
711
- script: contract.script,
712
- command: contract.command,
713
- };
714
- return (
715
- hasExactKeys(contract, [
716
- "kind",
717
- "id",
718
- "packageManager",
719
- "script",
720
- "command",
721
- "args",
722
- "classification",
723
- ...expectedFailure,
724
- ]) &&
725
- validVerificationIdentity(contract) &&
726
- validatePackageScriptRunner(runner) &&
727
- validVerificationArgs(contract.args)
728
- );
729
- }
730
- if (contract.kind !== "static-check") return false;
731
- const runner = contract.runner as Record<string, unknown> | undefined;
732
- const validRunner =
733
- validateExecutableRunner(runner) ||
734
- (hasExactKeys(runner, ["kind", "script"]) &&
735
- runner.kind === "node" &&
736
- isValidRelativePath(runner.script));
737
- return (
738
- hasExactKeys(contract, [
739
- "kind",
740
- "id",
741
- "runner",
742
- "args",
743
- "classification",
744
- ...expectedFailure,
745
- ]) &&
746
- validVerificationIdentity(contract) &&
747
- validRunner &&
748
- validVerificationArgs(contract.args)
749
- );
750
- }
751
-
752
- function validateStructuredVerification(
753
- value: unknown,
754
- ): value is StructuredVerificationContract {
755
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
756
- const contract = value as Record<string, unknown>;
757
- if (contract.kind !== "steps") {
758
- return validateAtomicVerification(contract);
759
- }
760
- if (
761
- !hasExactKeys(contract, ["kind", "id", "classification", "steps"]) ||
762
- !validIdentifier(contract.id) ||
763
- !["expected-red", "expected-green", "expected-refactor"].includes(
764
- String(contract.classification),
765
- ) ||
766
- !Array.isArray(contract.steps) ||
767
- contract.steps.length < VERIFICATION_LIMITS.minSteps ||
768
- contract.steps.length > VERIFICATION_LIMITS.maxSteps ||
769
- !contract.steps.every((step) => validateAtomicVerification(step))
770
- ) {
771
- return false;
772
- }
773
- const steps = contract.steps as AtomicVerificationContract[];
774
- return (
775
- steps
776
- .slice(0, -1)
777
- .every((step) => step.classification === "expected-green") &&
778
- steps.at(-1)?.classification === contract.classification &&
779
- new Set(steps.map((step) => step.id)).size === steps.length
780
- );
781
- }
782
-
783
- export function validateVerificationContract(
784
- value: unknown,
785
- ):
786
- | { ok: true; value: StructuredVerificationContract }
787
- | { ok: false; reason: string } {
788
- if (validateStructuredVerification(value)) {
789
- return { ok: true, value: structuredClone(value) };
790
- }
791
- return {
792
- ok: false,
793
- reason: "verification contract is unsupported by the Implement runtime",
794
- };
795
- }
796
-
797
- export function verificationSteps(
798
- verification: VerificationContract,
799
- ): AtomicVerificationContract[] {
800
- const normalized = validateVerificationContract(verification);
801
- if (!normalized.ok) return [];
802
- return normalized.value.kind === "steps"
803
- ? structuredClone(normalized.value.steps)
804
- : [structuredClone(normalized.value)];
805
- }
806
-
807
- export const VERIFICATION_CONFIGURATION_PATHS: readonly string[] = [
808
- "package.json",
809
- "package-lock.json",
810
- "npm-shrinkwrap.json",
811
- "bun.lock",
812
- "bun.lockb",
813
- "pnpm-lock.yaml",
814
- "yarn.lock",
815
- ".npmrc",
816
- ".yarnrc",
817
- ".yarnrc.yml",
818
- "bunfig.toml",
819
- "pnpm-workspace.yaml",
820
- ];
821
-
822
- export function verificationBoundInputPaths(
823
- verification: VerificationContract,
824
- ): string[] {
825
- return [
826
- ...new Set([
827
- ...verificationInputPaths(verification),
828
- ...verificationSteps(verification).flatMap((step) =>
829
- step.kind === "vitest" ||
830
- step.kind === "package-script" ||
831
- ("runner" in step && step.runner.kind === "package-script")
832
- ? VERIFICATION_CONFIGURATION_PATHS
833
- : [],
834
- ),
835
- ]),
836
- ];
837
- }
838
-
839
- export function verificationInputPaths(
840
- verification: VerificationContract,
841
- ): string[] {
842
- return [
843
- ...new Set(
844
- verificationSteps(verification).flatMap((step) => {
845
- const paths =
846
- step.kind === "vitest"
847
- ? [...step.testFiles]
848
- : step.kind === "static-check" && step.runner.kind === "node"
849
- ? [step.runner.script]
850
- : [];
851
- if (
852
- step.kind === "package-script" ||
853
- ("runner" in step && step.runner.kind === "package-script")
854
- ) {
855
- paths.push("package.json");
856
- }
857
- return paths;
858
- }),
859
- ),
860
- ];
861
- }
862
-
863
- export function cloneVerificationContract(
864
- verification: VerificationContract,
865
- ): StructuredVerificationContract {
866
- const normalized = validateVerificationContract(verification);
867
- if (!normalized.ok) throw new Error(normalized.reason);
868
- return structuredClone(normalized.value);
869
- }
870
-
871
- function validateApprovedDependencies(value: unknown): string | null {
872
- if (
873
- !Array.isArray(value) ||
874
- value.some(
875
- (dependency) =>
876
- typeof dependency !== "string" ||
877
- !/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/iu.test(
878
- dependency,
879
- ),
880
- ) ||
881
- new Set(value).size !== value.length
882
- ) {
883
- return "invalid approved dependencies";
884
- }
885
- return null;
886
- }
887
-
888
- function validateImpactClosure(
889
- value: unknown,
890
- declared: Record<string, unknown>,
891
- snapshot: unknown,
892
- ): string | null {
893
- if (
894
- !hasExactKeys(value, [
895
- "changedSurfaces",
896
- "searchEvidence",
897
- "relatedTests",
898
- "affectedSuite",
899
- ])
900
- ) {
901
- return "invalid impact closure";
902
- }
903
- const closure = value as Record<string, unknown>;
904
- const surfaces = closure.changedSurfaces;
905
- const searchEvidence = closure.searchEvidence;
906
- const relatedTests = closure.relatedTests;
907
- const affectedSuite = closure.affectedSuite;
908
- if (
909
- !Array.isArray(surfaces) ||
910
- surfaces.length === 0 ||
911
- surfaces.some(
912
- (surface) => !(IMPACT_SURFACES as readonly unknown[]).includes(surface),
913
- ) ||
914
- new Set(surfaces).size !== surfaces.length ||
915
- (surfaces.includes("none") && surfaces.length !== 1) ||
916
- !Array.isArray(searchEvidence) ||
917
- searchEvidence.some(
918
- (evidence) => typeof evidence !== "string" || evidence.length === 0,
919
- ) ||
920
- !Array.isArray(relatedTests) ||
921
- !Array.isArray(affectedSuite) ||
922
- affectedSuite.some(
923
- (test) => !isValidRelativePath(test) || !isTestPath(test),
924
- ) ||
925
- new Set(affectedSuite).size !== affectedSuite.length
926
- ) {
927
- return "invalid impact closure";
928
- }
929
-
930
- const scope = new Set([
931
- ...((declared.read as string[]) ?? []),
932
- ...((declared.write as string[]) ?? []),
933
- ]);
934
- const writes = new Set((declared.write as string[]) ?? []);
935
- const relatedPaths = new Set<string>();
936
- for (const item of relatedTests) {
937
- if (!item || typeof item !== "object" || Array.isArray(item)) {
938
- return "invalid impact closure related test";
939
- }
940
- const related = item as Record<string, unknown>;
941
- const relatedKeys =
942
- related.disposition === "regression-task"
943
- ? ["path", "disposition", "evidence", "regressionTaskId"]
944
- : ["path", "disposition", "evidence"];
945
- if (
946
- !hasExactKeys(related, relatedKeys) ||
947
- !isValidRelativePath(related.path) ||
948
- !isTestPath(related.path) ||
949
- relatedPaths.has(related.path) ||
950
- !scope.has(related.path) ||
951
- !["current-task", "regression-task", "unaffected"].includes(
952
- String(related.disposition),
953
- ) ||
954
- typeof related.evidence !== "string" ||
955
- related.evidence.length === 0
956
- ) {
957
- return "invalid impact closure related test";
958
- }
959
- relatedPaths.add(related.path);
960
- if (related.disposition === "current-task" && !writes.has(related.path)) {
961
- return "current-task related test is outside the write set";
962
- }
963
- if (
964
- related.disposition === "regression-task" &&
965
- (typeof related.regressionTaskId !== "string" ||
966
- related.regressionTaskId.length === 0)
967
- ) {
968
- return "regression task identity is required";
969
- }
970
- if (
971
- related.disposition !== "regression-task" &&
972
- related.regressionTaskId !== undefined
973
- ) {
974
- return "unexpected regression task identity";
975
- }
976
- }
977
-
978
- if (surfaces[0] === "none") return null;
979
- if (
980
- searchEvidence.length === 0 ||
981
- relatedTests.length === 0 ||
982
- affectedSuite.length === 0 ||
983
- affectedSuite.some((test) => !relatedPaths.has(test))
984
- ) {
985
- return "public impact closure is incomplete";
986
- }
987
- const bounds = snapshot as Record<string, { kind?: unknown }> | undefined;
988
- if (
989
- bounds !== undefined &&
990
- !affectedSuite.some((test) => bounds[test]?.kind === "file")
991
- ) {
992
- return "affected suite contains no existing test evidence";
993
- }
994
- return null;
995
- }
996
-
997
- function isTestPath(path: string): boolean {
998
- return /^(?:test|tests)\//u.test(path);
999
- }
1000
-
1001
- function hasExactKeys(
1002
- value: unknown,
1003
- required: readonly string[],
1004
- optional: readonly string[] = [],
1005
- ): value is Record<string, unknown> {
1006
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1007
- const record = value as Record<string, unknown>;
1008
- const allowed = new Set([...required, ...optional]);
1009
- return (
1010
- required.every((key) => Object.hasOwn(record, key)) &&
1011
- Object.keys(record).every((key) => allowed.has(key))
1012
- );
1013
- }
1014
-
1015
- function validIdentifier(value: unknown): value is string {
1016
- return typeof value === "string" && IDENTIFIER.test(value);
1017
- }
1018
-
1019
- function validatePathSet(value: unknown): value is string[] {
1020
- return (
1021
- Array.isArray(value) &&
1022
- value.every(isValidRelativePath) &&
1023
- new Set(value).size === value.length
1024
- );
1025
- }
1026
-
1027
- function validateIdentifierSet(value: unknown): value is string[] {
1028
- return (
1029
- Array.isArray(value) &&
1030
- value.every(validIdentifier) &&
1031
- new Set(value).size === value.length
1032
- );
1033
- }
1034
-
1035
- function validateVerificationInputBindings(
1036
- value: unknown,
1037
- ): value is VerificationInputBinding[] {
1038
- if (!Array.isArray(value)) return false;
1039
- const keys = new Set<string>();
1040
- for (const binding of value) {
1041
- if (
1042
- binding &&
1043
- typeof binding === "object" &&
1044
- !Array.isArray(binding) &&
1045
- (binding as { kind?: unknown }).kind === "workspace" &&
1046
- hasExactKeys(binding, ["kind", "path"]) &&
1047
- isValidRelativePath(binding.path)
1048
- ) {
1049
- if (keys.has(`workspace:${binding.path}`)) return false;
1050
- keys.add(`workspace:${binding.path}`);
1051
- continue;
1052
- }
1053
- if (
1054
- binding &&
1055
- typeof binding === "object" &&
1056
- !Array.isArray(binding) &&
1057
- (binding as { kind?: unknown }).kind === "output" &&
1058
- hasExactKeys(binding, ["kind", "outputId"]) &&
1059
- validIdentifier(binding.outputId)
1060
- ) {
1061
- if (keys.has(`output:${binding.outputId}`)) return false;
1062
- keys.add(`output:${binding.outputId}`);
1063
- continue;
1064
- }
1065
- return false;
1066
- }
1067
- return true;
1068
- }
1069
-
1070
- function validateRoots(value: unknown): value is string[] {
1071
- if (!validatePathSet(value) || value.length === 0) return false;
1072
- if (
1073
- value.some(
1074
- (root) => (root !== "." && root.startsWith("./")) || root.endsWith("/"),
1075
- )
1076
- ) {
1077
- return false;
1078
- }
1079
- return !value.some((root, index) =>
1080
- value.some(
1081
- (other, otherIndex) =>
1082
- index !== otherIndex && (root === "." || other.startsWith(`${root}/`)),
1083
- ),
1084
- );
1085
- }
1086
-
1087
- function isWithinRoots(path: string, roots: string[]): boolean {
1088
- return roots.some(
1089
- (root) => root === "." || path === root || path.startsWith(`${root}/`),
1090
- );
1091
- }
1092
-
1093
- function validatePhaseBoundary(
1094
- value: unknown,
1095
- phase: ImplementationPhase,
1096
- mode: unknown = "behavior",
1097
- ): value is PhaseBoundary {
1098
- if (
1099
- !hasExactKeys(
1100
- value,
1101
- ["read", "write", "delete", "verification", "verificationInputs"],
1102
- ["verificationLock"],
1103
- )
1104
- ) {
1105
- return false;
1106
- }
1107
- if (
1108
- !validatePathSet(value.read) ||
1109
- !validatePathSet(value.write) ||
1110
- !validatePathSet(value.delete) ||
1111
- !validateVerificationInputBindings(value.verificationInputs) ||
1112
- value.write.some(isAgentsPath) ||
1113
- value.delete.some(isAgentsPath) ||
1114
- value.write.some((path) => (value.delete as string[]).includes(path)) ||
1115
- (value.verificationLock !== undefined &&
1116
- !isValidRelativePath(value.verificationLock))
1117
- ) {
1118
- return false;
1119
- }
1120
- const verification = validateVerificationContract(value.verification);
1121
- if (!verification.ok) return false;
1122
- const classification = {
1123
- red:
1124
- mode !== undefined && mode !== "behavior"
1125
- ? "expected-green"
1126
- : "expected-red",
1127
- green: "expected-green",
1128
- refactor: "expected-refactor",
1129
- } as const;
1130
- return verification.value.classification === classification[phase];
1131
- }
1132
-
1133
- function validateTaskBoundary(
1134
- value: unknown,
1135
- snapshot: unknown,
1136
- ): value is TaskBoundary {
1137
- if (
1138
- !hasExactKeys(
1139
- value,
1140
- [
1141
- "changeId",
1142
- "taskId",
1143
- "dependsOn",
1144
- "objective",
1145
- "context",
1146
- "roots",
1147
- "phases",
1148
- "scheduling",
1149
- "agents",
1150
- "approvedDependencies",
1151
- "impactClosure",
1152
- ],
1153
- ["verificationMode"],
1154
- ) ||
1155
- (value.verificationMode !== undefined &&
1156
- !["behavior", "mechanical", "refactor"].includes(
1157
- String(value.verificationMode),
1158
- )) ||
1159
- !validIdentifier(value.changeId) ||
1160
- !validIdentifier(value.taskId) ||
1161
- !validateIdentifierSet(value.dependsOn) ||
1162
- typeof value.objective !== "string" ||
1163
- value.objective.length === 0 ||
1164
- value.objective.length > 4096 ||
1165
- !hasExactKeys(value.context, ["agents", "contract"]) ||
1166
- typeof value.context.agents !== "string" ||
1167
- typeof value.context.contract !== "string" ||
1168
- !validateRoots(value.roots) ||
1169
- !hasExactKeys(value.phases, ["red", "green"], ["refactor"]) ||
1170
- !validatePhaseBoundary(value.phases.red, "red", value.verificationMode) ||
1171
- !validatePhaseBoundary(value.phases.green, "green") ||
1172
- (value.phases.refactor !== undefined &&
1173
- !validatePhaseBoundary(value.phases.refactor, "refactor")) ||
1174
- !hasExactKeys(value.scheduling, ["conflicts", "resources"]) ||
1175
- !validatePathSet(value.scheduling.conflicts) ||
1176
- !validatePathSet(value.scheduling.resources) ||
1177
- !hasExactKeys(value.agents, ["impact", "managedOnly"], ["target"]) ||
1178
- !(AGENTS_IMPACTS as readonly unknown[]).includes(value.agents.impact) ||
1179
- value.agents.managedOnly !== true
1180
- ) {
1181
- return false;
1182
- }
1183
- if (
1184
- value.agents.impact === "none"
1185
- ? value.agents.target !== undefined
1186
- : !isAgentsPath(value.agents.target)
1187
- ) {
1188
- return false;
1189
- }
1190
- if (validateApprovedDependencies(value.approvedDependencies) !== null) {
1191
- return false;
1192
- }
1193
- const phases = [
1194
- value.phases.red,
1195
- value.phases.green,
1196
- ...(value.phases.refactor ? [value.phases.refactor] : []),
1197
- ];
1198
- const roots = value.roots as string[];
1199
- if (
1200
- phases.some((phase) =>
1201
- [
1202
- ...phase.read,
1203
- ...phase.write,
1204
- ...phase.delete,
1205
- ...verificationInputPaths(phase.verification),
1206
- ].some((path) => !isWithinRoots(path, roots)),
1207
- )
1208
- ) {
1209
- return false;
1210
- }
1211
- if (
1212
- phases.some((phase) => {
1213
- if (phase.verification.kind === undefined) return false;
1214
- const declared = new Set([...phase.read, ...phase.write]);
1215
- return verificationInputPaths(phase.verification).some(
1216
- (input) => !declared.has(input),
1217
- );
1218
- })
1219
- ) {
1220
- return false;
1221
- }
1222
- const declared = {
1223
- read: [...new Set(phases.flatMap((entry) => entry.read))],
1224
- write: [
1225
- ...new Set(phases.flatMap((entry) => [...entry.write, ...entry.delete])),
1226
- ],
1227
- };
1228
- return (
1229
- validateImpactClosure(value.impactClosure, declared, snapshot) === null
1230
- );
1231
- }
1232
-
1233
- export type ImplementGraphContractDiagnostic =
1234
- | {
1235
- code: "invalid-implement-graph";
1236
- taskId?: string;
1237
- phase?: ImplementationPhase;
1238
- field?: string;
1239
- category?: string;
1240
- outputId?: string;
1241
- path?: string;
1242
- expectedPaths?: string[];
1243
- actualPaths?: string[];
1244
- allowedValues?: string[];
1245
- }
1246
- | {
1247
- code: "invalid-output-path";
1248
- outputId?: string;
1249
- field: "outputs.path";
1250
- category: "path";
1251
- };
1252
-
1253
- function phaseBoundaryContractDiagnostic(
1254
- value: unknown,
1255
- phase: ImplementationPhase,
1256
- mode: unknown = "behavior",
1257
- ): Omit<
1258
- Extract<
1259
- ImplementGraphContractDiagnostic,
1260
- { code: "invalid-implement-graph" }
1261
- >,
1262
- "code"
1263
- > | null {
1264
- const prefix = `phases.${phase}`;
1265
- if (
1266
- !hasExactKeys(
1267
- value,
1268
- ["read", "write", "delete", "verification", "verificationInputs"],
1269
- ["verificationLock"],
1270
- )
1271
- ) {
1272
- return { phase, field: prefix, category: "shape" };
1273
- }
1274
- if (!validatePathSet(value.read)) {
1275
- return { phase, field: `${prefix}.read`, category: "path-set" };
1276
- }
1277
- if (!validatePathSet(value.write) || value.write.some(isAgentsPath)) {
1278
- return { phase, field: `${prefix}.write`, category: "path-set" };
1279
- }
1280
- if (!validatePathSet(value.delete) || value.delete.some(isAgentsPath)) {
1281
- return { phase, field: `${prefix}.delete`, category: "path-set" };
1282
- }
1283
- const deletions = value.delete as string[];
1284
- if (value.write.some((candidate) => deletions.includes(candidate))) {
1285
- return { phase, field: prefix, category: "write-delete-overlap" };
1286
- }
1287
- if (!validateVerificationInputBindings(value.verificationInputs)) {
1288
- return {
1289
- phase,
1290
- field: `${prefix}.verificationInputs`,
1291
- category: "verification-input-binding",
1292
- };
1293
- }
1294
- if (
1295
- value.verificationLock !== undefined &&
1296
- !isValidRelativePath(value.verificationLock)
1297
- ) {
1298
- return {
1299
- phase,
1300
- field: `${prefix}.verificationLock`,
1301
- category: "path",
1302
- };
1303
- }
1304
- const verification = validateVerificationContract(value.verification);
1305
- if (!verification.ok) {
1306
- return {
1307
- phase,
1308
- field: `${prefix}.verification`,
1309
- category: "verification-contract",
1310
- };
1311
- }
1312
- const classification = {
1313
- red:
1314
- mode !== undefined && mode !== "behavior"
1315
- ? "expected-green"
1316
- : "expected-red",
1317
- green: "expected-green",
1318
- refactor: "expected-refactor",
1319
- } as const;
1320
- if (verification.value.classification !== classification[phase]) {
1321
- return {
1322
- phase,
1323
- field: `${prefix}.verification.classification`,
1324
- category: "verification-classification",
1325
- };
1326
- }
1327
- return null;
1328
- }
1329
-
1330
- function impactClosureCategory(reason: string): string {
1331
- const categories: Record<string, string> = {
1332
- "invalid impact closure": "shape",
1333
- "invalid impact closure related test": "related-test",
1334
- "current-task related test is outside the write set":
1335
- "current-task-outside-write-set",
1336
- "regression task identity is required": "regression-task-identity",
1337
- "unexpected regression task identity": "regression-task-identity",
1338
- "public impact closure is incomplete": "public-impact-incomplete",
1339
- "affected suite contains no existing test evidence":
1340
- "existing-test-evidence",
1341
- };
1342
- return categories[reason] ?? "contract";
1343
- }
1344
-
1345
- function taskBoundaryContractDiagnostic(
1346
- value: Record<string, unknown>,
1347
- snapshot: unknown,
1348
- ): Omit<
1349
- Extract<
1350
- ImplementGraphContractDiagnostic,
1351
- { code: "invalid-implement-graph" }
1352
- >,
1353
- "code"
1354
- > | null {
1355
- const taskId = validIdentifier(value.taskId) ? value.taskId : undefined;
1356
- const diagnostic = (
1357
- field: string,
1358
- category: string,
1359
- phase?: ImplementationPhase,
1360
- ) => ({
1361
- ...(taskId ? { taskId } : {}),
1362
- ...(phase ? { phase } : {}),
1363
- field,
1364
- category,
1365
- });
1366
- if (
1367
- !hasExactKeys(
1368
- value,
1369
- [
1370
- "changeId",
1371
- "taskId",
1372
- "dependsOn",
1373
- "objective",
1374
- "context",
1375
- "roots",
1376
- "phases",
1377
- "scheduling",
1378
- "agents",
1379
- "approvedDependencies",
1380
- "impactClosure",
1381
- ],
1382
- ["verificationMode"],
1383
- )
1384
- ) {
1385
- return diagnostic("task", "shape");
1386
- }
1387
- if (
1388
- value.verificationMode !== undefined &&
1389
- !["behavior", "mechanical", "refactor"].includes(
1390
- String(value.verificationMode),
1391
- )
1392
- )
1393
- return diagnostic("verificationMode", "shape");
1394
- if (!validIdentifier(value.changeId)) {
1395
- return diagnostic("changeId", "identifier");
1396
- }
1397
- if (!validIdentifier(value.taskId)) {
1398
- return diagnostic("taskId", "identifier");
1399
- }
1400
- if (!validateIdentifierSet(value.dependsOn)) {
1401
- return diagnostic("dependsOn", "identifier-set");
1402
- }
1403
- if (
1404
- typeof value.objective !== "string" ||
1405
- value.objective.length === 0 ||
1406
- value.objective.length > 4096
1407
- ) {
1408
- return diagnostic("objective", "text-boundary");
1409
- }
1410
- if (
1411
- !hasExactKeys(value.context, ["agents", "contract"]) ||
1412
- typeof value.context.agents !== "string" ||
1413
- typeof value.context.contract !== "string"
1414
- ) {
1415
- return diagnostic("context", "shape");
1416
- }
1417
- if (!validateRoots(value.roots)) {
1418
- return diagnostic("roots", "path-set");
1419
- }
1420
- if (!hasExactKeys(value.phases, ["red", "green"], ["refactor"])) {
1421
- return diagnostic("phases", "shape");
1422
- }
1423
- for (const phase of IMPLEMENTATION_PHASES) {
1424
- const boundary = value.phases[phase];
1425
- if (phase === "refactor" && boundary === undefined) continue;
1426
- const phaseDiagnostic = phaseBoundaryContractDiagnostic(
1427
- boundary,
1428
- phase,
1429
- value.verificationMode,
1430
- );
1431
- if (phaseDiagnostic)
1432
- return { ...diagnostic("phases", "contract"), ...phaseDiagnostic };
1433
- }
1434
- if (
1435
- !hasExactKeys(value.scheduling, ["conflicts", "resources"]) ||
1436
- !validatePathSet(value.scheduling.conflicts) ||
1437
- !validatePathSet(value.scheduling.resources)
1438
- ) {
1439
- return diagnostic("scheduling", "path-set");
1440
- }
1441
- if (
1442
- !hasExactKeys(value.agents, ["impact", "managedOnly"], ["target"]) ||
1443
- !(AGENTS_IMPACTS as readonly unknown[]).includes(value.agents.impact) ||
1444
- value.agents.managedOnly !== true ||
1445
- (value.agents.impact === "none"
1446
- ? value.agents.target !== undefined
1447
- : !isAgentsPath(value.agents.target))
1448
- ) {
1449
- return diagnostic("agents", "contract");
1450
- }
1451
- if (validateApprovedDependencies(value.approvedDependencies) !== null) {
1452
- return diagnostic("approvedDependencies", "dependency-set");
1453
- }
1454
- const roots = value.roots as string[];
1455
- for (const phase of IMPLEMENTATION_PHASES) {
1456
- const boundary = value.phases[phase] as PhaseBoundary | undefined;
1457
- if (!boundary) continue;
1458
- for (const [field, candidates] of [
1459
- ["read", boundary.read],
1460
- ["write", boundary.write],
1461
- ["delete", boundary.delete],
1462
- ["verification", verificationInputPaths(boundary.verification)],
1463
- ] as const) {
1464
- if (candidates.some((candidate) => !isWithinRoots(candidate, roots))) {
1465
- return diagnostic(`phases.${phase}.${field}`, "outside-roots", phase);
1466
- }
1467
- }
1468
- if (boundary.verification.kind !== undefined) {
1469
- const declared = new Set([...boundary.read, ...boundary.write]);
1470
- if (
1471
- verificationInputPaths(boundary.verification).some(
1472
- (input) => !declared.has(input),
1473
- )
1474
- ) {
1475
- return diagnostic(
1476
- `phases.${phase}.verification`,
1477
- "verification-input-not-declared",
1478
- phase,
1479
- );
1480
- }
1481
- }
1482
- }
1483
- const taskPhases = value.phases as Record<
1484
- ImplementationPhase,
1485
- PhaseBoundary | undefined
1486
- >;
1487
- const phases = IMPLEMENTATION_PHASES.flatMap((phase) => {
1488
- const boundary = taskPhases[phase];
1489
- return boundary ? [boundary] : [];
1490
- });
1491
- const declared = {
1492
- read: [...new Set(phases.flatMap((entry) => entry.read))],
1493
- write: [
1494
- ...new Set(phases.flatMap((entry) => [...entry.write, ...entry.delete])),
1495
- ],
1496
- };
1497
- const impactReason = validateImpactClosure(
1498
- value.impactClosure,
1499
- declared,
1500
- snapshot,
1501
- );
1502
- if (impactReason !== null) {
1503
- if (impactReason === "current-task related test is outside the write set") {
1504
- const related = (value.impactClosure as ImpactClosureContract)
1505
- .relatedTests;
1506
- const index = related.findIndex(
1507
- (test) =>
1508
- test.disposition === "current-task" &&
1509
- !declared.write.includes(test.path),
1510
- );
1511
- const test = related[index];
1512
- if (test)
1513
- return {
1514
- ...diagnostic(
1515
- `impactClosure.relatedTests.${index}.disposition`,
1516
- "current-task-outside-write-set",
1517
- ),
1518
- path: test.path,
1519
- expectedPaths: [...declared.write].sort(),
1520
- actualPaths: [test.path],
1521
- };
1522
- }
1523
- return diagnostic("impactClosure", impactClosureCategory(impactReason));
1524
- }
1525
- return validateTaskBoundary(value, snapshot)
1526
- ? null
1527
- : diagnostic("task", "contract");
1528
- }
1529
-
1530
- /** Refine rejected author input; this never admits a partial or repaired task. */
1531
- function taskFieldDiagnostics(
1532
- task: Record<string, unknown>,
1533
- ): ImplementGraphContractDiagnostic[] {
1534
- const diagnostics: ImplementGraphContractDiagnostic[] = [];
1535
- const add = (
1536
- detail: Omit<
1537
- Extract<
1538
- ImplementGraphContractDiagnostic,
1539
- { code: "invalid-implement-graph" }
1540
- >,
1541
- "code"
1542
- >,
1543
- ) => {
1544
- if (diagnostics.length < 64)
1545
- diagnostics.push({
1546
- code: "invalid-implement-graph",
1547
- ...(validIdentifier(task.taskId) ? { taskId: task.taskId } : {}),
1548
- ...detail,
1549
- });
1550
- };
1551
- if (task.phases && typeof task.phases === "object") {
1552
- const phases = task.phases as Record<string, unknown>;
1553
- for (const phase of IMPLEMENTATION_PHASES) {
1554
- const boundary = phases[phase];
1555
- if (!boundary || typeof boundary !== "object") continue;
1556
- for (const field of ["read", "write", "delete"] as const) {
1557
- const paths = (boundary as Record<string, unknown>)[field];
1558
- if (!Array.isArray(paths)) continue;
1559
- const seen = new Set<string>();
1560
- for (const [index, file] of paths.entries()) {
1561
- if (!isValidRelativePath(file)) continue;
1562
- if (seen.has(file))
1563
- add({
1564
- phase,
1565
- field: `phases.${phase}.${field}.${index}`,
1566
- category: "duplicate-path",
1567
- path: file,
1568
- });
1569
- seen.add(file);
1570
- }
1571
- }
1572
- }
1573
- }
1574
- if (task.impactClosure && typeof task.impactClosure === "object") {
1575
- const closure = task.impactClosure as Record<string, unknown>;
1576
- if (Array.isArray(closure.changedSurfaces)) {
1577
- for (const [index, surface] of closure.changedSurfaces.entries()) {
1578
- if (!(IMPACT_SURFACES as readonly unknown[]).includes(surface))
1579
- add({
1580
- field: `impactClosure.changedSurfaces.${index}`,
1581
- category: "enum",
1582
- allowedValues: [...IMPACT_SURFACES],
1583
- });
1584
- }
1585
- const publicImpact = closure.changedSurfaces.some(
1586
- (surface) =>
1587
- surface !== "none" &&
1588
- (IMPACT_SURFACES as readonly unknown[]).includes(surface),
1589
- );
1590
- if (publicImpact) {
1591
- for (const [field, category] of [
1592
- ["searchEvidence", "search-evidence-required"],
1593
- ["relatedTests", "related-tests-required"],
1594
- ["affectedSuite", "affected-suite-required"],
1595
- ] as const) {
1596
- if (Array.isArray(closure[field]) && closure[field].length === 0)
1597
- add({ field: `impactClosure.${field}`, category });
1598
- }
1599
- }
1600
- if (
1601
- publicImpact &&
1602
- Array.isArray(closure.affectedSuite) &&
1603
- Array.isArray(closure.relatedTests)
1604
- ) {
1605
- const related = new Set(
1606
- closure.relatedTests.flatMap((item) =>
1607
- item && typeof item === "object" ? [item.path] : [],
1608
- ),
1609
- );
1610
- for (const [index, file] of closure.affectedSuite.entries())
1611
- if (isValidRelativePath(file) && !related.has(file))
1612
- add({
1613
- field: `impactClosure.affectedSuite.${index}`,
1614
- category: "related-test-missing",
1615
- path: file,
1616
- });
1617
- }
1618
- }
1619
- }
1620
- return diagnostics;
1621
- }
1622
-
1623
- export function validateImplementGraphBoundary(value: unknown):
1624
- | { ok: true; value: ImplementGraphBoundary }
1625
- | {
1626
- ok: false;
1627
- reason: string;
1628
- diagnostic: ImplementGraphContractDiagnostic;
1629
- diagnostics?: ImplementGraphContractDiagnostic[];
1630
- } {
1631
- if (
1632
- !hasExactKeys(value, ["changeId", "tasks", "outputs"]) ||
1633
- !validIdentifier(value.changeId) ||
1634
- !Array.isArray(value.tasks) ||
1635
- value.tasks.length === 0 ||
1636
- value.tasks.length > 128 ||
1637
- !Array.isArray(value.outputs) ||
1638
- value.outputs.length > 512
1639
- )
1640
- return {
1641
- ok: false,
1642
- reason: "invalid Implement graph boundary",
1643
- diagnostic: { code: "invalid-implement-graph" },
1644
- };
1645
-
1646
- // Collect independent task/output failures without admitting a partial graph.
1647
- const failures: Array<{
1648
- reason: string;
1649
- diagnostic: ImplementGraphContractDiagnostic;
1650
- }> = [];
1651
- for (const candidate of value.tasks) {
1652
- if (
1653
- !candidate ||
1654
- typeof candidate !== "object" ||
1655
- Array.isArray(candidate)
1656
- ) {
1657
- failures.push({
1658
- reason: "invalid Implement task boundary",
1659
- diagnostic: {
1660
- code: "invalid-implement-graph",
1661
- field: "tasks",
1662
- category: "shape",
1663
- },
1664
- });
1665
- continue;
1666
- }
1667
- const task = candidate as Record<string, unknown>;
1668
- const diagnostic = Object.hasOwn(task, "changeId")
1669
- ? {
1670
- ...(validIdentifier(task.taskId) ? { taskId: task.taskId } : {}),
1671
- field: "changeId",
1672
- category: "unexpected-field",
1673
- }
1674
- : taskBoundaryContractDiagnostic(
1675
- { changeId: value.changeId, ...task },
1676
- undefined,
1677
- );
1678
- if (diagnostic) {
1679
- const fields = taskFieldDiagnostics(task);
1680
- // Replace a broad diagnostic only when these details refine that field;
1681
- // retain unrelated shape/authority failures in the same batch.
1682
- if (
1683
- !fields.some(
1684
- (detail) =>
1685
- diagnostic.field &&
1686
- detail.field?.startsWith(`${diagnostic.field}.`),
1687
- )
1688
- )
1689
- fields.push({ code: "invalid-implement-graph", ...diagnostic });
1690
- for (const detail of fields)
1691
- failures.push({
1692
- reason: "invalid Implement task boundary",
1693
- diagnostic: detail,
1694
- });
1695
- }
1696
- }
1697
- for (const candidate of value.outputs) {
1698
- if (
1699
- !candidate ||
1700
- typeof candidate !== "object" ||
1701
- Array.isArray(candidate)
1702
- ) {
1703
- failures.push({
1704
- reason: "invalid Implement graph output",
1705
- diagnostic: { code: "invalid-implement-graph" },
1706
- });
1707
- continue;
1708
- }
1709
- const output = candidate as Record<string, unknown>;
1710
- if (!isValidRelativePath(output.path)) {
1711
- failures.push({
1712
- reason: "invalid Implement graph output path",
1713
- diagnostic: {
1714
- code: "invalid-output-path",
1715
- ...(validIdentifier(output.id) ? { outputId: output.id } : {}),
1716
- field: "outputs.path",
1717
- category: "path",
1718
- },
1719
- });
1720
- continue;
1721
- }
1722
- if (
1723
- !hasExactKeys(output, ["id", "path", "producer", "postcondition"]) ||
1724
- !validIdentifier(output.id) ||
1725
- !hasExactKeys(output.producer, ["taskId", "phase"]) ||
1726
- !validIdentifier(output.producer.taskId) ||
1727
- !(IMPLEMENTATION_PHASES as readonly unknown[]).includes(
1728
- output.producer.phase,
1729
- ) ||
1730
- output.postcondition !== "regular-file"
1731
- ) {
1732
- failures.push({
1733
- reason: "invalid Implement graph output",
1734
- diagnostic: {
1735
- code: "invalid-implement-graph",
1736
- ...(validIdentifier(output.id) ? { outputId: output.id } : {}),
1737
- field: "outputs",
1738
- category: "shape",
1739
- },
1740
- });
1741
- }
1742
- }
1743
- const first = failures[0];
1744
- if (first)
1745
- return {
1746
- ok: false,
1747
- ...first,
1748
- ...(failures.length > 1
1749
- ? { diagnostics: failures.map((failure) => failure.diagnostic) }
1750
- : {}),
1751
- };
1752
- return {
1753
- ok: true,
1754
- value: structuredClone(value) as unknown as ImplementGraphBoundary,
1755
- };
1756
- }
1757
-
1758
- export function validatePacketEnvelope(
1759
- value: unknown,
1760
- ): { ok: true; value: PacketEnvelope } | { ok: false; reason: string } {
1761
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
1762
- return { ok: false, reason: "missing request envelope" };
1763
- }
1764
- const packet = value as Record<string, unknown>;
1765
- const baseRequired = [
1766
- "stage",
1767
- "role",
1768
- "id",
1769
- "phase",
1770
- "objective",
1771
- "roots",
1772
- "context",
1773
- "declared",
1774
- "output",
1775
- ] as const;
1776
- const required =
1777
- packet.stage === "abel-design"
1778
- ? ([
1779
- ...baseRequired.slice(0, 2),
1780
- "runId",
1781
- ...baseRequired.slice(2),
1782
- ] as const)
1783
- : baseRequired;
1784
- for (const field of required) {
1785
- if (packet[field] === undefined) {
1786
- return { ok: false, reason: `missing required field: ${field}` };
1787
- }
1788
- }
1789
- if (!hasExactKeys(packet, required)) {
1790
- return { ok: false, reason: "invalid packet fields" };
1791
- }
1792
-
1793
- if (packet.stage !== "abel-design" && packet.stage !== "abel-diagnose") {
1794
- return {
1795
- ok: false,
1796
- reason: `unsupported packet stage: ${String(packet.stage)}`,
1797
- };
1798
- }
1799
- if (
1800
- (packet.stage === "abel-design" && packet.role !== "design-explorer") ||
1801
- (packet.stage === "abel-diagnose" && packet.role !== "diagnosis-worker")
1802
- ) {
1803
- return { ok: false, reason: "packet role does not match stage" };
1804
- }
1805
- if (
1806
- (packet.stage === "abel-design" && !validIdentifier(packet.runId)) ||
1807
- (packet.stage === "abel-diagnose" && packet.runId !== undefined)
1808
- ) {
1809
- return { ok: false, reason: "invalid Design run identity" };
1810
- }
1811
- if (!validIdentifier(packet.id)) {
1812
- return { ok: false, reason: "invalid request id" };
1813
- }
1814
- if (
1815
- typeof packet.phase !== "string" ||
1816
- !(PHASES as readonly string[]).includes(packet.phase)
1817
- ) {
1818
- return { ok: false, reason: `invalid phase: ${String(packet.phase)}` };
1819
- }
1820
- if (
1821
- typeof packet.objective !== "string" ||
1822
- packet.objective.length === 0 ||
1823
- packet.objective.length > 4096
1824
- ) {
1825
- return { ok: false, reason: "invalid objective" };
1826
- }
1827
- let serialized: string;
1828
- try {
1829
- serialized = JSON.stringify(packet);
1830
- } catch {
1831
- return { ok: false, reason: "request envelope is not serializable" };
1832
- }
1833
- if (serialized.length > LIMITS.maxEnvelopeBytes) {
1834
- return {
1835
- ok: false,
1836
- reason: `request envelope exceeds ${LIMITS.maxEnvelopeBytes / 1024} KiB`,
1837
- };
1838
- }
1839
- if (
1840
- !Array.isArray(packet.roots) ||
1841
- packet.roots.length === 0 ||
1842
- !packet.roots.every(isValidRelativePath)
1843
- ) {
1844
- return { ok: false, reason: "invalid path roots" };
1845
- }
1846
- if (
1847
- !hasExactKeys(packet.context, ["agents", "contract"]) ||
1848
- typeof packet.context.agents !== "string" ||
1849
- typeof packet.context.contract !== "string"
1850
- ) {
1851
- return { ok: false, reason: "invalid context" };
1852
- }
1853
- if (
1854
- !hasExactKeys(
1855
- packet.declared,
1856
- ["read", "write", "conflicts", "resources"],
1857
- ["verificationLock"],
1858
- )
1859
- ) {
1860
- return { ok: false, reason: "invalid declared sets" };
1861
- }
1862
- for (const key of ["read", "write", "conflicts", "resources"] as const) {
1863
- const entries = packet.declared[key];
1864
- if (
1865
- !Array.isArray(entries) ||
1866
- !entries.every(isValidRelativePath) ||
1867
- new Set(entries).size !== entries.length
1868
- ) {
1869
- return { ok: false, reason: `invalid declared ${key} set` };
1870
- }
1871
- }
1872
- if ((packet.declared.write as unknown[]).some(isAgentsPath)) {
1873
- return { ok: false, reason: "subagents cannot declare AGENTS writes" };
1874
- }
1875
- if (
1876
- packet.declared.verificationLock !== undefined &&
1877
- !isValidRelativePath(packet.declared.verificationLock)
1878
- ) {
1879
- return { ok: false, reason: "invalid verification lock" };
1880
- }
1881
- if (!(OUTPUT_KINDS as readonly unknown[]).includes(packet.output)) {
1882
- return {
1883
- ok: false,
1884
- reason: `invalid output kind: ${String(packet.output)}`,
1885
- };
1886
- }
1887
- if (
1888
- packet.stage === "abel-design" &&
1889
- (packet.phase !== "evidence" ||
1890
- packet.output !== "evidence" ||
1891
- (packet.declared.write as unknown[]).length !== 0)
1892
- ) {
1893
- return { ok: false, reason: "invalid Design packet contract" };
1894
- }
1895
-
1896
- return {
1897
- ok: true,
1898
- value: structuredClone(packet) as unknown as PacketEnvelope,
1899
- };
1900
- }
1901
-
1902
- /** Extract proposed write paths from ordinary unified diff header pairs. */
1903
- export function diffWritePaths(diffText: string): {
1904
- paths: string[];
1905
- kind: string;
1906
- } {
1907
- if (typeof diffText !== "string" || diffText.length === 0) {
1908
- throw new Error("empty diff");
1909
- }
1910
- const hasFinalLf = diffText.endsWith("\n");
1911
- const lines = (hasFinalLf ? diffText.slice(0, -1) : diffText).split("\n");
1912
-
1913
- for (const line of lines) {
1914
- if (/^(?:GIT binary patch|Binary files )/.test(line)) {
1915
- throw new Error("binary diff is not supported");
1916
- }
1917
- if (/^(?:copy|rename) (?:from|to) /.test(line)) {
1918
- throw new Error(`${line.split(" ", 1)[0]} records are not supported`);
1919
- }
1920
- if (/^(?:old|new) mode /.test(line)) {
1921
- throw new Error("mode transitions are not supported");
1922
- }
1923
- const mode = /^(?:new file mode|deleted file mode) (.+)$/.exec(line);
1924
- if (mode) requireRegularFileMode(mode[1]);
1925
- }
1926
-
1927
- const paths: string[] = [];
1928
- let i = 0;
1929
- while (i < lines.length) {
1930
- if (!isFileSectionStart(lines[i])) {
1931
- if (paths.length === 0) {
1932
- throw new Error("diff has no ordinary text headers");
1933
- }
1934
- throw new Error(`unconsumed diff content: ${lines[i]}`);
1935
- }
1936
-
1937
- let gitTarget: string | null = null;
1938
- if (lines[i].startsWith("diff --git ")) {
1939
- gitTarget = parseGitTarget(lines[i]);
1940
- i++;
1941
- }
1942
-
1943
- let operation: "add" | "delete" | null = null;
1944
- const mode = /^(new file mode|deleted file mode) (.+)$/.exec(
1945
- lines[i] ?? "",
1946
- );
1947
- if (mode) {
1948
- operation = mode[1] === "new file mode" ? "add" : "delete";
1949
- i++;
1950
- }
1951
- if (lines[i]?.startsWith("index ")) {
1952
- parseIndexMetadata(lines[i]);
1953
- i++;
1954
- }
1955
-
1956
- if (!lines[i]?.startsWith("--- ")) {
1957
- throw new Error("file section is missing an ordinary --- header");
1958
- }
1959
- const oldPath = parseHeaderPath(lines[i].slice(4), "a");
1960
- i++;
1961
- if (!lines[i]?.startsWith("+++ ")) {
1962
- throw new Error("diff header pair is malformed");
1963
- }
1964
- const newPath = parseHeaderPath(lines[i].slice(4), "b");
1965
- i++;
1966
-
1967
- if (oldPath === null && newPath === null) {
1968
- throw new Error("diff header pair has no target");
1969
- }
1970
- if (oldPath !== null && newPath !== null && oldPath !== newPath) {
1971
- throw new Error("mismatched old/new diff targets");
1972
- }
1973
- const target = oldPath ?? newPath;
1974
- if (target === null) throw new Error("diff header pair has no target");
1975
- if (gitTarget !== null && gitTarget !== target) {
1976
- throw new Error("mismatched diff --git and text targets");
1977
- }
1978
- if (
1979
- (operation === "add" && oldPath !== null) ||
1980
- (operation === "delete" && newPath !== null)
1981
- ) {
1982
- throw new Error("create/delete metadata does not match text headers");
1983
- }
1984
- if (paths.includes(target)) throw new Error("duplicate diff targets");
1985
- paths.push(target);
1986
-
1987
- let sawHunk = false;
1988
- while (lines[i]?.startsWith("@@")) {
1989
- const hunk = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(?: .*)?$/.exec(
1990
- lines[i],
1991
- );
1992
- if (!hunk) throw new Error(`invalid hunk header: ${lines[i]}`);
1993
- const range = [
1994
- Number(hunk[1]),
1995
- Number(hunk[2] ?? "1"),
1996
- Number(hunk[3]),
1997
- Number(hunk[4] ?? "1"),
1998
- ];
1999
- if (!range.every(Number.isSafeInteger)) {
2000
- throw new Error("hunk range exceeds supported integer bounds");
2001
- }
2002
- let oldRemaining = range[1];
2003
- let newRemaining = range[3];
2004
- sawHunk = true;
2005
- i++;
2006
-
2007
- while (oldRemaining > 0 || newRemaining > 0) {
2008
- if (i >= lines.length) throw new Error("hunk body underflow");
2009
- const body = lines[i];
2010
- switch (body[0]) {
2011
- case " ":
2012
- if (oldRemaining === 0 || newRemaining === 0) {
2013
- throw new Error("hunk body overflow");
2014
- }
2015
- oldRemaining--;
2016
- newRemaining--;
2017
- break;
2018
- case "-":
2019
- if (oldRemaining === 0) throw new Error("hunk body overflow");
2020
- oldRemaining--;
2021
- break;
2022
- case "+":
2023
- if (newRemaining === 0) throw new Error("hunk body overflow");
2024
- newRemaining--;
2025
- break;
2026
- default:
2027
- throw new Error("hunk body underflow");
2028
- }
2029
- i++;
2030
- if (lines[i] === "\") i++;
2031
- }
2032
- }
2033
- if (!sawHunk) {
2034
- throw new Error("file section has no valid unified-diff hunk");
2035
- }
2036
- }
2037
-
2038
- if (!hasFinalLf) throw new Error("diff must end with LF");
2039
- return { paths, kind: "text" };
2040
- }
2041
-
2042
- function isFileSectionStart(line: string | undefined): boolean {
2043
- return (
2044
- line?.startsWith("diff --git ") === true ||
2045
- line?.startsWith("new file mode ") === true ||
2046
- line?.startsWith("deleted file mode ") === true ||
2047
- line?.startsWith("index ") === true ||
2048
- line?.startsWith("--- ") === true
2049
- );
2050
- }
2051
-
2052
- function parseGitTarget(line: string): string {
2053
- const prefix = "diff --git a/";
2054
- if (!line.startsWith(prefix)) throw new Error("malformed diff --git header");
2055
- const rest = line.slice(prefix.length);
2056
- for (
2057
- let separator = rest.indexOf(" b/");
2058
- separator >= 0;
2059
- separator = rest.indexOf(" b/", separator + 1)
2060
- ) {
2061
- const oldPath = rest.slice(0, separator);
2062
- const newPath = rest.slice(separator + 3);
2063
- if (oldPath === newPath) return requireCanonicalDiffPath(oldPath);
2064
- }
2065
- throw new Error("mismatched diff --git targets");
2066
- }
2067
-
2068
- function parseIndexMetadata(line: string): void {
2069
- const index = /^index [0-9a-f]+\.\.[0-9a-f]+(?: ([0-7]{6}))?$/.exec(line);
2070
- if (!index) throw new Error("malformed index metadata");
2071
- if (index[1]) requireRegularFileMode(index[1]);
2072
- }
2073
-
2074
- function requireRegularFileMode(mode: string): void {
2075
- if (mode === "160000") {
2076
- throw new Error("submodule mode is not supported: 160000");
2077
- }
2078
- if (mode !== "100644" && mode !== "100755") {
2079
- throw new Error(`non-regular file mode is not supported: ${mode}`);
2080
- }
2081
- }
2082
-
2083
- function parseHeaderPath(header: string, prefix: "a" | "b"): string | null {
2084
- if (header === "/dev/null") return null;
2085
- const other = prefix === "a" ? "b" : "a";
2086
- if (header.startsWith(`${other}/`)) {
2087
- throw new Error("mismatched old/new diff header prefixes");
2088
- }
2089
- const path = header.startsWith(`${prefix}/`) ? header.slice(2) : header;
2090
- return requireCanonicalDiffPath(path);
2091
- }
2092
-
2093
- function requireCanonicalDiffPath(path: string): string {
2094
- if (
2095
- !isValidRelativePath(path) ||
2096
- path.trim() !== path ||
2097
- /[\r\n\t]/.test(path)
2098
- ) {
2099
- throw new Error(`noncanonical or escaping diff path: ${path}`);
2100
- }
2101
- return path;
2102
- }
2103
-
2104
- export interface CompactEvidenceResult {
2105
- id: string;
2106
- role: string;
2107
- kind: "evidence";
2108
- conclusions: string[];
2109
- citations: { path: string; lines: string }[];
2110
- constraints: string[];
2111
- dependencies: string[];
2112
- risks: string[];
2113
- blockingQuestions: string[];
2114
- hints: { writeSet: string[]; verification: string; agentsImpact: string };
2115
- }
2116
-
2117
- export interface DesignEvidenceResult {
2118
- id: string;
2119
- role: "design-explorer";
2120
- kind: "evidence";
2121
- packet_id: string;
2122
- module_name: string;
2123
- scope: string[];
2124
- files_read: string[];
2125
- evidence: Array<{
2126
- claim: string;
2127
- path: string;
2128
- line_start: number;
2129
- line_end: number;
2130
- }>;
2131
- existing_structures: string[];
2132
- existing_conventions: string[];
2133
- constraints_discovered: string[];
2134
- open_questions: string[];
2135
- dependencies: string[];
2136
- write_set_hints: string[];
2137
- validation_hints: string[];
2138
- agents_impact_hints: string[];
2139
- risks: string[];
2140
- success_criteria_hints: string[];
2141
- }
2142
-
2143
- export type EvidenceResult = CompactEvidenceResult | DesignEvidenceResult;
2144
-
2145
- export interface DiffResult {
2146
- id: string;
2147
- role: string;
2148
- kind: "diff";
2149
- taskId: string;
2150
- phase: string;
2151
- summary: string;
2152
- diff: string;
2153
- expectedVerification: string;
2154
- risks: string[];
2155
- contractCompliant: boolean;
2156
- }
2157
-
2158
- // Diagnostics contain only code-owned field names, never unexpected keys or values.
2159
- function evidenceFieldsFailure(
2160
- value: unknown,
2161
- fields: readonly string[],
2162
- location: string,
2163
- ): { ok: false; reason: string } {
2164
- if (!value || typeof value !== "object" || Array.isArray(value))
2165
- return { ok: false, reason: `${location}: expected an object` };
2166
- const missing = fields.filter((field) => !Object.hasOwn(value, field));
2167
- return {
2168
- ok: false,
2169
- reason:
2170
- missing.length > 0
2171
- ? `${location}: missing fields ${missing.join(", ")}`
2172
- : `${location}: unexpected fields; allowed fields ${fields.join(", ")}`,
2173
- };
2174
- }
2175
-
2176
- export function validateEvidenceResult(value: unknown): {
2177
- ok: boolean;
2178
- reason?: string;
2179
- failure?: CandidateFailure;
2180
- } {
2181
- if (value === null || typeof value !== "object")
2182
- return { ok: false, reason: "missing result" };
2183
- const r = value as Record<string, unknown>;
2184
- if (r.kind !== "evidence") return { ok: false, reason: "wrong result kind" };
2185
- if (r.role === "design-explorer") return validateDesignEvidenceResult(r);
2186
- const fields = [
2187
- "id",
2188
- "role",
2189
- "kind",
2190
- "conclusions",
2191
- "citations",
2192
- "constraints",
2193
- "dependencies",
2194
- "risks",
2195
- "blockingQuestions",
2196
- "hints",
2197
- ] as const;
2198
- if (!hasExactKeys(r, fields)) {
2199
- return evidenceFieldsFailure(r, fields, "result");
2200
- }
2201
- if (!validIdentifier(r.id) || typeof r.role !== "string")
2202
- return { ok: false, reason: "invalid identity" };
2203
- if (!(ROLES as readonly string[]).includes(r.role))
2204
- return { ok: false, reason: "unknown role" };
2205
- for (const field of [
2206
- "conclusions",
2207
- "constraints",
2208
- "dependencies",
2209
- "risks",
2210
- "blockingQuestions",
2211
- ] as const) {
2212
- if (
2213
- !Array.isArray(r[field]) ||
2214
- !r[field].every((entry) => typeof entry === "string")
2215
- ) {
2216
- return { ok: false, reason: `invalid evidence field: ${field}` };
2217
- }
2218
- }
2219
- if (!Array.isArray(r.citations))
2220
- return { ok: false, reason: "citations: expected an array" };
2221
- for (const [index, citation] of r.citations.entries()) {
2222
- const field = `citations[${index}]`;
2223
- if (!hasExactKeys(citation, ["path", "lines"]))
2224
- return evidenceFieldsFailure(citation, ["path", "lines"], field);
2225
- if (!isValidRelativePath(citation.path))
2226
- return {
2227
- ok: false,
2228
- reason: `${field}.path: expected a canonical relative path`,
2229
- };
2230
- if (typeof citation.lines !== "string")
2231
- return { ok: false, reason: `${field}.lines: expected a string` };
2232
- }
2233
- if (!hasExactKeys(r.hints, ["writeSet", "verification", "agentsImpact"]))
2234
- return evidenceFieldsFailure(
2235
- r.hints,
2236
- ["writeSet", "verification", "agentsImpact"],
2237
- "hints",
2238
- );
2239
- if (!validatePathSet(r.hints.writeSet))
2240
- return {
2241
- ok: false,
2242
- reason: "hints.writeSet: expected unique canonical relative paths",
2243
- };
2244
- if (typeof r.hints.verification !== "string")
2245
- return { ok: false, reason: "hints.verification: expected a string" };
2246
- if (!(AGENTS_IMPACTS as readonly unknown[]).includes(r.hints.agentsImpact))
2247
- return {
2248
- ok: false,
2249
- reason: `hints.agentsImpact: expected one of ${AGENTS_IMPACTS.join(", ")}`,
2250
- };
2251
- const serialized = JSON.stringify(r);
2252
- if (Buffer.byteLength(serialized, "utf8") > LIMITS.maxCompleteResultBytes) {
2253
- return {
2254
- ok: false,
2255
- reason: `result exceeds ${LIMITS.maxCompleteResultBytes} bytes`,
2256
- failure: {
2257
- kind: "result-limit",
2258
- limitBytes: LIMITS.maxCompleteResultBytes,
2259
- },
2260
- };
2261
- }
2262
- return { ok: true };
2263
- }
2264
-
2265
- function validateDesignEvidenceResult(r: Record<string, unknown>): {
2266
- ok: boolean;
2267
- reason?: string;
2268
- failure?: CandidateFailure;
2269
- } {
2270
- const fields = [
2271
- "id",
2272
- "role",
2273
- "kind",
2274
- "packet_id",
2275
- "module_name",
2276
- "scope",
2277
- "files_read",
2278
- "evidence",
2279
- "existing_structures",
2280
- "existing_conventions",
2281
- "constraints_discovered",
2282
- "open_questions",
2283
- "dependencies",
2284
- "write_set_hints",
2285
- "validation_hints",
2286
- "agents_impact_hints",
2287
- "risks",
2288
- "success_criteria_hints",
2289
- ] as const;
2290
- if (!hasExactKeys(r, fields)) {
2291
- return evidenceFieldsFailure(r, fields, "result");
2292
- }
2293
- if (!validIdentifier(r.id))
2294
- return { ok: false, reason: "id: expected a valid identifier" };
2295
- if (r.packet_id !== r.id)
2296
- return { ok: false, reason: "packet_id: must equal id" };
2297
- if (!isValidRelativePath(r.module_name))
2298
- return {
2299
- ok: false,
2300
- reason: "module_name: expected a canonical relative path or module slug",
2301
- };
2302
- if (!validatePathSet(r.scope) || r.scope.length === 0) {
2303
- return {
2304
- ok: false,
2305
- reason:
2306
- "scope: expected a nonempty set of unique canonical relative paths",
2307
- };
2308
- }
2309
- for (const field of ["files_read", "write_set_hints"] as const) {
2310
- if (!validatePathSet(r[field]))
2311
- return {
2312
- ok: false,
2313
- reason: `${field}: expected unique canonical relative paths`,
2314
- };
2315
- }
2316
- if (!Array.isArray(r.evidence))
2317
- return { ok: false, reason: "evidence: expected an array" };
2318
- for (const [index, entry] of r.evidence.entries()) {
2319
- const field = `evidence[${index}]`;
2320
- if (!hasExactKeys(entry, ["claim", "path", "line_start", "line_end"])) {
2321
- return evidenceFieldsFailure(
2322
- entry,
2323
- ["claim", "path", "line_start", "line_end"],
2324
- field,
2325
- );
2326
- }
2327
- if (typeof entry.claim !== "string" || entry.claim.length === 0)
2328
- return { ok: false, reason: `${field}.claim: expected nonempty text` };
2329
- if (!isValidRelativePath(entry.path))
2330
- return {
2331
- ok: false,
2332
- reason: `${field}.path: expected a canonical relative path`,
2333
- };
2334
- if (
2335
- !Number.isSafeInteger(entry.line_start) ||
2336
- (entry.line_start as number) < 1
2337
- )
2338
- return {
2339
- ok: false,
2340
- reason: `${field}.line_start: expected a positive safe integer`,
2341
- };
2342
- if (
2343
- !Number.isSafeInteger(entry.line_end) ||
2344
- (entry.line_end as number) < (entry.line_start as number)
2345
- )
2346
- return {
2347
- ok: false,
2348
- reason: `${field}.line_end: expected a safe integer >= line_start`,
2349
- };
2350
- }
2351
- for (const field of [
2352
- "existing_structures",
2353
- "existing_conventions",
2354
- "constraints_discovered",
2355
- "open_questions",
2356
- "dependencies",
2357
- "validation_hints",
2358
- "agents_impact_hints",
2359
- "risks",
2360
- "success_criteria_hints",
2361
- ] as const) {
2362
- if (
2363
- !Array.isArray(r[field]) ||
2364
- !r[field].every((entry) => typeof entry === "string")
2365
- ) {
2366
- return {
2367
- ok: false,
2368
- reason: `invalid Design evidence field: ${field}`,
2369
- };
2370
- }
2371
- }
2372
- const serialized = JSON.stringify(r);
2373
- if (Buffer.byteLength(serialized, "utf8") > LIMITS.maxCompleteResultBytes) {
2374
- return {
2375
- ok: false,
2376
- reason: `result exceeds ${LIMITS.maxCompleteResultBytes} bytes`,
2377
- failure: {
2378
- kind: "result-limit",
2379
- limitBytes: LIMITS.maxCompleteResultBytes,
2380
- },
2381
- };
2382
- }
2383
- return { ok: true };
2384
- }
2385
-
2386
- export function validateDiffResult(value: unknown): {
2387
- ok: boolean;
2388
- reason?: string;
2389
- paths?: string[];
2390
- failure?: CandidateFailure;
2391
- } {
2392
- if (value === null || typeof value !== "object")
2393
- return { ok: false, reason: "missing result" };
2394
- const r = value as Record<string, unknown>;
2395
- if (r.kind !== "diff") return { ok: false, reason: "wrong result kind" };
2396
- const fields = [
2397
- "id",
2398
- "role",
2399
- "kind",
2400
- "taskId",
2401
- "phase",
2402
- "summary",
2403
- "diff",
2404
- "expectedVerification",
2405
- "risks",
2406
- "contractCompliant",
2407
- ] as const;
2408
- if (!hasExactKeys(r, fields)) {
2409
- return { ok: false, reason: "invalid diff result fields" };
2410
- }
2411
- for (const field of fields) {
2412
- if (r[field] === undefined)
2413
- return { ok: false, reason: `missing field: ${field}` };
2414
- }
2415
- if (
2416
- typeof r.id !== "string" ||
2417
- r.id.length === 0 ||
2418
- r.id.length > 128 ||
2419
- typeof r.role !== "string" ||
2420
- r.role.length === 0 ||
2421
- typeof r.taskId !== "string" ||
2422
- r.taskId.length === 0
2423
- ) {
2424
- return { ok: false, reason: "invalid identity" };
2425
- }
2426
- if (!(ROLES as readonly string[]).includes(r.role))
2427
- return { ok: false, reason: "unknown role" };
2428
- if (
2429
- typeof r.phase !== "string" ||
2430
- !(PHASES as readonly string[]).includes(r.phase)
2431
- ) {
2432
- return { ok: false, reason: "invalid phase" };
2433
- }
2434
- if (
2435
- typeof r.summary !== "string" ||
2436
- r.summary.length === 0 ||
2437
- typeof r.expectedVerification !== "string" ||
2438
- r.expectedVerification.length === 0
2439
- ) {
2440
- return { ok: false, reason: "invalid diff result text" };
2441
- }
2442
- if (
2443
- !Array.isArray(r.risks) ||
2444
- !(r.risks as unknown[]).every((x) => typeof x === "string")
2445
- ) {
2446
- return { ok: false, reason: "invalid risks" };
2447
- }
2448
- if (r.contractCompliant !== true)
2449
- return { ok: false, reason: "contract compliance not affirmed" };
2450
- let paths: string[];
2451
- try {
2452
- paths = diffWritePaths(r.diff as string).paths;
2453
- } catch (err) {
2454
- return {
2455
- ok: false,
2456
- reason: `invalid diff: ${(err as Error).message}`,
2457
- failure: {
2458
- kind: "artifact",
2459
- code: "invalid-diff",
2460
- stage: "candidate-diff",
2461
- },
2462
- };
2463
- }
2464
- const serialized = JSON.stringify(r);
2465
- if (Buffer.byteLength(serialized, "utf8") > LIMITS.maxCompleteResultBytes) {
2466
- return {
2467
- ok: false,
2468
- reason: `result exceeds ${LIMITS.maxCompleteResultBytes} bytes`,
2469
- failure: {
2470
- kind: "result-limit",
2471
- limitBytes: LIMITS.maxCompleteResultBytes,
2472
- },
2473
- };
2474
- }
2475
- return { ok: true, paths };
2476
- }