@abelxiaoxing/cadence 1.8.3 → 2.0.0

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