@abelxiaoxing/cadence 1.8.4 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +324 -117
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -18
  4. package/agents/diagnosis-worker.md +14 -14
  5. package/agents/implementation-worker.md +16 -17
  6. package/config/workflow-task.example.json +7 -0
  7. package/docs/remote-access.md +60 -0
  8. package/docs/reuse-and-distribution.md +44 -0
  9. package/docs/service-management.md +108 -0
  10. package/licenses/bundled-web.txt +6245 -0
  11. package/licenses/pi-web-ui-MIT.txt +21 -0
  12. package/licenses/pi-web-ui-reuse.md +22 -0
  13. package/package.json +92 -20
  14. package/src/index.ts +21 -1802
  15. package/src/native-build.ts +6 -0
  16. package/src/native-client.ts +103 -0
  17. package/src/native-export.ts +58 -0
  18. package/src/native-history.ts +359 -0
  19. package/src/operator-cli.mjs +453 -1937
  20. package/src/plan-proposal.ts +87 -0
  21. package/src/sdk-worker.mjs +549 -0
  22. package/src/service/contracts/active.ts +82 -0
  23. package/src/service/contracts/budget.ts +148 -0
  24. package/src/service/contracts/build.ts +67 -0
  25. package/src/service/contracts/capture.ts +48 -0
  26. package/src/service/contracts/core.ts +33 -0
  27. package/src/service/contracts/current.ts +261 -0
  28. package/src/service/contracts/domain.ts +68 -0
  29. package/src/service/contracts/history.ts +2245 -0
  30. package/src/service/contracts/models.ts +139 -0
  31. package/src/service/coordination.ts +862 -0
  32. package/src/service/private-files.ts +204 -0
  33. package/src/service-cli.mjs +5592 -0
  34. package/src/service-client.ts +747 -0
  35. package/src/service-contract.ts +245 -0
  36. package/src/service-host.mjs +22382 -0
  37. package/src/service-http-client.ts +425 -0
  38. package/src/service-wire.ts +372 -0
  39. package/src/task-shell.ts +1045 -0
  40. package/src/task-store.ts +479 -0
  41. package/src/task-types.ts +184 -0
  42. package/src/verification-diagnostics.ts +29 -72
  43. package/src/worker-capture.mjs +1197 -0
  44. package/src/worker-public.mjs +1753 -0
  45. package/src/worker-redaction.mjs +608 -0
  46. package/src/worker-tools.mjs +202 -0
  47. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  50. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  56. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  62. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  68. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  71. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  74. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  77. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  89. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  92. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  98. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  104. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  107. package/web/dist/assets/cadence.css +2 -0
  108. package/web/dist/assets/cadence.js +7 -0
  109. package/web/dist/assets/markdown.js +296 -0
  110. package/web/dist/assets/react.js +9 -0
  111. package/web/dist/assets/rolldown-runtime.js +1 -0
  112. package/web/dist/assets/timeline-client.js +41 -0
  113. package/web/dist/assets/vis-timeline.css +1274 -0
  114. package/web/dist/assets/vis-timeline.js +32 -0
  115. package/web/dist/index.html +18 -0
  116. package/web/dist/manifest.json +505 -0
  117. package/config/plan-draft.example.json +0 -125
  118. package/config/plan-draft.multiple-tasks.example.json +0 -375
  119. package/config/plan-draft.quick.example.json +0 -78
  120. package/prompts/abel-design.md +0 -382
  121. package/prompts/abel-diagnose.md +0 -71
  122. package/prompts/abel-implement.md +0 -284
  123. package/prompts/abel-init.md +0 -52
  124. package/src/activation.ts +0 -44
  125. package/src/activity-contracts.ts +0 -82
  126. package/src/agent-registry.ts +0 -53
  127. package/src/apply-transaction.ts +0 -1437
  128. package/src/artifact-store.ts +0 -213
  129. package/src/build-windows-job.mjs +0 -95
  130. package/src/candidate-artifact.ts +0 -143
  131. package/src/candidate-context.ts +0 -289
  132. package/src/candidate-patch.ts +0 -271
  133. package/src/candidate-workspace.ts +0 -80
  134. package/src/canonical.ts +0 -16
  135. package/src/change-contract-schema.ts +0 -191
  136. package/src/change-contract.ts +0 -324
  137. package/src/change-verification.ts +0 -1617
  138. package/src/contracts.ts +0 -2466
  139. package/src/control-contracts.ts +0 -382
  140. package/src/delivery-compiler.ts +0 -1805
  141. package/src/delivery-revision.ts +0 -42
  142. package/src/design-control.ts +0 -1299
  143. package/src/design-diagnostics.ts +0 -181
  144. package/src/design-journal.ts +0 -1773
  145. package/src/directory-sync.ts +0 -36
  146. package/src/durable-contracts.ts +0 -227
  147. package/src/durable-workflow.ts +0 -822
  148. package/src/evidence-draft.ts +0 -122
  149. package/src/execution-profile.ts +0 -64
  150. package/src/execution-retention.ts +0 -77
  151. package/src/file-snapshot.ts +0 -163
  152. package/src/implement-continuation.ts +0 -559
  153. package/src/implement-graph.ts +0 -690
  154. package/src/implement-plan.ts +0 -72
  155. package/src/install-config.d.mts +0 -6
  156. package/src/isolation-backend.ts +0 -353
  157. package/src/model-source.ts +0 -22
  158. package/src/openspec-cli.ts +0 -494
  159. package/src/operator-cli-entry.mjs +0 -24
  160. package/src/operator-tools.ts +0 -309
  161. package/src/package-candidate-process.ts +0 -206
  162. package/src/package-candidate.ts +0 -24
  163. package/src/package-delivery.ts +0 -486
  164. package/src/package-state.ts +0 -320
  165. package/src/package-verification.ts +0 -635
  166. package/src/package-workflow.ts +0 -305
  167. package/src/packet-runtime.ts +0 -459
  168. package/src/phase-execution.ts +0 -2163
  169. package/src/pi-adapter.ts +0 -19
  170. package/src/plan-draft-summary.ts +0 -527
  171. package/src/plan-draft.ts +0 -502
  172. package/src/remove-path.ts +0 -32
  173. package/src/run-state.ts +0 -420
  174. package/src/run-store.ts +0 -1179
  175. package/src/safe-path.ts +0 -77
  176. package/src/scoped-grep-worker.mjs +0 -23
  177. package/src/scoped-tools.ts +0 -775
  178. package/src/single-task-draft.ts +0 -142
  179. package/src/sqlite-schema.ts +0 -171
  180. package/src/state-root.ts +0 -215
  181. package/src/storage-schema.ts +0 -360
  182. package/src/subagent-activity.ts +0 -1417
  183. package/src/subagent-process.ts +0 -476
  184. package/src/task-ledger.ts +0 -1338
  185. package/src/transport-budget.ts +0 -50
  186. package/src/verification-capability.ts +0 -1191
  187. package/src/verification-environment-io.ts +0 -389
  188. package/src/verification-environment.ts +0 -162
  189. package/src/verification-identity.ts +0 -77
  190. package/src/verification-scan.ts +0 -64
  191. package/src/windows-job-backend.ts +0 -419
  192. package/src/windows-job.c +0 -149
  193. package/src/workflow-engine.ts +0 -28
  194. package/src/workflow-policy.ts +0 -1670
  195. package/src/workflow-recovery-policy.ts +0 -97
  196. package/src/workflow-scheduling.ts +0 -105
  197. package/src/workflow-state-machine.ts +0 -4341
  198. package/src/workflow-status.ts +0 -576
  199. package/src/workspace-io-entry.mjs +0 -76
  200. package/src/workspace-io-worker.mjs +0 -1312
  201. package/src/workspace-io.ts +0 -89
  202. package/src/workspace-store.ts +0 -634
@@ -1,822 +0,0 @@
1
- import { ChangeVerification, sha256Bytes } from "./change-verification.ts";
2
- import type {
3
- DurableExecutionResources,
4
- DurablePhaseFact,
5
- DurableResourceInput,
6
- DurableWorkflowEngineOptions,
7
- } from "./durable-contracts.ts";
8
- import { assertExecutionsSettled } from "./execution-retention.ts";
9
- import { PhaseExecution } from "./phase-execution.ts";
10
- import { removePathSync } from "./remove-path.ts";
11
-
12
- export type {
13
- DurableChangeVerificationResult,
14
- DurablePhaseVerificationResult,
15
- DurableWorkflowEngineOptions,
16
- } from "./durable-contracts.ts";
17
-
18
- interface DurableRunResources extends DurableExecutionResources {
19
- transactions: ApplyTransaction;
20
- closed: boolean;
21
- }
22
-
23
- import { lstatSync, mkdirSync, readFileSync } from "node:fs";
24
- import path from "node:path";
25
- import { ApplyTransaction } from "./apply-transaction.ts";
26
- import { ArtifactStore } from "./artifact-store.ts";
27
- import { compareCanonicalStrings } from "./canonical.ts";
28
- import type { ImplementPlan, PlanTaskDraft } from "./delivery-compiler.ts";
29
- import { observeSafePath } from "./safe-path.ts";
30
- import { TaskLedger } from "./task-ledger.ts";
31
- import {
32
- deliveryBoundPaths,
33
- deliveryTrackingPath,
34
- hash,
35
- hasVerificationLifecycle,
36
- IDENTIFIER,
37
- isRecord,
38
- SHA256,
39
- type WorkflowApplication,
40
- type WorkflowApplicationContext,
41
- type WorkflowChangeVerifier,
42
- type WorkflowRunLifecycle,
43
- type WorkflowWorker,
44
- workspaceEntryEqual,
45
- } from "./workflow-policy.ts";
46
- import { WorkflowEngine } from "./workflow-state-machine.ts";
47
- import { type WorkspaceRevision, WorkspaceStore } from "./workspace-store.ts";
48
-
49
- function ensurePrivateRunDirectory(directory: string): void {
50
- const existing = lstatSync(directory, { throwIfNoEntry: false });
51
- if (existing) {
52
- if (existing.isSymbolicLink() || !existing.isDirectory()) {
53
- throw new Error("workflow-run-data-unsafe");
54
- }
55
- return;
56
- }
57
- mkdirSync(directory, { recursive: true, mode: 0o700 });
58
- const created = lstatSync(directory);
59
- if (created.isSymbolicLink() || !created.isDirectory()) {
60
- throw new Error("workflow-run-data-unsafe");
61
- }
62
- }
63
-
64
- class DurableWorkflowComposition
65
- implements
66
- WorkflowWorker,
67
- WorkflowChangeVerifier,
68
- WorkflowApplication,
69
- WorkflowRunLifecycle
70
- {
71
- readonly #phase: PhaseExecution;
72
- readonly #verification: ChangeVerification;
73
- readonly #options: DurableWorkflowEngineOptions;
74
- readonly #runs = new Map<string, DurableRunResources>();
75
-
76
- constructor(options: DurableWorkflowEngineOptions) {
77
- this.#options = options;
78
- this.#verification = new ChangeVerification(options, {
79
- prepareResources: (input, signal) =>
80
- this.#prepareResources(input, signal),
81
- ledger: (resources, revision) => this.#ledger(resources, revision),
82
- run: (runId) => this.#runs.get(runId),
83
- revalidatePhasePolicy: (...args) =>
84
- this.#phase.revalidatePhasePolicy(...args),
85
- });
86
- this.#phase = new PhaseExecution(options, {
87
- prepareResources: (input, signal) =>
88
- this.#prepareResources(input, signal),
89
- ledger: (resources, revision) => this.#ledger(resources, revision),
90
- ensureVerificationBaseline: (input) =>
91
- this.#verification.ensureVerificationBaseline(input),
92
- verifyTaskAffected: (input) =>
93
- this.#verification.verifyTaskAffected(input),
94
- verifyPhase: (input) => this.#verification.verifyPhase(input),
95
- });
96
- }
97
-
98
- #runRoot(runId: string): string {
99
- if (!IDENTIFIER.test(runId)) throw new Error("workflow-run-id-invalid");
100
- const dataRoot = path.join(this.#options.stateRoot.rootDir, "run-data");
101
- const root = path.join(dataRoot, runId);
102
- const relative = path.relative(dataRoot, root);
103
- if (relative.startsWith("..") || path.isAbsolute(relative)) {
104
- throw new Error("workflow-run-data-unsafe");
105
- }
106
- return root;
107
- }
108
-
109
- #captureBaseline(
110
- workspaces: WorkspaceStore,
111
- plan: ImplementPlan,
112
- signal: AbortSignal,
113
- ) {
114
- const candidates = new Set<string>();
115
- const absent = new Set<string>();
116
- for (const relative of deliveryBoundPaths(plan)) {
117
- const observation = observeSafePath(this.#options.consumerRoot, relative);
118
- if (observation.kind === "file") candidates.add(relative);
119
- else if (observation.kind === "absent") absent.add(relative);
120
- else throw new Error("workflow-baseline-path-unsafe");
121
- }
122
- return workspaces.captureBaselineAsync(
123
- {
124
- consumerRoot: this.#options.consumerRoot,
125
- approvedUntracked: [...candidates].sort(),
126
- absent: [...absent].sort(),
127
- },
128
- signal,
129
- );
130
- }
131
-
132
- readonly #initializing = new Map<string, Promise<DurableRunResources>>();
133
-
134
- async #prepareResources(
135
- input: DurableResourceInput,
136
- signal: AbortSignal,
137
- ): Promise<DurableRunResources> {
138
- signal.throwIfAborted();
139
- assertExecutionsSettled(this.#options.stateRoot.rootDir);
140
- const pending = this.#initializing.get(input.runId);
141
- if (pending) return pending;
142
- if (
143
- this.#runs.has(input.runId) ||
144
- (input.baselineRevisionId && input.currentWorkspaceRevisionId)
145
- )
146
- return this.#resources(input);
147
- const initialize = async () => {
148
- if (
149
- !input.plan ||
150
- input.baselineRevisionId ||
151
- input.currentWorkspaceRevisionId
152
- )
153
- throw new Error("workflow-workspace-revision-unavailable");
154
- const root = this.#runRoot(input.runId);
155
- ensurePrivateRunDirectory(root);
156
- const artifacts = new ArtifactStore(path.join(root, "artifacts"));
157
- const workspaces = new WorkspaceStore(
158
- path.join(root, "workspaces"),
159
- artifacts,
160
- );
161
- const baseline = await this.#captureBaseline(
162
- workspaces,
163
- input.plan,
164
- signal,
165
- );
166
- signal.throwIfAborted();
167
- return this.#resources({
168
- ...input,
169
- baselineRevisionId: baseline.revisionId,
170
- currentWorkspaceRevisionId: baseline.revisionId,
171
- });
172
- };
173
- const promise = initialize();
174
- this.#initializing.set(input.runId, promise);
175
- try {
176
- return await promise;
177
- } finally {
178
- this.#initializing.delete(input.runId);
179
- }
180
- }
181
-
182
- #resources(input: {
183
- runId: string;
184
- deliveryRevision: number;
185
- plan?: ImplementPlan;
186
- baselineRevisionId?: string;
187
- currentWorkspaceRevisionId?: string;
188
- }): DurableRunResources {
189
- assertExecutionsSettled(this.#options.stateRoot.rootDir);
190
- const existing = this.#runs.get(input.runId);
191
- if (existing) {
192
- if (
193
- input.baselineRevisionId &&
194
- existing.baselineRevisionId !== input.baselineRevisionId
195
- ) {
196
- throw new Error("workflow-baseline-revision-conflict");
197
- }
198
- if (input.plan) {
199
- existing.plan = structuredClone(input.plan);
200
- existing.deliveryRevision = input.deliveryRevision;
201
- }
202
- return existing;
203
- }
204
- if (!input.plan) {
205
- throw new Error("workflow-plan-unavailable");
206
- }
207
- const root = this.#runRoot(input.runId);
208
- ensurePrivateRunDirectory(root);
209
- const artifacts = new ArtifactStore(path.join(root, "artifacts"));
210
- const workspaces = new WorkspaceStore(
211
- path.join(root, "workspaces"),
212
- artifacts,
213
- );
214
- const baselineRevisionId = input.baselineRevisionId;
215
- const currentRevisionId = input.currentWorkspaceRevisionId;
216
- if (!baselineRevisionId || !currentRevisionId)
217
- throw new Error("workflow-workspace-revision-unavailable");
218
- let originalBaseline = workspaces.getRevision(baselineRevisionId);
219
- const ancestors = new Set<string>();
220
- while (originalBaseline.parentRevisionId !== null) {
221
- if (ancestors.has(originalBaseline.revisionId))
222
- throw new Error("workflow-baseline-revision-conflict");
223
- ancestors.add(originalBaseline.revisionId);
224
- originalBaseline = workspaces.getRevision(
225
- originalBaseline.parentRevisionId,
226
- );
227
- }
228
- workspaces.getRevision(currentRevisionId);
229
- let resources!: DurableRunResources;
230
- const transactions = new ApplyTransaction({
231
- root: path.join(root, "transactions"),
232
- artifacts,
233
- workspaces,
234
- hooks: {
235
- awaitPostApplySettlement: this.#options.awaitPostApplySettlement,
236
- postApply: ({ transactionId, root: verificationRoot, signal }) =>
237
- this.#verification.verifyPostApply(
238
- resources,
239
- verificationRoot,
240
- signal,
241
- transactionId,
242
- ),
243
- },
244
- });
245
- resources = {
246
- runId: input.runId,
247
- root,
248
- plan: structuredClone(input.plan),
249
- deliveryRevision: input.deliveryRevision,
250
- artifacts,
251
- workspaces,
252
- ledgers: new Map(),
253
- transactions,
254
- baselineRevisionId,
255
- originalBaselineRevisionId: originalBaseline.revisionId,
256
- currentRevisionId,
257
- mergeTail: Promise.resolve(),
258
- baselinePromises: new Map(),
259
- closed: false,
260
- };
261
- resources.baselineLedger = this.#ledger(resources, 1);
262
- this.#runs.set(input.runId, resources);
263
- return resources;
264
- }
265
-
266
- #closeResources(resources: DurableRunResources): void {
267
- if (resources.closed) return;
268
- for (const ledger of resources.ledgers.values()) ledger.close();
269
- resources.ledgers.clear();
270
- resources.transactions.close();
271
- resources.closed = true;
272
- }
273
-
274
- #ledger(
275
- resources: DurableExecutionResources,
276
- deliveryRevision: number,
277
- ): TaskLedger {
278
- const existing = resources.ledgers.get(deliveryRevision);
279
- if (existing) return existing;
280
- const ledger = new TaskLedger({
281
- root: path.join(
282
- resources.root,
283
- "ledgers",
284
- `revision-${deliveryRevision}`,
285
- ),
286
- artifacts: resources.artifacts,
287
- });
288
- resources.ledgers.set(deliveryRevision, ledger);
289
- return ledger;
290
- }
291
-
292
- #expandedBaseline(
293
- resources: DurableRunResources,
294
- plan: ImplementPlan,
295
- ): WorkspaceRevision {
296
- const baseline = resources.workspaces.getRevision(
297
- resources.baselineRevisionId,
298
- );
299
- const candidates = new Map<string, boolean>(
300
- deliveryBoundPaths(plan).map((relative) => [relative, false]),
301
- );
302
- for (const task of plan.tasks) {
303
- for (const phase of Object.values(task.phases)) {
304
- for (const relative of phase.read) candidates.set(relative, false);
305
- for (const relative of [...phase.write, ...phase.delete]) {
306
- candidates.set(relative, true);
307
- }
308
- }
309
- }
310
- for (const output of plan.outputs) candidates.set(output.path, true);
311
- for (const task of plan.tasks) {
312
- if (task.agents.impact === "none" || !task.agents.target) continue;
313
- candidates.set(task.agents.target, task.agents.impact === "create-index");
314
- }
315
- const trackingPath = deliveryTrackingPath(plan);
316
- if (
317
- trackingPath &&
318
- (Object.hasOwn(baseline.entries, trackingPath) ||
319
- observeSafePath(this.#options.consumerRoot, trackingPath).kind ===
320
- "file")
321
- ) {
322
- candidates.set(trackingPath, false);
323
- }
324
- const changes: Record<
325
- string,
326
- { kind: "absent" } | { kind: "file"; bytes: Uint8Array; mode: number }
327
- > = {};
328
- for (const [relative, mayBeAbsent] of [...candidates].sort(
329
- ([left], [right]) => compareCanonicalStrings(left, right),
330
- )) {
331
- if (relative === ".") continue;
332
- const observation = observeSafePath(this.#options.consumerRoot, relative);
333
- if (observation.kind === "absent") {
334
- if (baseline.entries[relative]?.kind === "absent") continue;
335
- if (!mayBeAbsent)
336
- throw new Error("workflow-baseline-input-unavailable");
337
- changes[relative] = { kind: "absent" };
338
- continue;
339
- }
340
- if (observation.kind !== "file") {
341
- throw new Error("workflow-baseline-path-unsafe");
342
- }
343
- const target = path.join(
344
- this.#options.consumerRoot,
345
- ...relative.split("/"),
346
- );
347
- const stat = lstatSync(target);
348
- const bytes = readFileSync(target);
349
- const mode = stat.mode & 0o777;
350
- const prior = baseline.entries[relative];
351
- if (
352
- prior?.kind === "file" &&
353
- prior.hash === sha256Bytes(bytes) &&
354
- prior.bytes === bytes.byteLength &&
355
- prior.mode === mode
356
- ) {
357
- continue;
358
- }
359
- changes[relative] = {
360
- kind: "file",
361
- bytes,
362
- mode,
363
- };
364
- }
365
- return Object.keys(changes).length === 0
366
- ? baseline
367
- : resources.workspaces.createRevision({
368
- parentRevisionId: baseline.revisionId,
369
- changes,
370
- });
371
- }
372
-
373
- #orderedTasks(plan: ImplementPlan): PlanTaskDraft[] {
374
- const remaining = new Map(plan.tasks.map((task) => [task.taskId, task]));
375
- const ordered: PlanTaskDraft[] = [];
376
- const admitted = new Set<string>();
377
- while (remaining.size > 0) {
378
- let progressed = false;
379
- for (const task of plan.tasks) {
380
- if (
381
- !remaining.has(task.taskId) ||
382
- task.dependsOn.some((dependency) => !admitted.has(dependency))
383
- ) {
384
- continue;
385
- }
386
- remaining.delete(task.taskId);
387
- admitted.add(task.taskId);
388
- ordered.push(task);
389
- progressed = true;
390
- }
391
- if (!progressed) throw new Error("workflow-plan-dependency-cycle");
392
- }
393
- return ordered;
394
- }
395
-
396
- #phaseFacts(
397
- resources: DurableRunResources,
398
- task: {
399
- taskId: string;
400
- deliveryRevision: number;
401
- phase: "red" | "green" | "refactor";
402
- },
403
- ): DurablePhaseFact[] {
404
- let projection: unknown;
405
- try {
406
- projection = this.#ledger(resources, task.deliveryRevision).projection({
407
- runId: resources.runId,
408
- taskId: task.taskId,
409
- nextPhase: task.phase,
410
- });
411
- } catch (error) {
412
- if (
413
- error instanceof Error &&
414
- error.message === "ledger-task-unavailable"
415
- ) {
416
- return [];
417
- }
418
- throw error;
419
- }
420
- if (!isRecord(projection) || !Array.isArray(projection.history)) {
421
- throw new Error("workflow-ledger-projection-invalid");
422
- }
423
- return projection.history.flatMap((event): DurablePhaseFact[] => {
424
- if (
425
- !isRecord(event) ||
426
- (event.kind !== "phase-verified" && event.kind !== "repair-verified")
427
- ) {
428
- return [];
429
- }
430
- if (
431
- !["red", "green", "refactor"].includes(String(event.phase)) ||
432
- typeof event.isolatedRevisionId !== "string" ||
433
- !SHA256.test(event.isolatedRevisionId) ||
434
- !Array.isArray(event.outputFacts)
435
- ) {
436
- throw new Error("workflow-ledger-phase-invalid");
437
- }
438
- const outputFacts = event.outputFacts.map((fact) => {
439
- if (
440
- !isRecord(fact) ||
441
- typeof fact.path !== "string" ||
442
- (fact.kind !== "absent" && fact.kind !== "file")
443
- ) {
444
- throw new Error("workflow-ledger-phase-invalid");
445
- }
446
- if (fact.kind === "absent") {
447
- return { path: fact.path, kind: "absent" as const };
448
- }
449
- if (
450
- typeof fact.hash !== "string" ||
451
- !SHA256.test(fact.hash) ||
452
- !Number.isSafeInteger(fact.bytes) ||
453
- (fact.bytes as number) < 0
454
- ) {
455
- throw new Error("workflow-ledger-phase-invalid");
456
- }
457
- return {
458
- path: fact.path,
459
- kind: "file" as const,
460
- hash: fact.hash,
461
- bytes: fact.bytes as number,
462
- };
463
- });
464
- return [
465
- {
466
- kind: event.kind,
467
- phase: event.phase as DurablePhaseFact["phase"],
468
- isolatedRevisionId: event.isolatedRevisionId,
469
- outputFacts,
470
- },
471
- ];
472
- });
473
- }
474
-
475
- revalidateDelivery(
476
- input: Parameters<NonNullable<WorkflowWorker["revalidateDelivery"]>>[0],
477
- ): ReturnType<NonNullable<WorkflowWorker["revalidateDelivery"]>> {
478
- const resources = this.#resources({
479
- runId: input.runId,
480
- deliveryRevision: input.deliveryRevision,
481
- plan: input.plan,
482
- baselineRevisionId: input.baselineRevisionId,
483
- currentWorkspaceRevisionId: input.currentWorkspaceRevisionId,
484
- });
485
- const expandedBaseline = this.#expandedBaseline(resources, input.plan);
486
- const rows = new Map(input.tasks.map((task) => [task.taskId, task]));
487
- const facts = new Map(
488
- input.tasks.map((task) => [
489
- task.taskId,
490
- this.#phaseFacts(resources, task),
491
- ]),
492
- );
493
- const invalidated = new Set(input.invalidatedTaskIds);
494
- const ordered = this.#orderedTasks(input.plan);
495
- let rebuilt = expandedBaseline;
496
- for (;;) {
497
- let grew = false;
498
- rebuilt = expandedBaseline;
499
- for (const task of ordered) {
500
- const row = rows.get(task.taskId);
501
- if (!row || invalidated.has(task.taskId)) continue;
502
- if (task.dependsOn.some((dependency) => invalidated.has(dependency))) {
503
- invalidated.add(task.taskId);
504
- grew = true;
505
- continue;
506
- }
507
- for (const fact of facts.get(task.taskId) ?? []) {
508
- const phase = task.phases[fact.phase];
509
- if (!phase) {
510
- invalidated.add(task.taskId);
511
- grew = true;
512
- break;
513
- }
514
- const original = resources.workspaces.getRevision(
515
- fact.isolatedRevisionId,
516
- );
517
- if (!original.parentRevisionId) {
518
- throw new Error("workflow-ledger-revision-base-invalid");
519
- }
520
- const originalBase = resources.workspaces.getRevision(
521
- original.parentRevisionId,
522
- );
523
- const current = resources.workspaces.getRevision(rebuilt.revisionId);
524
- const boundaries =
525
- fact.kind === "repair-verified"
526
- ? Object.values(task.phases)
527
- : [phase];
528
- const boundPaths = [
529
- ...new Set(
530
- boundaries.flatMap((boundary) => [
531
- ...(row.contextReadPaths ?? []),
532
- ...boundary.read,
533
- ...boundary.write,
534
- ...boundary.delete,
535
- ...boundary.verificationInputs.flatMap((binding) =>
536
- binding.kind === "workspace" ? [binding.path] : [],
537
- ),
538
- ]),
539
- ),
540
- ];
541
- if (
542
- boundPaths.some(
543
- (relative) =>
544
- !workspaceEntryEqual(
545
- originalBase.entries[relative],
546
- current.entries[relative],
547
- ),
548
- )
549
- ) {
550
- invalidated.add(task.taskId);
551
- grew = true;
552
- break;
553
- }
554
- const changes: Record<
555
- string,
556
- | { kind: "absent" }
557
- | { kind: "file"; bytes: Uint8Array; mode: number }
558
- > = {};
559
- for (const output of fact.outputFacts) {
560
- const entry = original.entries[output.path];
561
- if (
562
- output.kind === "absent"
563
- ? entry?.kind !== "absent"
564
- : entry?.kind !== "file" ||
565
- entry.hash !== output.hash ||
566
- entry.bytes !== output.bytes
567
- ) {
568
- throw new Error("workflow-ledger-output-integrity-invalid");
569
- }
570
- changes[output.path] =
571
- entry.kind === "absent"
572
- ? { kind: "absent" }
573
- : {
574
- kind: "file",
575
- bytes: resources.artifacts.read(entry.hash),
576
- mode: entry.mode,
577
- };
578
- }
579
- rebuilt = resources.workspaces.createRevision({
580
- parentRevisionId: rebuilt.revisionId,
581
- changes,
582
- });
583
- }
584
- }
585
- for (const output of input.plan.outputs) {
586
- if (invalidated.has(output.producer.taskId)) continue;
587
- const produced = (facts.get(output.producer.taskId) ?? []).some(
588
- (fact) => fact.phase === output.producer.phase,
589
- );
590
- if (
591
- produced &&
592
- rebuilt.entries[output.path]?.kind !== "file" &&
593
- !invalidated.has(output.producer.taskId)
594
- ) {
595
- invalidated.add(output.producer.taskId);
596
- grew = true;
597
- }
598
- }
599
- if (!grew) break;
600
- }
601
- // Boundary expansion changes candidate/apply inputs, never the root capture
602
- // or its verification observations. Their contract-bound caches revalidate
603
- // only the obligations changed by this delivery.
604
- resources.baselinePromises.clear();
605
- resources.baselineRevisionId = expandedBaseline.revisionId;
606
- resources.currentRevisionId = rebuilt.revisionId;
607
- if (hasVerificationLifecycle(input.plan)) {
608
- for (const task of input.tasks) {
609
- if (task.state === "verified" && !invalidated.has(task.taskId)) {
610
- this.#phase.completeTracking(resources, task.taskId);
611
- }
612
- }
613
- }
614
- return {
615
- invalidatedTaskIds: [...invalidated].sort(),
616
- baselineRevisionId: resources.baselineRevisionId,
617
- currentWorkspaceRevisionId: resources.currentRevisionId,
618
- };
619
- }
620
-
621
- isContextReadAvailable(
622
- input: Parameters<NonNullable<WorkflowWorker["isContextReadAvailable"]>>[0],
623
- ): boolean {
624
- const resources = this.#resources(input);
625
- const revision = resources.workspaces.getRevision(
626
- input.currentWorkspaceRevisionId,
627
- );
628
- return revision.entries[input.path]?.kind === "file";
629
- }
630
-
631
- rebind(_input: Parameters<WorkflowWorker["rebind"]>[0]) {
632
- return { ok: false as const, code: "route-configuration-removed" };
633
- }
634
-
635
- async prepare(
636
- input: Parameters<WorkflowApplication["begin"]>[0],
637
- ): Promise<Record<string, unknown>> {
638
- if (!input.baselineRevisionId || !input.currentWorkspaceRevisionId) {
639
- return { state: "paused", code: "workspace-revision-unavailable" };
640
- }
641
- const resources = await this.#prepareResources(
642
- {
643
- runId: input.runId,
644
- deliveryRevision: input.deliveryRevision,
645
- plan: input.plan,
646
- baselineRevisionId: input.baselineRevisionId,
647
- currentWorkspaceRevisionId: input.currentWorkspaceRevisionId,
648
- },
649
- input.signal,
650
- );
651
- if (!resources.verificationFact) {
652
- return { state: "paused", code: "verification-fact-unavailable" };
653
- }
654
- if (this.#options.verificationEnvironment) {
655
- if (!resources.environmentIdentity)
656
- return {
657
- state: "paused",
658
- code: "verification-environment-unavailable",
659
- };
660
- this.#ledger(resources, resources.deliveryRevision).putDurableFact(
661
- `apply-environment-${hash(input.transactionId).slice(0, 40)}`,
662
- { identity: resources.environmentIdentity },
663
- );
664
- }
665
- const prepared = await resources.transactions.prepare({
666
- transactionId: input.transactionId,
667
- consumerRoot: input.consumerRoot,
668
- baselineRevisionId: resources.baselineRevisionId,
669
- finalRevisionId: resources.currentRevisionId,
670
- boundPaths: [
671
- ...new Set([
672
- ...deliveryBoundPaths(input.plan),
673
- ...(input.contextReadPaths ?? []),
674
- ]),
675
- ].sort(),
676
- verificationFact: resources.verificationFact,
677
- });
678
- return prepared;
679
- }
680
-
681
- async applyPrepared(
682
- input: Parameters<WorkflowApplication["begin"]>[0],
683
- ): Promise<Record<string, unknown>> {
684
- const resources = await this.#prepareResources(
685
- {
686
- runId: input.runId,
687
- deliveryRevision: input.deliveryRevision,
688
- plan: input.plan,
689
- ...(input.baselineRevisionId
690
- ? { baselineRevisionId: input.baselineRevisionId }
691
- : {}),
692
- ...(input.currentWorkspaceRevisionId
693
- ? { currentWorkspaceRevisionId: input.currentWorkspaceRevisionId }
694
- : {}),
695
- },
696
- input.signal,
697
- );
698
- return resources.transactions.apply(input.transactionId, input.signal);
699
- }
700
-
701
- async begin(
702
- input: Parameters<WorkflowApplication["begin"]>[0],
703
- ): Promise<Record<string, unknown>> {
704
- const prepared = await this.prepare(input);
705
- return prepared.state === "prepared" ? this.applyPrepared(input) : prepared;
706
- }
707
-
708
- #resourcesForApplication(
709
- context: WorkflowApplicationContext | undefined,
710
- ): DurableRunResources {
711
- if (!context) throw new Error("workflow-application-context-unavailable");
712
- return this.#resources({
713
- runId: context.runId,
714
- deliveryRevision: context.deliveryRevision,
715
- plan: context.plan,
716
- ...(context.baselineRevisionId
717
- ? { baselineRevisionId: context.baselineRevisionId }
718
- : {}),
719
- ...(context.currentWorkspaceRevisionId
720
- ? { currentWorkspaceRevisionId: context.currentWorkspaceRevisionId }
721
- : {}),
722
- });
723
- }
724
-
725
- requestControl(
726
- transactionId: string,
727
- intent: "cancel" | "discard",
728
- context?: WorkflowApplicationContext,
729
- ): Record<string, unknown> {
730
- return this.#resourcesForApplication(context).transactions.requestControl(
731
- transactionId,
732
- intent,
733
- );
734
- }
735
-
736
- recover(
737
- transactionId: string,
738
- context?: WorkflowApplicationContext,
739
- ): Promise<Record<string, unknown>> {
740
- return this.#resourcesForApplication(context).transactions.recover(
741
- transactionId,
742
- );
743
- }
744
-
745
- cleanup(runId: string): void {
746
- assertExecutionsSettled(this.#options.stateRoot.rootDir, true);
747
- const resources = this.#runs.get(runId);
748
- if (resources) {
749
- this.#closeResources(resources);
750
- this.#runs.delete(runId);
751
- }
752
- removePathSync(this.#runRoot(runId), { recursive: true, force: true });
753
- }
754
-
755
- close(): void {
756
- for (const resources of this.#runs.values()) {
757
- this.#closeResources(resources);
758
- }
759
- this.#runs.clear();
760
- }
761
- runAttempt(input: Parameters<WorkflowWorker["runAttempt"]>[0]) {
762
- return this.#phase.runAttempt(input);
763
- }
764
-
765
- async prepareTask(input: Parameters<WorkflowWorker["runAttempt"]>[0]) {
766
- const resources = await this.#prepareResources(input, input.signal);
767
- const workspace = {
768
- baselineRevisionId: resources.baselineRevisionId,
769
- currentWorkspaceRevisionId: resources.currentRevisionId,
770
- };
771
- if (hasVerificationLifecycle(input.plan)) {
772
- const captured = await this.#verification.ensureVerificationBaseline({
773
- resources,
774
- ledger: this.#ledger(resources, input.deliveryRevision),
775
- taskId: input.taskId,
776
- signal: input.signal,
777
- });
778
- if (!captured.ok)
779
- return {
780
- ...captured.outcome,
781
- ...workspace,
782
- ...(captured.outcome.kind === "paused"
783
- ? {
784
- verification: {
785
- scope: "baseline" as const,
786
- attribution: "environment" as const,
787
- },
788
- }
789
- : {}),
790
- };
791
- }
792
- return { kind: "prepared" as const, ...workspace };
793
- }
794
- hasPendingVerification(input: Parameters<WorkflowWorker["runAttempt"]>[0]) {
795
- return this.#phase.hasPendingVerification(input);
796
- }
797
- verify(input: Parameters<WorkflowChangeVerifier["verify"]>[0]) {
798
- return this.#verification.verify(input);
799
- }
800
- }
801
-
802
- export function openDurableWorkflowEngine(
803
- options: DurableWorkflowEngineOptions,
804
- ): WorkflowEngine {
805
- const composition = new DurableWorkflowComposition(options);
806
- return WorkflowEngine.open({
807
- consumerRoot: options.consumerRoot,
808
- stateRoot: options.stateRoot,
809
- deliverySource: options.deliverySource,
810
- worker: composition,
811
- ...(options.workHardLimit !== undefined
812
- ? { workHardLimit: options.workHardLimit }
813
- : {}),
814
- changeVerifier: composition,
815
- application: composition,
816
- lifecycle: composition,
817
- ...(options.now ? { now: options.now } : {}),
818
- ...(options.leaseTtlMs !== undefined
819
- ? { leaseTtlMs: options.leaseTtlMs }
820
- : {}),
821
- });
822
- }