@abelxiaoxing/cadence 1.8.4 → 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 (202) hide show
  1. package/README.md +324 -117
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -18
  4. package/agents/diagnosis-worker.md +14 -14
  5. package/agents/implementation-worker.md +16 -17
  6. package/config/workflow-task.example.json +7 -0
  7. package/docs/remote-access.md +60 -0
  8. package/docs/reuse-and-distribution.md +44 -0
  9. package/docs/service-management.md +108 -0
  10. package/licenses/bundled-web.txt +6245 -0
  11. package/licenses/pi-web-ui-MIT.txt +21 -0
  12. package/licenses/pi-web-ui-reuse.md +22 -0
  13. package/package.json +94 -21
  14. package/src/index.ts +21 -1802
  15. package/src/native-build.ts +6 -0
  16. package/src/native-client.ts +103 -0
  17. package/src/native-export.ts +58 -0
  18. package/src/native-history.ts +359 -0
  19. package/src/operator-cli.mjs +453 -1937
  20. package/src/plan-proposal.ts +87 -0
  21. package/src/sdk-worker.mjs +549 -0
  22. package/src/service/contracts/active.ts +82 -0
  23. package/src/service/contracts/budget.ts +148 -0
  24. package/src/service/contracts/build.ts +67 -0
  25. package/src/service/contracts/capture.ts +48 -0
  26. package/src/service/contracts/core.ts +33 -0
  27. package/src/service/contracts/current.ts +261 -0
  28. package/src/service/contracts/domain.ts +68 -0
  29. package/src/service/contracts/history.ts +2245 -0
  30. package/src/service/contracts/models.ts +139 -0
  31. package/src/service/coordination.ts +862 -0
  32. package/src/service/private-files.ts +204 -0
  33. package/src/service-cli.mjs +5592 -0
  34. package/src/service-client.ts +747 -0
  35. package/src/service-contract.ts +245 -0
  36. package/src/service-host.mjs +22382 -0
  37. package/src/service-http-client.ts +425 -0
  38. package/src/service-wire.ts +372 -0
  39. package/src/task-shell.ts +1045 -0
  40. package/src/task-store.ts +479 -0
  41. package/src/task-types.ts +184 -0
  42. package/src/verification-diagnostics.ts +29 -72
  43. package/src/worker-capture.mjs +1197 -0
  44. package/src/worker-public.mjs +1753 -0
  45. package/src/worker-redaction.mjs +608 -0
  46. package/src/worker-tools.mjs +202 -0
  47. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  50. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  56. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  62. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  68. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  71. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  74. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  77. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  89. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  92. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  98. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  104. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  107. package/web/dist/assets/cadence.css +2 -0
  108. package/web/dist/assets/cadence.js +7 -0
  109. package/web/dist/assets/markdown.js +296 -0
  110. package/web/dist/assets/react.js +9 -0
  111. package/web/dist/assets/rolldown-runtime.js +1 -0
  112. package/web/dist/assets/timeline-client.js +41 -0
  113. package/web/dist/assets/vis-timeline.css +1274 -0
  114. package/web/dist/assets/vis-timeline.js +32 -0
  115. package/web/dist/index.html +18 -0
  116. package/web/dist/manifest.json +505 -0
  117. package/config/plan-draft.example.json +0 -125
  118. package/config/plan-draft.multiple-tasks.example.json +0 -375
  119. package/config/plan-draft.quick.example.json +0 -78
  120. package/prompts/abel-design.md +0 -382
  121. package/prompts/abel-diagnose.md +0 -71
  122. package/prompts/abel-implement.md +0 -284
  123. package/prompts/abel-init.md +0 -52
  124. package/src/activation.ts +0 -44
  125. package/src/activity-contracts.ts +0 -82
  126. package/src/agent-registry.ts +0 -53
  127. package/src/apply-transaction.ts +0 -1437
  128. package/src/artifact-store.ts +0 -213
  129. package/src/build-windows-job.mjs +0 -95
  130. package/src/candidate-artifact.ts +0 -143
  131. package/src/candidate-context.ts +0 -289
  132. package/src/candidate-patch.ts +0 -271
  133. package/src/candidate-workspace.ts +0 -80
  134. package/src/canonical.ts +0 -16
  135. package/src/change-contract-schema.ts +0 -191
  136. package/src/change-contract.ts +0 -324
  137. package/src/change-verification.ts +0 -1617
  138. package/src/contracts.ts +0 -2466
  139. package/src/control-contracts.ts +0 -382
  140. package/src/delivery-compiler.ts +0 -1805
  141. package/src/delivery-revision.ts +0 -42
  142. package/src/design-control.ts +0 -1299
  143. package/src/design-diagnostics.ts +0 -181
  144. package/src/design-journal.ts +0 -1773
  145. package/src/directory-sync.ts +0 -36
  146. package/src/durable-contracts.ts +0 -227
  147. package/src/durable-workflow.ts +0 -822
  148. package/src/evidence-draft.ts +0 -122
  149. package/src/execution-profile.ts +0 -64
  150. package/src/execution-retention.ts +0 -77
  151. package/src/file-snapshot.ts +0 -163
  152. package/src/implement-continuation.ts +0 -559
  153. package/src/implement-graph.ts +0 -690
  154. package/src/implement-plan.ts +0 -72
  155. package/src/install-config.d.mts +0 -6
  156. package/src/isolation-backend.ts +0 -353
  157. package/src/model-source.ts +0 -22
  158. package/src/openspec-cli.ts +0 -494
  159. package/src/operator-cli-entry.mjs +0 -24
  160. package/src/operator-tools.ts +0 -309
  161. package/src/package-candidate-process.ts +0 -206
  162. package/src/package-candidate.ts +0 -24
  163. package/src/package-delivery.ts +0 -486
  164. package/src/package-state.ts +0 -320
  165. package/src/package-verification.ts +0 -635
  166. package/src/package-workflow.ts +0 -305
  167. package/src/packet-runtime.ts +0 -459
  168. package/src/phase-execution.ts +0 -2163
  169. package/src/pi-adapter.ts +0 -19
  170. package/src/plan-draft-summary.ts +0 -527
  171. package/src/plan-draft.ts +0 -502
  172. package/src/remove-path.ts +0 -32
  173. package/src/run-state.ts +0 -420
  174. package/src/run-store.ts +0 -1179
  175. package/src/safe-path.ts +0 -77
  176. package/src/scoped-grep-worker.mjs +0 -23
  177. package/src/scoped-tools.ts +0 -775
  178. package/src/single-task-draft.ts +0 -142
  179. package/src/sqlite-schema.ts +0 -171
  180. package/src/state-root.ts +0 -215
  181. package/src/storage-schema.ts +0 -360
  182. package/src/subagent-activity.ts +0 -1417
  183. package/src/subagent-process.ts +0 -476
  184. package/src/task-ledger.ts +0 -1338
  185. package/src/transport-budget.ts +0 -50
  186. package/src/verification-capability.ts +0 -1191
  187. package/src/verification-environment-io.ts +0 -389
  188. package/src/verification-environment.ts +0 -162
  189. package/src/verification-identity.ts +0 -77
  190. package/src/verification-scan.ts +0 -64
  191. package/src/windows-job-backend.ts +0 -419
  192. package/src/windows-job.c +0 -149
  193. package/src/workflow-engine.ts +0 -28
  194. package/src/workflow-policy.ts +0 -1670
  195. package/src/workflow-recovery-policy.ts +0 -97
  196. package/src/workflow-scheduling.ts +0 -105
  197. package/src/workflow-state-machine.ts +0 -4341
  198. package/src/workflow-status.ts +0 -576
  199. package/src/workspace-io-entry.mjs +0 -76
  200. package/src/workspace-io-worker.mjs +0 -1312
  201. package/src/workspace-io.ts +0 -89
  202. package/src/workspace-store.ts +0 -634
@@ -1,1670 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { readFileSync } from "node:fs";
3
- import path from "node:path";
4
- import type { WorkflowActivityUpdate } from "./activity-contracts.ts";
5
- import {
6
- type CandidateContextBoundary,
7
- type CandidateContextRef,
8
- classifyCandidateContextRequest,
9
- normalizeCandidateContextRefs,
10
- } from "./candidate-context.ts";
11
- import { compareCanonicalStrings } from "./canonical.ts";
12
- import {
13
- type ApprovalBoundaryCode,
14
- isValidRelativePath,
15
- type StructuredVerificationContract,
16
- verificationBoundInputPaths,
17
- } from "./contracts.ts";
18
- import type { ControlStage } from "./control-contracts.ts";
19
- import type {
20
- GateApprovalProof,
21
- ImplementPlan,
22
- PlanTaskDraft,
23
- } from "./delivery-compiler.ts";
24
- import { canonicalJson } from "./run-state.ts";
25
- import type { OperationLease } from "./run-store.ts";
26
- import type { ResolvedStateRoot } from "./state-root.ts";
27
- import type { WorkspaceEntry } from "./workspace-store.ts";
28
-
29
- export const SHA256 = /^[a-f0-9]{64}$/u;
30
- export const IDENTIFIER = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
31
- export const CHANGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,126}[a-z0-9])?$/u;
32
- export const PACKAGE_NAME =
33
- /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/iu;
34
- export const LOCKFILES = new Set([
35
- "bun.lock",
36
- "bun.lockb",
37
- "package-lock.json",
38
- "pnpm-lock.yaml",
39
- "yarn.lock",
40
- ]);
41
- export const IMPLEMENTATION_ROUTE_LIMITS = Object.freeze({
42
- preferredContextWindow: 128_000,
43
- preferredOutputTokens: 64_000,
44
- minimumContextWindow: 16_000,
45
- minimumOutputTokens: 8_000,
46
- });
47
-
48
- export function implementationRouteRequirements(
49
- input: {
50
- task?: PlanTaskDraft;
51
- artifactCorrection?: RedArtifactCorrection;
52
- repair?: { failureIdentities: string[] };
53
- } = {},
54
- ) {
55
- if (!input.task) {
56
- return {
57
- minContextWindow: IMPLEMENTATION_ROUTE_LIMITS.minimumContextWindow,
58
- minOutputTokens: IMPLEMENTATION_ROUTE_LIMITS.minimumOutputTokens,
59
- };
60
- }
61
- const serializedBytes = Buffer.byteLength(canonicalJson(input.task), "utf8");
62
- const pathCount = Object.values(input.task.phases).reduce(
63
- (total, phase) =>
64
- total + phase.read.length + phase.write.length + phase.delete.length,
65
- 0,
66
- );
67
- const complexity =
68
- serializedBytes +
69
- pathCount * 512 +
70
- (input.artifactCorrection ? 16_384 : 0) +
71
- (input.repair ? input.repair.failureIdentities.length * 256 + 16_384 : 0);
72
- return {
73
- minContextWindow: IMPLEMENTATION_ROUTE_LIMITS.minimumContextWindow,
74
- minOutputTokens: IMPLEMENTATION_ROUTE_LIMITS.minimumOutputTokens,
75
- preferredContextWindow: Math.min(
76
- IMPLEMENTATION_ROUTE_LIMITS.preferredContextWindow,
77
- Math.max(
78
- IMPLEMENTATION_ROUTE_LIMITS.minimumContextWindow,
79
- 16_384 + complexity * 2,
80
- ),
81
- ),
82
- preferredOutputTokens: Math.min(
83
- IMPLEMENTATION_ROUTE_LIMITS.preferredOutputTokens,
84
- Math.max(
85
- IMPLEMENTATION_ROUTE_LIMITS.minimumOutputTokens,
86
- 8_192 + Math.ceil(complexity / 2),
87
- ),
88
- ),
89
- };
90
- }
91
-
92
- export const SEMANTIC_ROUTE_FAILURE_CODES = new Set([
93
- "child-no-structural-submit",
94
- "invalid-structural-result",
95
- "structural-identity-mismatch",
96
- "invalid-diff",
97
- "candidate-diff-invalid",
98
- ]);
99
-
100
- export function semanticRouteFailure(
101
- value: DurableCandidateProposal,
102
- ): string | undefined {
103
- return (value.kind === "retryable" || value.kind === "paused") &&
104
- SEMANTIC_ROUTE_FAILURE_CODES.has(value.code)
105
- ? value.code
106
- : undefined;
107
- }
108
-
109
- export function isCancellationException(
110
- error: unknown,
111
- signal: AbortSignal,
112
- ): boolean {
113
- if (!signal.aborted) return false;
114
- if (error === signal.reason) return true;
115
- return (
116
- error instanceof Error &&
117
- (error.name === "AbortError" ||
118
- error.message === "cancelled" ||
119
- error.message === "operation-cancelled")
120
- );
121
- }
122
-
123
- // These adapters are read-only. Cancellation fences their late settlement;
124
- // mutating operations must instead be awaited through their owned lifecycle.
125
- export async function cancellableRead<T>(
126
- read: () => Promise<T>,
127
- signal?: AbortSignal,
128
- ): Promise<T> {
129
- signal?.throwIfAborted();
130
- if (!signal) return read();
131
- return new Promise<T>((resolve, reject) => {
132
- const onAbort = () => reject(signal.reason);
133
- signal.addEventListener("abort", onAbort, { once: true });
134
- const reading = Promise.resolve().then(() => {
135
- signal.throwIfAborted();
136
- return read();
137
- });
138
- reading
139
- .then(resolve, reject)
140
- .finally(() => signal.removeEventListener("abort", onAbort));
141
- });
142
- }
143
-
144
- export function emitWorkflowActivity(
145
- observer: ((event: WorkflowActivityUpdate) => void) | undefined,
146
- event: WorkflowActivityUpdate,
147
- ): void {
148
- try {
149
- observer?.(structuredClone(event));
150
- } catch {
151
- // Presentation is observational and cannot change workflow behavior.
152
- }
153
- }
154
-
155
- export interface WorkflowWorkspaceFacts {
156
- baselineRevisionId?: string;
157
- currentWorkspaceRevisionId?: string;
158
- }
159
-
160
- export interface WorkflowRouteFacts {
161
- routeId?: string;
162
- routeFingerprint?: string;
163
- }
164
-
165
- export interface WorkflowVerificationStatus {
166
- scope: "baseline" | DurableVerificationScope;
167
- attribution: "pre-existing" | "introduced" | "unresolved" | "environment";
168
- taskId?: string;
169
- failureIdentities?: string[];
170
- }
171
-
172
- export interface WorkflowContextRequest {
173
- code:
174
- | "approved-context-needed"
175
- | "task-split-needed"
176
- | "boundary-review-needed";
177
- refs: CandidateContextRef[];
178
- }
179
-
180
- export interface RedArtifactCorrection {
181
- code: "red-artifact-constraint";
182
- attempt: number;
183
- maxAttempts: number;
184
- contextRequest?: WorkflowContextRequest;
185
- }
186
-
187
- export type {
188
- RecoveryActionDecision,
189
- RecoveryActionRequest,
190
- WorkflowRecoveryFact,
191
- WorkflowRecoveryFeedback,
192
- } from "./workflow-recovery-policy.ts";
193
- export { decideRecoveryAction } from "./workflow-recovery-policy.ts";
194
-
195
- import type {
196
- RecoveryActionDecision,
197
- RecoveryActionRequest,
198
- WorkflowRecoveryFact,
199
- WorkflowRecoveryFeedback,
200
- } from "./workflow-recovery-policy.ts";
201
-
202
- export const APPROVAL_AUTHORITY_CATEGORIES = [
203
- "observable-behavior",
204
- "architecture-policy",
205
- "dependency",
206
- "path-boundary",
207
- "conflict-resource",
208
- "verification-contract",
209
- "agents-contract",
210
- "irreversible-scope",
211
- ] as const;
212
-
213
- export type ApprovalAuthorityCategory =
214
- (typeof APPROVAL_AUTHORITY_CATEGORIES)[number];
215
-
216
- export interface ApprovalRequirement {
217
- category: ApprovalAuthorityCategory;
218
- requiredGates: Array<"gate-a" | "gate-b">;
219
- refs: string[];
220
- }
221
-
222
- export const INTERNAL_APPROVAL_CODES = [
223
- "boundary-review-needed",
224
- "repair-boundary-expansion",
225
- "task-write-set-empty",
226
- ] as const;
227
-
228
- export type InternalApprovalCode = (typeof INTERNAL_APPROVAL_CODES)[number];
229
- export type WorkflowApprovalCode = ApprovalBoundaryCode | InternalApprovalCode;
230
-
231
- // These pauses need a new executable plan, not a new user decision. Environment,
232
- // integrity and cancellation codes deliberately do not grant this continuation.
233
- export function permitsPlanAmendment(code: string): boolean {
234
- return ["needs-task-split", "task-split-needed"].includes(code);
235
- }
236
-
237
- export type ParentRecoveryStrategy =
238
- | "inspect-exhausted-correction"
239
- | "inspect-route-availability"
240
- | "restore-runtime-environment"
241
- | "restore-verification-capability";
242
-
243
- const ROUTE_RECOVERY_CODES = new Set(["child-timeout", "transport-failure"]);
244
-
245
- const ENVIRONMENT_RECOVERY_CODES = new Set([
246
- "bubblewrap-launch-failed",
247
- "bubblewrap-or-dependency-unavailable",
248
- "bun-executable-unavailable",
249
- "checkpoint-unavailable",
250
- "checkout-cleanup-failed",
251
- "checkout-failed",
252
- "child-provider-authentication-failed",
253
- "clone-failed",
254
- "git-apply-check-unavailable",
255
- "git-apply-unavailable",
256
- "git-index-unavailable",
257
- "isolation-backend-launch-failed",
258
- "isolation-backend-unavailable",
259
- "local-executable-missing",
260
- "root-unavailable",
261
- "runner-missing",
262
- "sandbox-runtime-unavailable",
263
- "script-missing",
264
- "verification-environment-changed",
265
- "verification-environment-requires-trusted-mode",
266
- "verification-environment-unavailable",
267
- "verification-runner-launch-failed",
268
- "workspace-dependency-missing",
269
- ]);
270
-
271
- const CORRECTION_RECOVERY_CODES = new Set([
272
- "child-timeout",
273
- "transport-failure",
274
- "artifact-attempts-exhausted",
275
- "baseline-deletion-drift",
276
- "baseline-directory-drift",
277
- "baseline-file-drift",
278
- "candidate-check-failed",
279
- "candidate-diff-invalid",
280
- "child-no-structural-submit",
281
- "git-apply-check-failed",
282
- "invalid-diff",
283
- "invalid-diff-bytes",
284
- "invalid-structural-result",
285
- "needs-task-split",
286
- "parent-review-rejected",
287
- "red-artifact-constraint",
288
- "red-not-witnessed",
289
- "repair-attempts-exhausted",
290
- "stale-snapshot",
291
- "structural-identity-mismatch",
292
- "task-split-needed",
293
- "verification-rejected",
294
- "verification-report-invalid",
295
- "verification-report-missing",
296
- "write-set-mismatch",
297
- "workspace-revision-stale",
298
- ]);
299
-
300
- const RECOVERY_AMENDMENT_CODES = new Set([
301
- "artifact-attempts-exhausted",
302
- "candidate-check-failed",
303
- "candidate-diff-invalid",
304
- "child-no-structural-submit",
305
- "invalid-diff",
306
- "invalid-diff-bytes",
307
- "invalid-structural-result",
308
- "needs-task-split",
309
- "red-artifact-constraint",
310
- "red-not-witnessed",
311
- "repair-attempts-exhausted",
312
- "structural-identity-mismatch",
313
- "task-split-needed",
314
- "verification-rejected",
315
- "write-set-mismatch",
316
- ]);
317
-
318
- function hasCurrentExhaustedRecovery(
319
- code: string,
320
- diagnostic: SafeAttemptDiagnostic | undefined,
321
- ): diagnostic is SafeAttemptDiagnostic & { recovery: WorkflowRecoveryFact } {
322
- const recovery = diagnostic?.recovery;
323
- return (
324
- recovery !== undefined &&
325
- recovery.failures >= recovery.feedback.maxAttempts &&
326
- CORRECTION_RECOVERY_CODES.has(recovery.feedback.code) &&
327
- (code === recovery.feedback.code ||
328
- ["artifact-attempts-exhausted", "repair-attempts-exhausted"].includes(
329
- code,
330
- ))
331
- );
332
- }
333
-
334
- /**
335
- * Classify only code-owned, locally actionable recovery evidence. The result is
336
- * parent guidance, never new command or acceptance authority.
337
- */
338
- export function parentRecoveryStrategy(
339
- code: string,
340
- diagnostic: SafeAttemptDiagnostic | undefined,
341
- ): ParentRecoveryStrategy | undefined {
342
- const prerequisite = normalizeVerificationPrerequisite(
343
- diagnostic?.prerequisite,
344
- );
345
- if (prerequisite) {
346
- return prerequisite.cause === "capability" &&
347
- ENVIRONMENT_RECOVERY_CODES.has(code)
348
- ? "restore-verification-capability"
349
- : undefined;
350
- }
351
- if (hasCurrentExhaustedRecovery(code, diagnostic))
352
- return "inspect-exhausted-correction";
353
- if (ROUTE_RECOVERY_CODES.has(code)) return "inspect-route-availability";
354
- if (ENVIRONMENT_RECOVERY_CODES.has(code))
355
- return "restore-runtime-environment";
356
- return undefined;
357
- }
358
-
359
- /** A failed candidate/repair may be decomposed only from retained exhaustion evidence. */
360
- export function permitsRecoveryAmendment(
361
- code: string,
362
- diagnostic: SafeAttemptDiagnostic | undefined,
363
- ): boolean {
364
- return (
365
- hasCurrentExhaustedRecovery(code, diagnostic) &&
366
- diagnostic.recovery.feedback.strategy !== "refresh-candidate" &&
367
- RECOVERY_AMENDMENT_CODES.has(diagnostic.recovery.feedback.code)
368
- );
369
- }
370
-
371
- /** Parent-verifier facts about an original-revision obligation. */
372
- export interface WorkflowVerificationPrerequisite {
373
- kind: "verification-prerequisite";
374
- scope: "baseline-task-affected" | "baseline-full-suite";
375
- cause:
376
- | "future-output"
377
- | "missing-input"
378
- | "unsafe-input"
379
- | "capability"
380
- | "unknown";
381
- verificationId: string;
382
- contractIdentity: string;
383
- originalRevisionId: string;
384
- environmentIdentity: string;
385
- taskId?: string;
386
- input?: { path: string; kind: "absent" | "unsafe" };
387
- producer?: { taskId: string; phase: "red" | "green" | "refactor" };
388
- }
389
-
390
- export function normalizeVerificationPrerequisite(
391
- value: unknown,
392
- ): WorkflowVerificationPrerequisite | undefined {
393
- if (
394
- !isRecord(value) ||
395
- value.kind !== "verification-prerequisite" ||
396
- !["baseline-task-affected", "baseline-full-suite"].includes(
397
- String(value.scope),
398
- ) ||
399
- ![
400
- "future-output",
401
- "missing-input",
402
- "unsafe-input",
403
- "capability",
404
- "unknown",
405
- ].includes(String(value.cause)) ||
406
- typeof value.verificationId !== "string" ||
407
- !IDENTIFIER.test(value.verificationId) ||
408
- typeof value.contractIdentity !== "string" ||
409
- !SHA256.test(value.contractIdentity) ||
410
- typeof value.originalRevisionId !== "string" ||
411
- !SHA256.test(value.originalRevisionId) ||
412
- typeof value.environmentIdentity !== "string" ||
413
- !(
414
- SHA256.test(value.environmentIdentity) ||
415
- value.environmentIdentity === "unobserved"
416
- ) ||
417
- (value.taskId !== undefined &&
418
- (typeof value.taskId !== "string" || !IDENTIFIER.test(value.taskId)))
419
- )
420
- return undefined;
421
- if (
422
- value.input !== undefined &&
423
- (!isRecord(value.input) ||
424
- !isValidRelativePath(value.input.path) ||
425
- !["absent", "unsafe"].includes(String(value.input.kind)))
426
- )
427
- return undefined;
428
- if (
429
- value.producer !== undefined &&
430
- (!isRecord(value.producer) ||
431
- typeof value.producer.taskId !== "string" ||
432
- !IDENTIFIER.test(value.producer.taskId) ||
433
- !["red", "green", "refactor"].includes(String(value.producer.phase)))
434
- )
435
- return undefined;
436
- if (
437
- (value.cause === "future-output" &&
438
- (!value.producer ||
439
- !isRecord(value.input) ||
440
- value.input.kind !== "absent")) ||
441
- (value.cause === "missing-input" &&
442
- (!isRecord(value.input) || value.input.kind !== "absent")) ||
443
- (value.cause === "unsafe-input" &&
444
- (!isRecord(value.input) || value.input.kind !== "unsafe"))
445
- )
446
- return undefined;
447
- return {
448
- kind: "verification-prerequisite",
449
- scope: value.scope as WorkflowVerificationPrerequisite["scope"],
450
- cause: value.cause as WorkflowVerificationPrerequisite["cause"],
451
- verificationId: value.verificationId,
452
- contractIdentity: value.contractIdentity,
453
- originalRevisionId: value.originalRevisionId,
454
- environmentIdentity: value.environmentIdentity,
455
- ...(typeof value.taskId === "string" ? { taskId: value.taskId } : {}),
456
- ...(isRecord(value.input)
457
- ? {
458
- input: {
459
- path: value.input.path as string,
460
- kind: value.input.kind as "absent" | "unsafe",
461
- },
462
- }
463
- : {}),
464
- ...(isRecord(value.producer)
465
- ? {
466
- producer: {
467
- taskId: value.producer.taskId as string,
468
- phase: value.producer.phase as "red" | "green" | "refactor",
469
- },
470
- }
471
- : {}),
472
- };
473
- }
474
-
475
- export function permitsPrerequisiteAmendment(value: unknown): boolean {
476
- const fact = normalizeVerificationPrerequisite(value);
477
- return (
478
- fact !== undefined &&
479
- ["future-output", "missing-input"].includes(fact.cause)
480
- );
481
- }
482
-
483
- export const APPROVAL_REQUIREMENT_BY_CODE = {
484
- "agents-contract-insufficient": {
485
- category: "agents-contract",
486
- requiredGates: ["gate-b"],
487
- },
488
- "architecture-contract-insufficient": {
489
- category: "architecture-policy",
490
- requiredGates: ["gate-b"],
491
- },
492
- "behavior-contract-insufficient": {
493
- category: "observable-behavior",
494
- requiredGates: ["gate-a", "gate-b"],
495
- },
496
- "boundary-review-needed": {
497
- category: "path-boundary",
498
- requiredGates: ["gate-b"],
499
- },
500
- "conflict-resource-authority-insufficient": {
501
- category: "conflict-resource",
502
- requiredGates: ["gate-b"],
503
- },
504
- "irreversible-scope-insufficient": {
505
- category: "irreversible-scope",
506
- requiredGates: ["gate-a", "gate-b"],
507
- },
508
- "repair-boundary-expansion": {
509
- category: "path-boundary",
510
- requiredGates: ["gate-b"],
511
- },
512
- "task-scope-insufficient": {
513
- category: "path-boundary",
514
- requiredGates: ["gate-b"],
515
- },
516
- "task-write-set-empty": {
517
- category: "path-boundary",
518
- requiredGates: ["gate-b"],
519
- },
520
- "unapproved-dependency-change": {
521
- category: "dependency",
522
- requiredGates: ["gate-b"],
523
- },
524
- "verification-contract-insufficient": {
525
- category: "verification-contract",
526
- requiredGates: ["gate-b"],
527
- },
528
- } as const satisfies Record<
529
- WorkflowApprovalCode,
530
- {
531
- category: ApprovalAuthorityCategory;
532
- requiredGates: readonly ("gate-a" | "gate-b")[];
533
- }
534
- >;
535
-
536
- export type WorkflowRetryPolicy =
537
- | "artifact"
538
- | "stale"
539
- | "verification"
540
- | "checkpoint";
541
-
542
- export type WorkflowAttemptOutcome = (
543
- | {
544
- kind: "phase-committed";
545
- artifactHash: string;
546
- isolatedRevisionId: string;
547
- exitCode: number;
548
- classification: "expected-red" | "expected-green" | "expected-refactor";
549
- }
550
- | {
551
- kind: "paused" | "retryable" | "approval-needed";
552
- code: string;
553
- untrustedCandidate?: Uint8Array;
554
- verification?: WorkflowVerificationStatus;
555
- prerequisite?: WorkflowVerificationPrerequisite;
556
- contextRequest?: WorkflowContextRequest;
557
- retryPolicy?: WorkflowRetryPolicy;
558
- }
559
- | { kind: "operation-cancelled"; code: "cancelled" }
560
- ) &
561
- WorkflowWorkspaceFacts &
562
- WorkflowRouteFacts & {
563
- attemptDiagnostic?: SafeAttemptDiagnostic;
564
- };
565
-
566
- export interface WorkflowWorker {
567
- /** Baseline admission before reserving Worker work; never launches a child. */
568
- prepareTask?(
569
- input: Parameters<WorkflowWorker["runAttempt"]>[0],
570
- ): Promise<
571
- ({ kind: "prepared" } & WorkflowWorkspaceFacts) | WorkflowAttemptOutcome
572
- >;
573
- hasPendingVerification?(
574
- input: Parameters<WorkflowWorker["runAttempt"]>[0],
575
- ): Promise<boolean>;
576
- isContextReadAvailable?(input: {
577
- runId: string;
578
- deliveryRevision: number;
579
- plan: ImplementPlan;
580
- baselineRevisionId: string;
581
- currentWorkspaceRevisionId: string;
582
- path: string;
583
- }): boolean;
584
- runAttempt(input: {
585
- runId: string;
586
- operationId: string;
587
- deliveryRevision: number;
588
- taskId: string;
589
- phase: "red" | "green" | "refactor";
590
- task: PlanTaskDraft;
591
- plan: ImplementPlan;
592
- routeId?: string;
593
- routeFingerprint?: string;
594
- baselineRevisionId?: string;
595
- currentWorkspaceRevisionId?: string;
596
- repair?: {
597
- attribution: "introduced";
598
- failureIdentities: string[];
599
- };
600
- artifactCorrection?: RedArtifactCorrection;
601
- contextRequest?: WorkflowContextRequest;
602
- contextReadPaths?: string[];
603
- recoveryFeedback?: WorkflowRecoveryFeedback;
604
- additionalAttempt?: boolean;
605
- verificationOnly?: boolean;
606
- reserveCandidate?: () => boolean;
607
- recoveryDecision?: (
608
- request: RecoveryActionRequest,
609
- ) => RecoveryActionDecision;
610
- signal: AbortSignal;
611
- onActivity?: (
612
- event: Pick<
613
- WorkflowActivityUpdate,
614
- "state" | "code" | "attempt" | "maxAttempts" | "wait"
615
- >,
616
- ) => void;
617
- }): Promise<WorkflowAttemptOutcome>;
618
- rebind(input: {
619
- runId: string;
620
- taskId: string;
621
- task: PlanTaskDraft;
622
- role: "implementation-worker";
623
- routeId: string;
624
- }):
625
- | {
626
- ok: true;
627
- routeId?: string;
628
- routeFingerprint?: string;
629
- route?: { id?: string; fingerprint?: string };
630
- }
631
- | { ok: false; code: string };
632
- revalidateDelivery?(input: {
633
- runId: string;
634
- deliveryRevision: number;
635
- plan: ImplementPlan;
636
- tasks: Array<{
637
- taskId: string;
638
- deliveryRevision: number;
639
- state: string;
640
- phase: "red" | "green" | "refactor";
641
- contextReadPaths?: string[];
642
- }>;
643
- invalidatedTaskIds: string[];
644
- baselineRevisionId: string;
645
- currentWorkspaceRevisionId: string;
646
- }): {
647
- invalidatedTaskIds: string[];
648
- baselineRevisionId: string;
649
- currentWorkspaceRevisionId: string;
650
- };
651
- }
652
-
653
- export interface WorkflowDelivery {
654
- gate: "gate-a" | "gate-b";
655
- revision: number;
656
- receiptHash: string;
657
- plan: ImplementPlan;
658
- approvalProofs?: {
659
- gateA: GateApprovalProof;
660
- gateB: GateApprovalProof;
661
- };
662
- }
663
-
664
- export interface WorkflowAvailableDelivery {
665
- deliveryRevision: number;
666
- receiptHash: string;
667
- }
668
-
669
- export interface WorkflowDeliverySource {
670
- load(input: {
671
- stage: ControlStage;
672
- change: string;
673
- signal?: AbortSignal;
674
- deliveryRevision?: number;
675
- receiptHash?: string;
676
- }): Promise<WorkflowDelivery>;
677
- discoverLatest?(input: {
678
- stage: ControlStage;
679
- change: string;
680
- signal?: AbortSignal;
681
- }): Promise<WorkflowAvailableDelivery | undefined>;
682
- }
683
-
684
- export interface WorkflowChangeVerifier {
685
- verify(input: {
686
- taskEvidence?: Array<{ taskId: string; deliveryRevision: number }>;
687
- runId: string;
688
- deliveryRevision: number;
689
- plan: ImplementPlan;
690
- baselineRevisionId?: string;
691
- currentWorkspaceRevisionId?: string;
692
- signal: AbortSignal;
693
- }): Promise<
694
- | {
695
- kind: "verified";
696
- verificationId: string;
697
- currentWorkspaceRevisionId?: string;
698
- }
699
- | {
700
- kind: "paused" | "retryable" | "approval-needed";
701
- code: string;
702
- verification?: WorkflowVerificationStatus;
703
- }
704
- >;
705
- }
706
-
707
- export interface WorkflowApplication {
708
- prepare?(input: {
709
- transactionId: string;
710
- runId: string;
711
- consumerRoot: string;
712
- deliveryRevision: number;
713
- plan: ImplementPlan;
714
- contextReadPaths?: string[];
715
- baselineRevisionId?: string;
716
- currentWorkspaceRevisionId?: string;
717
- signal: AbortSignal;
718
- }): Promise<Record<string, unknown>>;
719
- applyPrepared?(input: {
720
- transactionId: string;
721
- runId: string;
722
- consumerRoot: string;
723
- deliveryRevision: number;
724
- plan: ImplementPlan;
725
- contextReadPaths?: string[];
726
- baselineRevisionId?: string;
727
- currentWorkspaceRevisionId?: string;
728
- signal: AbortSignal;
729
- }): Promise<Record<string, unknown>>;
730
- begin(input: {
731
- transactionId: string;
732
- runId: string;
733
- consumerRoot: string;
734
- deliveryRevision: number;
735
- plan: ImplementPlan;
736
- contextReadPaths?: string[];
737
- baselineRevisionId?: string;
738
- currentWorkspaceRevisionId?: string;
739
- signal: AbortSignal;
740
- }): Promise<Record<string, unknown>>;
741
- requestControl(
742
- transactionId: string,
743
- intent: "cancel" | "discard",
744
- context?: WorkflowApplicationContext,
745
- ): Record<string, unknown>;
746
- recover(
747
- transactionId: string,
748
- context?: WorkflowApplicationContext,
749
- ): Promise<Record<string, unknown>>;
750
- }
751
-
752
- export interface WorkflowApplicationContext {
753
- runId: string;
754
- deliveryRevision: number;
755
- plan: ImplementPlan;
756
- contextReadPaths?: string[];
757
- baselineRevisionId?: string;
758
- currentWorkspaceRevisionId?: string;
759
- }
760
-
761
- export interface WorkflowRunLifecycle {
762
- cleanup(runId: string): void;
763
- close?(): void;
764
- }
765
-
766
- export interface WorkflowEngineOptions {
767
- consumerRoot: string;
768
- stateRoot: ResolvedStateRoot;
769
- deliverySource: WorkflowDeliverySource;
770
- worker: WorkflowWorker;
771
- changeVerifier: WorkflowChangeVerifier;
772
- application?: WorkflowApplication;
773
- lifecycle?: WorkflowRunLifecycle;
774
- now?: () => number;
775
- leaseTtlMs?: number;
776
- workHardLimit?: number;
777
- }
778
-
779
- export interface EngineRunRow {
780
- current_revision: number | null;
781
- baseline_workspace_revision: string | null;
782
- current_workspace_revision: string | null;
783
- cleanup_state: "none" | "retained" | "complete";
784
- route_id: string | null;
785
- route_fingerprint: string | null;
786
- transaction_id: string | null;
787
- verification_json: string | null;
788
- delivery_diagnostics_json: string | null;
789
- next_queue_position: number;
790
- }
791
-
792
- export interface EngineTaskRow {
793
- task_id: string;
794
- task_order: number;
795
- delivery_revision: number;
796
- plan_json: string;
797
- state: string;
798
- phase: "red" | "green" | "refactor";
799
- pause_code: string | null;
800
- context_request_json: string | null;
801
- attempt_diagnostic_json: string | null;
802
- route_id: string | null;
803
- route_fingerprint: string | null;
804
- queue_position: number | null;
805
- }
806
-
807
- export interface SafeAttemptDiagnostic {
808
- fingerprint?: string;
809
- finalCategory?: string;
810
- submitAttempts?: number;
811
- schema?: string;
812
- identityMismatch?: string[];
813
- sameFailureCount?: number;
814
- action?: "rebind-or-revise-delivery";
815
- recovery?: WorkflowRecoveryFact;
816
- prerequisite?: WorkflowVerificationPrerequisite;
817
- }
818
-
819
- export function recoveryKey(
820
- task: PlanTaskDraft,
821
- phase: string,
822
- _row?: EngineTaskRow,
823
- _run?: EngineRunRow,
824
- ): string {
825
- // Keep the Red witness and the active phase's obligation across replanning.
826
- // Execution identities, verifier names, and wording are not progress.
827
- const obligation = (boundary: PlanTaskDraft["phases"]["red"]) => {
828
- const verification = structuredClone(
829
- boundary.verification,
830
- ) as unknown as Record<string, unknown>;
831
- delete verification.id;
832
- return {
833
- verification,
834
- verificationInputs: boundary.verificationInputs
835
- .map((binding) => canonicalJson(binding))
836
- .sort(compareCanonicalStrings),
837
- };
838
- };
839
- const boundary = task.phases[phase as keyof PlanTaskDraft["phases"]];
840
- if (!boundary) throw new Error("workflow-task-phase-invalid");
841
- return hash(
842
- canonicalJson({
843
- red: obligation(task.phases.red),
844
- phase: obligation(boundary),
845
- }),
846
- phase,
847
- );
848
- }
849
-
850
- /** Original-baseline recovery belongs to a stable execution boundary, not its label. */
851
- export function baselineRecoveryKey(
852
- task: PlanTaskDraft,
853
- phase: string,
854
- ): string {
855
- return hash(
856
- "baseline-recovery-owner-v1",
857
- recoveryKey(task, phase),
858
- canonicalJson(
859
- Object.fromEntries(
860
- Object.entries(task.phases).map(([name, boundary]) => [
861
- name,
862
- Object.fromEntries(
863
- (["read", "write", "delete"] as const).map((access) => [
864
- access,
865
- [...new Set(boundary[access])].sort(compareCanonicalStrings),
866
- ]),
867
- ),
868
- ]),
869
- ),
870
- ),
871
- );
872
- }
873
-
874
- export interface EngineOperationRow {
875
- command: string;
876
- state: string;
877
- outcome_json: string | null;
878
- }
879
-
880
- export interface ActiveOperation {
881
- operationId: string;
882
- kind: "work" | "applying";
883
- controller: AbortController;
884
- settled: Promise<Record<string, unknown>>;
885
- }
886
-
887
- export interface HeldOperationLease {
888
- lease: OperationLease;
889
- timer: ReturnType<typeof setInterval>;
890
- error?: Error;
891
- }
892
-
893
- export interface BootstrapRow {
894
- handoff_id: string;
895
- owner_run_id: string;
896
- receipt_hash: string;
897
- state: string;
898
- facts_json: string;
899
- }
900
-
901
- export function isRecord(value: unknown): value is Record<string, unknown> {
902
- return value !== null && typeof value === "object" && !Array.isArray(value);
903
- }
904
-
905
- export function normalizeWorkflowVerificationStatus(
906
- value: unknown,
907
- ): WorkflowVerificationStatus {
908
- if (
909
- !isRecord(value) ||
910
- typeof value.scope !== "string" ||
911
- ![
912
- "baseline",
913
- "baseline-task-affected",
914
- "baseline-full-suite",
915
- "task-affected",
916
- "change-task-affected",
917
- "change-full-suite",
918
- "agents-checkpoint",
919
- "post-apply",
920
- ].includes(value.scope) ||
921
- !["pre-existing", "introduced", "unresolved", "environment"].includes(
922
- String(value.attribution),
923
- ) ||
924
- (value.taskId !== undefined && typeof value.taskId !== "string") ||
925
- (value.failureIdentities !== undefined &&
926
- (!Array.isArray(value.failureIdentities) ||
927
- value.failureIdentities.length > 256 ||
928
- value.failureIdentities.some(
929
- (identity) => typeof identity !== "string" || !SHA256.test(identity),
930
- )))
931
- ) {
932
- throw new Error("workflow-verification-status-invalid");
933
- }
934
- return {
935
- scope: value.scope as WorkflowVerificationStatus["scope"],
936
- attribution: value.attribution as WorkflowVerificationStatus["attribution"],
937
- ...(typeof value.taskId === "string" ? { taskId: value.taskId } : {}),
938
- ...(Array.isArray(value.failureIdentities)
939
- ? {
940
- failureIdentities: [
941
- ...new Set(value.failureIdentities as string[]),
942
- ].sort(),
943
- }
944
- : {}),
945
- };
946
- }
947
-
948
- export function normalizeWorkflowContextRequest(
949
- value: unknown,
950
- ): WorkflowContextRequest {
951
- if (
952
- !isRecord(value) ||
953
- ![
954
- "approved-context-needed",
955
- "task-split-needed",
956
- "boundary-review-needed",
957
- ].includes(String(value.code))
958
- ) {
959
- throw new Error("workflow-context-request-invalid");
960
- }
961
- let refs: CandidateContextRef[];
962
- try {
963
- refs = normalizeCandidateContextRefs(value.refs);
964
- } catch {
965
- throw new Error("workflow-context-request-invalid");
966
- }
967
- return {
968
- code: value.code as WorkflowContextRequest["code"],
969
- refs,
970
- };
971
- }
972
-
973
- export function contextApprovalRefs(request: WorkflowContextRequest): string[] {
974
- return request.refs.flatMap((ref) =>
975
- ref.kind === "requested-path" ? [ref.path] : [],
976
- );
977
- }
978
-
979
- export function contextBoundaryForTask(
980
- task: PlanTaskDraft,
981
- phase: "red" | "green" | "refactor",
982
- ): CandidateContextBoundary {
983
- const boundary = task.phases[phase];
984
- if (!boundary) throw new Error("workflow-task-phase-invalid");
985
- return {
986
- phase,
987
- contextReadRoots: task.roots,
988
- readPaths: boundary.read,
989
- writePaths: boundary.write,
990
- deletePaths: boundary.delete,
991
- taskPaths: [
992
- ...new Set(
993
- Object.values(task.phases).flatMap((candidate) => [
994
- ...candidate.read,
995
- ...candidate.write,
996
- ...candidate.delete,
997
- ]),
998
- ),
999
- ],
1000
- redWritePaths: task.phases.red.write,
1001
- agents: {
1002
- impact: task.agents.impact,
1003
- ...(task.agents.target ? { target: task.agents.target } : {}),
1004
- },
1005
- };
1006
- }
1007
-
1008
- export function classifyPersistedContextRequest(
1009
- request: WorkflowContextRequest,
1010
- task: PlanTaskDraft,
1011
- phase: "red" | "green" | "refactor",
1012
- ) {
1013
- return classifyCandidateContextRequest(
1014
- {
1015
- kind: "context-request",
1016
- candidateId: "candidate-durable-reclassification",
1017
- code: request.code,
1018
- refs: request.refs,
1019
- },
1020
- contextBoundaryForTask(task, phase),
1021
- );
1022
- }
1023
-
1024
- export function approvalRequirement(
1025
- code: string,
1026
- contextRequest?: WorkflowContextRequest,
1027
- ): ApprovalRequirement {
1028
- const requirement = Object.hasOwn(APPROVAL_REQUIREMENT_BY_CODE, code)
1029
- ? APPROVAL_REQUIREMENT_BY_CODE[
1030
- code as keyof typeof APPROVAL_REQUIREMENT_BY_CODE
1031
- ]
1032
- : undefined;
1033
- if (!requirement) throw new Error("approval-code-invalid");
1034
- return {
1035
- category: requirement.category,
1036
- requiredGates: [...requirement.requiredGates],
1037
- refs: contextRequest ? contextApprovalRefs(contextRequest) : [],
1038
- };
1039
- }
1040
-
1041
- export function isWorkflowApprovalCode(
1042
- code: string,
1043
- ): code is WorkflowApprovalCode {
1044
- return Object.hasOwn(APPROVAL_REQUIREMENT_BY_CODE, code);
1045
- }
1046
-
1047
- export function hash(...values: string[]): string {
1048
- const digest = createHash("sha256");
1049
- for (const value of values) {
1050
- digest.update(`${Buffer.byteLength(value)}:`);
1051
- digest.update(value);
1052
- }
1053
- return digest.digest("hex");
1054
- }
1055
-
1056
- export interface DependencyContractEntry {
1057
- value: string;
1058
- names: string[];
1059
- }
1060
-
1061
- export class DependencyManifestError extends Error {
1062
- constructor() {
1063
- super("package-manifest-invalid");
1064
- this.name = "DependencyManifestError";
1065
- }
1066
- }
1067
-
1068
- export function dependencyName(selector: string): string | null {
1069
- const scoped = /(@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*)/iu.exec(
1070
- selector,
1071
- )?.[1];
1072
- if (scoped) return scoped;
1073
- const segment = selector.split("/").at(-1) ?? "";
1074
- const name = segment.replace(/@.*$/u, "");
1075
- return PACKAGE_NAME.test(name) ? name : null;
1076
- }
1077
-
1078
- export function addDependencyResolutionEntries(
1079
- result: Map<string, DependencyContractEntry>,
1080
- section: string,
1081
- value: unknown,
1082
- selectorPath: string[] = [],
1083
- names: string[] = [],
1084
- ): void {
1085
- if (isRecord(value)) {
1086
- for (const [key, entry] of Object.entries(value)) {
1087
- const name = dependencyName(key);
1088
- addDependencyResolutionEntries(
1089
- result,
1090
- section,
1091
- entry,
1092
- [...selectorPath, key],
1093
- name && key !== "." ? [...new Set([...names, name])] : names,
1094
- );
1095
- }
1096
- return;
1097
- }
1098
- result.set(`${section}:${selectorPath.join(":")}`, {
1099
- value: canonicalJson(value),
1100
- names,
1101
- });
1102
- }
1103
-
1104
- export function addDependencyListPolicy(
1105
- result: Map<string, DependencyContractEntry>,
1106
- section: string,
1107
- value: unknown,
1108
- ): void {
1109
- if (value === undefined) return;
1110
- if (
1111
- !Array.isArray(value) ||
1112
- value.some(
1113
- (name) => typeof name !== "string" || !PACKAGE_NAME.test(name),
1114
- ) ||
1115
- new Set(value).size !== value.length
1116
- ) {
1117
- throw new DependencyManifestError();
1118
- }
1119
- result.set(section, {
1120
- value: canonicalJson([...value].sort()),
1121
- names: [],
1122
- });
1123
- }
1124
-
1125
- export function addPatchedDependencyPolicy(
1126
- result: Map<string, DependencyContractEntry>,
1127
- section: string,
1128
- value: unknown,
1129
- ): void {
1130
- if (value === undefined) return;
1131
- if (!isRecord(value)) throw new DependencyManifestError();
1132
- for (const [selector, patchPath] of Object.entries(value)) {
1133
- if (
1134
- !dependencyName(selector) ||
1135
- typeof patchPath !== "string" ||
1136
- !isValidRelativePath(patchPath)
1137
- ) {
1138
- throw new DependencyManifestError();
1139
- }
1140
- }
1141
- result.set(section, { value: canonicalJson(value), names: [] });
1142
- }
1143
-
1144
- export function dependencyContract(
1145
- root: string,
1146
- ): Map<string, DependencyContractEntry> {
1147
- let manifest: unknown;
1148
- try {
1149
- manifest = JSON.parse(
1150
- readFileSync(path.join(root, "package.json"), "utf8"),
1151
- );
1152
- } catch {
1153
- throw new DependencyManifestError();
1154
- }
1155
- if (!isRecord(manifest)) throw new DependencyManifestError();
1156
-
1157
- const result = new Map<string, DependencyContractEntry>();
1158
- for (const section of [
1159
- "dependencies",
1160
- "devDependencies",
1161
- "peerDependencies",
1162
- "optionalDependencies",
1163
- ]) {
1164
- const dependencies = manifest[section];
1165
- if (dependencies === undefined) continue;
1166
- if (!isRecord(dependencies)) throw new DependencyManifestError();
1167
- for (const [name, version] of Object.entries(dependencies)) {
1168
- if (typeof version !== "string") throw new DependencyManifestError();
1169
- result.set(`${section}:${name}`, { value: version, names: [name] });
1170
- }
1171
- }
1172
- for (const section of [
1173
- "overrides",
1174
- "resolutions",
1175
- "dependenciesMeta",
1176
- "peerDependenciesMeta",
1177
- ]) {
1178
- if (manifest[section] !== undefined) {
1179
- addDependencyResolutionEntries(result, section, manifest[section]);
1180
- }
1181
- }
1182
- for (const section of ["workspaces", "catalog", "catalogs"]) {
1183
- if (manifest[section] !== undefined) {
1184
- result.set(section, {
1185
- value: canonicalJson(manifest[section]),
1186
- names: [],
1187
- });
1188
- }
1189
- }
1190
- for (const section of [
1191
- "trustedDependencies",
1192
- "blockedDependencies",
1193
- "bundledDependencies",
1194
- "bundleDependencies",
1195
- ]) {
1196
- addDependencyListPolicy(result, section, manifest[section]);
1197
- }
1198
- addPatchedDependencyPolicy(
1199
- result,
1200
- "patchedDependencies",
1201
- manifest.patchedDependencies,
1202
- );
1203
-
1204
- const pnpm = manifest.pnpm;
1205
- if (pnpm !== undefined) {
1206
- if (!isRecord(pnpm)) throw new DependencyManifestError();
1207
- const resolutionSections = [
1208
- "overrides",
1209
- "packageExtensions",
1210
- "peerDependencyRules",
1211
- "allowedDeprecatedVersions",
1212
- ];
1213
- for (const section of resolutionSections) {
1214
- if (pnpm[section] !== undefined) {
1215
- addDependencyResolutionEntries(
1216
- result,
1217
- `pnpm.${section}`,
1218
- pnpm[section],
1219
- );
1220
- }
1221
- }
1222
- addPatchedDependencyPolicy(
1223
- result,
1224
- "pnpm.patchedDependencies",
1225
- pnpm.patchedDependencies,
1226
- );
1227
- const tracked = new Set([...resolutionSections, "patchedDependencies"]);
1228
- const policy = Object.fromEntries(
1229
- Object.entries(pnpm).filter(([key]) => !tracked.has(key)),
1230
- );
1231
- if (Object.keys(policy).length > 0) {
1232
- result.set("pnpm.policy", { value: canonicalJson(policy), names: [] });
1233
- }
1234
- }
1235
- return result;
1236
- }
1237
-
1238
- export function hasUnapprovedDependencyChange(
1239
- before: Map<string, DependencyContractEntry>,
1240
- after: Map<string, DependencyContractEntry>,
1241
- approvedDependencies: readonly string[],
1242
- lockfileChanged: boolean,
1243
- ): boolean {
1244
- if (lockfileChanged) return true;
1245
- const approved = new Set(approvedDependencies);
1246
- for (const key of new Set([...before.keys(), ...after.keys()])) {
1247
- const previous = before.get(key);
1248
- const next = after.get(key);
1249
- if (previous?.value === next?.value) continue;
1250
- const names = new Set([...(previous?.names ?? []), ...(next?.names ?? [])]);
1251
- if (names.size === 0 || [...names].some((name) => !approved.has(name))) {
1252
- return true;
1253
- }
1254
- }
1255
- return false;
1256
- }
1257
-
1258
- export function parseJsonRecord(value: string): Record<string, unknown> {
1259
- const parsed: unknown = JSON.parse(value);
1260
- if (!isRecord(parsed)) throw new Error("workflow-engine-record-invalid");
1261
- return parsed;
1262
- }
1263
-
1264
- export function parseDeliveryDiagnostics(value: string): string[] {
1265
- const parsed: unknown = JSON.parse(value);
1266
- if (
1267
- !Array.isArray(parsed) ||
1268
- parsed.length === 0 ||
1269
- parsed.length > 256 ||
1270
- parsed.some(
1271
- (diagnostic) =>
1272
- typeof diagnostic !== "string" ||
1273
- diagnostic.length === 0 ||
1274
- diagnostic.length > 1024,
1275
- ) ||
1276
- new Set(parsed).size !== parsed.length
1277
- ) {
1278
- throw new Error("workflow-delivery-diagnostics-invalid");
1279
- }
1280
- return [...(parsed as string[])];
1281
- }
1282
-
1283
- export interface BootstrapAcceptanceFact {
1284
- command: string;
1285
- exitCode: 0;
1286
- evidenceHash: string;
1287
- }
1288
-
1289
- export function normalizeBootstrapAcceptanceFacts(
1290
- value: unknown,
1291
- ): BootstrapAcceptanceFact[] | undefined {
1292
- if (value === undefined) return undefined;
1293
- if (!Array.isArray(value) || value.length === 0 || value.length > 32) {
1294
- throw new Error("bootstrap-handoff-invalid");
1295
- }
1296
- const commands = new Set<string>();
1297
- const facts = value.map((entry): BootstrapAcceptanceFact => {
1298
- if (
1299
- !isRecord(entry) ||
1300
- Object.keys(entry).length !== 3 ||
1301
- !Object.hasOwn(entry, "command") ||
1302
- !Object.hasOwn(entry, "exitCode") ||
1303
- !Object.hasOwn(entry, "evidenceHash") ||
1304
- typeof entry.command !== "string" ||
1305
- entry.command.length === 0 ||
1306
- entry.command.length > 512 ||
1307
- /[\n\r\0]/u.test(entry.command) ||
1308
- entry.exitCode !== 0 ||
1309
- typeof entry.evidenceHash !== "string" ||
1310
- !SHA256.test(entry.evidenceHash) ||
1311
- commands.has(entry.command)
1312
- ) {
1313
- throw new Error("bootstrap-handoff-invalid");
1314
- }
1315
- commands.add(entry.command);
1316
- return {
1317
- command: entry.command,
1318
- exitCode: 0,
1319
- evidenceHash: entry.evidenceHash,
1320
- };
1321
- });
1322
- return facts.sort((left, right) =>
1323
- compareCanonicalStrings(left.command, right.command),
1324
- );
1325
- }
1326
-
1327
- export function bootstrapAcceptanceHash(
1328
- facts: readonly BootstrapAcceptanceFact[],
1329
- workspaceManifestHash: string,
1330
- ) {
1331
- return createHash("sha256")
1332
- .update(
1333
- canonicalJson({
1334
- acceptanceFacts: facts,
1335
- workspaceManifestHash,
1336
- }),
1337
- )
1338
- .digest("hex");
1339
- }
1340
-
1341
- export function parsePlan(value: string): ImplementPlan {
1342
- const parsed: unknown = JSON.parse(value);
1343
- assertPlan(parsed);
1344
- return parsed;
1345
- }
1346
-
1347
- export function assertPlan(value: unknown): asserts value is ImplementPlan {
1348
- if (
1349
- !isRecord(value) ||
1350
- typeof value.changeId !== "string" ||
1351
- !Array.isArray(value.tasks) ||
1352
- !Array.isArray(value.outputs) ||
1353
- !isRecord(value.verification) ||
1354
- !isRecord(value.verification.artifactCorrection) ||
1355
- !Number.isSafeInteger(value.verification.artifactCorrection.maxAttempts) ||
1356
- (value.verification.artifactCorrection.maxAttempts as number) < 2 ||
1357
- (value.verification.artifactCorrection.maxAttempts as number) > 3 ||
1358
- value.tasks.length === 0
1359
- ) {
1360
- throw new Error("delivery-plan-invalid");
1361
- }
1362
- const identifiers = new Set<string>();
1363
- for (const task of value.tasks) {
1364
- if (
1365
- !isRecord(task) ||
1366
- typeof task.taskId !== "string" ||
1367
- !IDENTIFIER.test(task.taskId) ||
1368
- identifiers.has(task.taskId) ||
1369
- !Array.isArray(task.dependsOn) ||
1370
- !isRecord(task.phases) ||
1371
- !isRecord(task.phases.red) ||
1372
- !isRecord(task.phases.green) ||
1373
- !isRecord(task.scheduling)
1374
- ) {
1375
- throw new Error("delivery-plan-invalid-task");
1376
- }
1377
- identifiers.add(task.taskId);
1378
- }
1379
- for (const task of value.tasks as unknown as PlanTaskDraft[]) {
1380
- if (task.dependsOn.some((dependency) => !identifiers.has(dependency))) {
1381
- throw new Error("delivery-plan-invalid-dependency");
1382
- }
1383
- }
1384
- }
1385
-
1386
- export function assertDelivery(
1387
- value: unknown,
1388
- stage: ControlStage,
1389
- change: string,
1390
- requestedRevision?: number,
1391
- requestedReceipt?: string,
1392
- ): asserts value is WorkflowDelivery {
1393
- if (
1394
- !isRecord(value) ||
1395
- (value.gate !== "gate-a" && value.gate !== "gate-b") ||
1396
- !Number.isSafeInteger(value.revision) ||
1397
- (value.revision as number) < 1 ||
1398
- typeof value.receiptHash !== "string" ||
1399
- !SHA256.test(value.receiptHash)
1400
- ) {
1401
- throw new Error("delivery-invalid");
1402
- }
1403
- assertPlan(value.plan);
1404
- if (value.approvalProofs !== undefined) {
1405
- if (!isRecord(value.approvalProofs)) throw new Error("delivery-invalid");
1406
- for (const gate of ["gateA", "gateB"] as const) {
1407
- const proof = value.approvalProofs[gate];
1408
- if (
1409
- !isRecord(proof) ||
1410
- !Number.isSafeInteger(proof.revision) ||
1411
- (proof.revision as number) < 1 ||
1412
- typeof proof.contractHash !== "string" ||
1413
- !SHA256.test(proof.contractHash) ||
1414
- typeof proof.recordHash !== "string" ||
1415
- !SHA256.test(proof.recordHash)
1416
- ) {
1417
- throw new Error("delivery-invalid");
1418
- }
1419
- }
1420
- }
1421
- if (stage === "abel-implement" && value.gate !== "gate-b") {
1422
- throw new Error("delivery-gate-b-required");
1423
- }
1424
- if ((value.plan as ImplementPlan).changeId !== change) {
1425
- throw new Error("delivery-change-mismatch");
1426
- }
1427
- if (requestedRevision !== undefined && value.revision !== requestedRevision) {
1428
- throw new Error("delivery-revision-mismatch");
1429
- }
1430
- if (
1431
- requestedReceipt !== undefined &&
1432
- value.receiptHash !== requestedReceipt
1433
- ) {
1434
- throw new Error("delivery-receipt-mismatch");
1435
- }
1436
- }
1437
-
1438
- export function phases(
1439
- task: PlanTaskDraft,
1440
- ): Array<"red" | "green" | "refactor"> {
1441
- return [
1442
- ...(!task.verificationMode || task.verificationMode === "behavior"
1443
- ? ["red" as const]
1444
- : []),
1445
- "green",
1446
- ...(task.phases.refactor ? (["refactor"] as const) : []),
1447
- ];
1448
- }
1449
-
1450
- export function stringArray(value: unknown): string[] {
1451
- return Array.isArray(value)
1452
- ? value.filter((entry): entry is string => typeof entry === "string")
1453
- : [];
1454
- }
1455
-
1456
- export function declarationPathsConflict(left: string, right: string): boolean {
1457
- return (
1458
- left === right ||
1459
- left === "." ||
1460
- right === "." ||
1461
- left.startsWith(`${right}/`) ||
1462
- right.startsWith(`${left}/`)
1463
- );
1464
- }
1465
-
1466
- export function taskConflicts(
1467
- left: PlanTaskDraft,
1468
- right: PlanTaskDraft,
1469
- ): { taskLifetime: boolean; verification: boolean } {
1470
- if (
1471
- left.scheduling.conflicts.includes(right.taskId) ||
1472
- right.scheduling.conflicts.includes(left.taskId)
1473
- ) {
1474
- return { taskLifetime: true, verification: false };
1475
- }
1476
- const leftResources = new Set(left.scheduling.resources);
1477
- if (right.scheduling.resources.some((entry) => leftResources.has(entry))) {
1478
- return { taskLifetime: true, verification: false };
1479
- }
1480
-
1481
- const declarations = (task: PlanTaskDraft) => {
1482
- const reads = new Set<string>();
1483
- const writes = new Set<string>();
1484
- const verificationLocks = new Set<string>();
1485
- for (const phase of Object.values(task.phases)) {
1486
- for (const entry of stringArray(phase.read)) reads.add(entry);
1487
- for (const entry of stringArray(phase.write)) writes.add(entry);
1488
- for (const entry of stringArray(phase.delete)) writes.add(entry);
1489
- if (typeof phase.verificationLock === "string") {
1490
- verificationLocks.add(phase.verificationLock);
1491
- }
1492
- }
1493
- return { reads, writes, verificationLocks };
1494
- };
1495
- const leftDeclarations = declarations(left);
1496
- const rightDeclarations = declarations(right);
1497
- const leftWriteConflict = [...leftDeclarations.writes].some((entry) =>
1498
- [...rightDeclarations.writes, ...rightDeclarations.reads].some((other) =>
1499
- declarationPathsConflict(entry, other),
1500
- ),
1501
- );
1502
- const rightWriteConflict = [...rightDeclarations.writes].some((entry) =>
1503
- [...leftDeclarations.reads].some((other) =>
1504
- declarationPathsConflict(entry, other),
1505
- ),
1506
- );
1507
- const agentsConflict =
1508
- left.agents.impact !== "none" &&
1509
- right.agents.impact !== "none" &&
1510
- left.agents.target !== undefined &&
1511
- left.agents.target === right.agents.target;
1512
- const verification = [...leftDeclarations.verificationLocks].some((lock) =>
1513
- rightDeclarations.verificationLocks.has(lock),
1514
- );
1515
- return {
1516
- taskLifetime: leftWriteConflict || rightWriteConflict || agentsConflict,
1517
- verification,
1518
- };
1519
- }
1520
-
1521
- /** Original-baseline obligations do not change a committed candidate contract. */
1522
- export function taskExecutionContract(task: PlanTaskDraft) {
1523
- const { baselineVerification: _baselineVerification, ...execution } = task;
1524
- return execution;
1525
- }
1526
-
1527
- export function taskApprovalBoundary(
1528
- plan: ImplementPlan,
1529
- task: PlanTaskDraft,
1530
- ): string {
1531
- const planBoundary = structuredClone(plan) as unknown as Record<
1532
- string,
1533
- unknown
1534
- >;
1535
- delete planBoundary.tasks;
1536
- delete planBoundary.outputs;
1537
- if (isRecord(planBoundary.tracking)) {
1538
- delete planBoundary.tracking.taskIds;
1539
- }
1540
- // Retry limits and presentation do not invalidate verified product facts.
1541
- if (isRecord(planBoundary.verification)) {
1542
- delete planBoundary.verification.baseline;
1543
- delete planBoundary.verification.artifactCorrection;
1544
- if (isRecord(planBoundary.verification.repair))
1545
- delete planBoundary.verification.repair.maxAttempts;
1546
- }
1547
- return canonicalJson({
1548
- plan: planBoundary,
1549
- task: taskExecutionContract(task),
1550
- outputs: plan.outputs.filter(
1551
- (output) => output.producer.taskId === task.taskId,
1552
- ),
1553
- });
1554
- }
1555
-
1556
- export function workspaceEntryEqual(
1557
- left: WorkspaceEntry | undefined,
1558
- right: WorkspaceEntry | undefined,
1559
- ): boolean {
1560
- if (!left || !right) return left === right;
1561
- if (left.kind !== right.kind) return false;
1562
- return (
1563
- left.kind === "absent" ||
1564
- (right.kind === "file" &&
1565
- left.hash === right.hash &&
1566
- left.bytes === right.bytes &&
1567
- left.mode === right.mode)
1568
- );
1569
- }
1570
-
1571
- export function expectedClassification(
1572
- phase: string,
1573
- ): "expected-red" | "expected-green" | "expected-refactor" {
1574
- return phase === "red"
1575
- ? "expected-red"
1576
- : phase === "refactor"
1577
- ? "expected-refactor"
1578
- : "expected-green";
1579
- }
1580
-
1581
- export type DurableCandidateProposal =
1582
- | { kind: "candidate"; bytes: Uint8Array }
1583
- | {
1584
- kind: "sealed-candidate";
1585
- candidateId: string;
1586
- artifactHash: string;
1587
- bytes: number;
1588
- paths: string[];
1589
- }
1590
- | {
1591
- kind: "paused" | "retryable" | "approval-needed";
1592
- code: string;
1593
- contextRequest?: WorkflowContextRequest;
1594
- attemptDiagnostic?: SafeAttemptDiagnostic;
1595
- }
1596
- | { kind: "operation-cancelled"; code: "cancelled" };
1597
-
1598
- export type DurableVerificationScope =
1599
- | "baseline-task-affected"
1600
- | "baseline-full-suite"
1601
- | "task-affected"
1602
- | "change-task-affected"
1603
- | "change-full-suite"
1604
- | "agents-checkpoint"
1605
- | "post-apply";
1606
-
1607
- export function deliveryTrackingPath(plan: ImplementPlan): string | undefined {
1608
- if (!isRecord(plan.tracking) || plan.tracking.path !== "tasks.md") {
1609
- return undefined;
1610
- }
1611
- const relative = `openspec/changes/${plan.changeId}/tasks.md`;
1612
- return isValidRelativePath(relative) ? relative : undefined;
1613
- }
1614
-
1615
- export function deliveryBoundPaths(plan: ImplementPlan): string[] {
1616
- const paths = new Set<string>();
1617
- const bindVerification = (verification: StructuredVerificationContract) => {
1618
- for (const relative of verificationBoundInputPaths(verification)) {
1619
- if (relative !== ".") paths.add(relative);
1620
- }
1621
- };
1622
- for (const task of plan.tasks) {
1623
- for (const phase of Object.values(task.phases)) {
1624
- for (const relative of [...phase.read, ...phase.write, ...phase.delete]) {
1625
- if (relative !== ".") paths.add(relative);
1626
- }
1627
- for (const binding of phase.verificationInputs) {
1628
- if (binding.kind === "workspace") paths.add(binding.path);
1629
- }
1630
- bindVerification(phase.verification);
1631
- }
1632
- if (hasVerificationLifecycle(plan)) {
1633
- if (task.baselineVerification)
1634
- bindVerification(task.baselineVerification);
1635
- bindVerification(task.affectedVerification);
1636
- bindVerification(task.repairVerification);
1637
- }
1638
- if (task.agents.target) paths.add(task.agents.target);
1639
- }
1640
- for (const output of plan.outputs) paths.add(output.path);
1641
- if (hasVerificationLifecycle(plan)) {
1642
- bindVerification(plan.verification.baseline.fullSuite);
1643
- bindVerification(plan.verification.change.fullSuite);
1644
- bindVerification(plan.verification.change.postApply);
1645
- if (plan.verification.agentsCheckpoint.verification) {
1646
- bindVerification(plan.verification.agentsCheckpoint.verification);
1647
- }
1648
- for (const operation of plan.verification.agentsCheckpoint.operations) {
1649
- paths.add(operation.target);
1650
- }
1651
- }
1652
- const trackingPath = deliveryTrackingPath(plan);
1653
- if (trackingPath) paths.add(trackingPath);
1654
- return [...paths].sort();
1655
- }
1656
-
1657
- export function hasVerificationLifecycle(plan: ImplementPlan): boolean {
1658
- return (
1659
- isRecord(plan.verification) &&
1660
- isRecord(plan.verification.baseline) &&
1661
- isRecord(plan.verification.change) &&
1662
- isRecord(plan.verification.repair) &&
1663
- isRecord(plan.tracking) &&
1664
- plan.tasks.every(
1665
- (task) =>
1666
- isRecord(task.affectedVerification) &&
1667
- isRecord(task.repairVerification),
1668
- )
1669
- );
1670
- }