@abelxiaoxing/cadence 1.6.0 → 1.8.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 (45) hide show
  1. package/README.md +25 -4
  2. package/config/.env.example +6 -0
  3. package/config/plan-draft.example.json +3 -0
  4. package/config/plan-draft.multiple-tasks.example.json +30 -0
  5. package/package.json +1 -1
  6. package/prompts/abel-design.md +33 -3
  7. package/prompts/abel-implement.md +36 -4
  8. package/src/apply-transaction.ts +28 -8
  9. package/src/build-windows-job.mjs +95 -0
  10. package/src/candidate-workspace.ts +6 -0
  11. package/src/change-verification.ts +764 -67
  12. package/src/contracts.ts +8 -0
  13. package/src/delivery-compiler.ts +452 -9
  14. package/src/design-control.ts +2 -2
  15. package/src/design-diagnostics.ts +6 -4
  16. package/src/durable-contracts.ts +21 -3
  17. package/src/durable-workflow.ts +55 -40
  18. package/src/execution-profile.ts +28 -2
  19. package/src/execution-retention.ts +77 -0
  20. package/src/implement-continuation.ts +559 -0
  21. package/src/implement-graph.ts +114 -0
  22. package/src/implement-plan.ts +2 -0
  23. package/src/index.ts +151 -1
  24. package/src/isolation-backend.ts +2 -1
  25. package/src/operator-cli.mjs +664 -104
  26. package/src/operator-tools.ts +7 -1
  27. package/src/package-state.ts +11 -0
  28. package/src/package-verification.ts +66 -18
  29. package/src/package-workflow.ts +6 -0
  30. package/src/phase-execution.ts +18 -7
  31. package/src/plan-draft-summary.ts +385 -7
  32. package/src/plan-draft.ts +9 -0
  33. package/src/remove-path.ts +32 -0
  34. package/src/subagent-activity.ts +3 -1
  35. package/src/verification-capability.ts +18 -6
  36. package/src/verification-environment-io.ts +123 -14
  37. package/src/verification-environment.ts +50 -3
  38. package/src/windows-job-backend.ts +419 -0
  39. package/src/windows-job.c +149 -0
  40. package/src/workflow-policy.ts +296 -5
  41. package/src/workflow-recovery-policy.ts +2 -0
  42. package/src/workflow-state-machine.ts +314 -80
  43. package/src/workflow-status.ts +251 -59
  44. package/src/workspace-io-entry.mjs +2 -2
  45. package/src/workspace-io-worker.mjs +207 -110
@@ -8,10 +8,14 @@ import {
8
8
  type EngineRunRow,
9
9
  type EngineTaskRow,
10
10
  hash,
11
+ normalizeVerificationPrerequisite,
11
12
  normalizeWorkflowContextRequest,
12
13
  normalizeWorkflowVerificationStatus,
14
+ parentRecoveryStrategy,
13
15
  parseDeliveryDiagnostics,
14
16
  permitsPlanAmendment,
17
+ permitsPrerequisiteAmendment,
18
+ permitsRecoveryAmendment,
15
19
  type SafeAttemptDiagnostic,
16
20
  } from "./workflow-policy.ts";
17
21
 
@@ -60,6 +64,9 @@ export function projectWorkflowStatus({
60
64
  (right.queue_position ?? Number.MAX_SAFE_INTEGER) ||
61
65
  left.task_order - right.task_order,
62
66
  );
67
+ const activeRows = rows.filter((row) =>
68
+ ["approval-needed", "paused", "retryable"].includes(row.state),
69
+ );
63
70
  const firstPaused = firstPausedTask(rows);
64
71
  const contextRequest = firstPaused?.context_request_json
65
72
  ? normalizeWorkflowContextRequest(
@@ -71,6 +78,10 @@ export function projectWorkflowStatus({
71
78
  firstPaused?.state === "approval-needed"
72
79
  ? (firstPaused.pause_code ?? projection.pauseCode ?? "task-paused")
73
80
  : (projection.pauseCode ?? firstPaused?.pause_code ?? "task-paused");
81
+ const taskPauseSuperseded =
82
+ projection.state === "paused" &&
83
+ projection.pauseCode !== undefined &&
84
+ !activeRows.some((row) => row.pause_code === projection.pauseCode);
74
85
  const approval =
75
86
  projection.state === "approval-needed"
76
87
  ? approvalRequirement(pauseCode, contextRequest)
@@ -78,36 +89,75 @@ export function projectWorkflowStatus({
78
89
  const attemptDiagnostic = firstPaused
79
90
  ? attemptDiagnostics.get(firstPaused.task_id)
80
91
  : undefined;
81
- const recovery =
82
- rows
83
- .map((row) => attemptDiagnostics.get(row.task_id)?.recovery)
84
- .find((fact) => fact && fact.failures >= fact.feedback.maxAttempts) ??
85
- attemptDiagnostic?.recovery;
92
+ const recovery = taskPauseSuperseded
93
+ ? undefined
94
+ : attemptDiagnostic?.recovery;
86
95
  const recoveryExhausted =
87
96
  recovery && recovery.failures >= recovery.feedback.maxAttempts;
88
- const blockers = rows
89
- .filter((row) =>
90
- ["approval-needed", "paused", "retryable"].includes(row.state),
91
- )
92
- .map((row) => {
93
- const context = row.context_request_json
94
- ? normalizeWorkflowContextRequest(JSON.parse(row.context_request_json))
97
+ const selectedPrerequisite = normalizeVerificationPrerequisite(
98
+ taskPauseSuperseded ? undefined : attemptDiagnostic?.prerequisite,
99
+ );
100
+ const selectedRecoveryStrategy = parentRecoveryStrategy(
101
+ pauseCode,
102
+ taskPauseSuperseded ? undefined : attemptDiagnostic,
103
+ );
104
+ const selectedPauseActionable =
105
+ (projection.state === "approval-needed" && approval !== undefined) ||
106
+ (projection.state === "paused" &&
107
+ (permitsPlanAmendment(pauseCode) ||
108
+ selectedRecoveryStrategy !== undefined ||
109
+ permitsPrerequisiteAmendment(selectedPrerequisite)));
110
+ const blockers = activeRows.map((row) => {
111
+ const context = row.context_request_json
112
+ ? normalizeWorkflowContextRequest(JSON.parse(row.context_request_json))
113
+ : undefined;
114
+ const code = row.pause_code ?? "task-paused";
115
+ const diagnostic = attemptDiagnostics.get(row.task_id);
116
+ const prerequisite = normalizeVerificationPrerequisite(
117
+ diagnostic?.prerequisite,
118
+ );
119
+ const requirement =
120
+ row.state === "approval-needed"
121
+ ? approvalRequirement(code, context)
95
122
  : undefined;
96
- const code = row.pause_code ?? "task-paused";
97
- const requirement =
98
- row.state === "approval-needed"
99
- ? approvalRequirement(code, context)
100
- : undefined;
101
- return {
102
- id: hash(runId, row.task_id, code, canonicalJson(context ?? {})),
103
- taskId: row.task_id,
104
- phase: row.phase,
105
- code,
106
- kind: requirement ? "decision" : "execution",
107
- ...(requirement ?? {}),
108
- ...(context ? { contextRequest: context } : {}),
109
- };
110
- });
123
+ return {
124
+ id: hash(runId, row.task_id, code, canonicalJson(context ?? {})),
125
+ taskId: row.task_id,
126
+ phase: row.phase,
127
+ code,
128
+ kind: requirement ? "decision" : "execution",
129
+ ...(requirement ?? {}),
130
+ ...(context ? { contextRequest: context } : {}),
131
+ ...(prerequisite
132
+ ? {
133
+ prerequisite,
134
+ scope:
135
+ prerequisite.scope === "baseline-full-suite" ? "change" : "task",
136
+ minimumRecovery: permitsPrerequisiteAmendment(prerequisite)
137
+ ? "Revise the baseline contract to read safe inputs from the retained original revision, then recompile within the existing authority."
138
+ : prerequisite.cause === "capability"
139
+ ? "Restore the required runner or external capability; a changed environment identity permits a bounded check."
140
+ : "Restore trusted verification prerequisites; the retained failure does not authorize plan changes.",
141
+ }
142
+ : {}),
143
+ ...(diagnostic?.recovery
144
+ ? {
145
+ recovery: {
146
+ code: diagnostic.recovery.feedback.code,
147
+ attempts: diagnostic.recovery.failures,
148
+ maxAttempts: diagnostic.recovery.feedback.maxAttempts,
149
+ strategy: diagnostic.recovery.feedback.strategy,
150
+ ...(diagnostic.recovery.feedback.failureIdentities
151
+ ? {
152
+ failureIdentities:
153
+ diagnostic.recovery.feedback.failureIdentities,
154
+ }
155
+ : {}),
156
+ },
157
+ }
158
+ : {}),
159
+ };
160
+ });
111
161
  if (
112
162
  projection.state === "approval-needed" &&
113
163
  !blockers.some((item) => item.kind === "decision") &&
@@ -123,9 +173,19 @@ export function projectWorkflowStatus({
123
173
  });
124
174
  }
125
175
  const decisions = blockers.filter((item) => item.kind === "decision");
126
- const amendments = blockers.filter(
127
- (item) => item.kind === "decision" || permitsPlanAmendment(item.code),
128
- );
176
+ const amendments = !selectedPauseActionable
177
+ ? []
178
+ : blockers.filter(
179
+ (item) =>
180
+ item.kind === "decision" ||
181
+ permitsPlanAmendment(item.code) ||
182
+ permitsRecoveryAmendment(
183
+ item.code,
184
+ attemptDiagnostics.get(item.taskId),
185
+ ) ||
186
+ ("prerequisite" in item &&
187
+ permitsPrerequisiteAmendment(item.prerequisite)),
188
+ );
129
189
  if (
130
190
  projection.state === "paused" &&
131
191
  permitsPlanAmendment(pauseCode) &&
@@ -147,6 +207,16 @@ export function projectWorkflowStatus({
147
207
  remaining: 64 - amendmentUsed,
148
208
  exhausted: amendmentUsed >= 64,
149
209
  };
210
+ const budgetCode = [
211
+ "change-work-budget-exhausted",
212
+ "change-recovery-budget-exhausted",
213
+ ].includes(pauseCode)
214
+ ? pauseCode
215
+ : undefined;
216
+ const budgetExhausted = budgetCode !== undefined;
217
+ const hasWorkCapacity =
218
+ !budgetExhausted &&
219
+ (!resourceBudget || resourceBudget.used < resourceBudget.maximum);
150
220
  const decisionBatch =
151
221
  amendments.length &&
152
222
  pauseCode !== "operation-cancelled" &&
@@ -179,12 +249,139 @@ export function projectWorkflowStatus({
179
249
  ].sort(),
180
250
  }
181
251
  : undefined;
182
- const budgetCode = rows.find(
183
- (row) =>
184
- row.pause_code === "change-work-budget-exhausted" ||
185
- row.pause_code === "change-recovery-budget-exhausted",
186
- )?.pause_code;
187
- const budgetExhausted = budgetCode !== undefined;
252
+ const guidanceRow = taskPauseSuperseded ? undefined : firstPaused;
253
+ const guidanceDiagnostic = taskPauseSuperseded
254
+ ? undefined
255
+ : attemptDiagnostic;
256
+ const guidanceCode = pauseCode;
257
+ const guidanceStrategy = selectedRecoveryStrategy;
258
+ const failureSequence = recovery
259
+ ? failureSequences.get(recovery.key)
260
+ : undefined;
261
+ const recoveryGrant =
262
+ recoveryExhausted &&
263
+ guidanceStrategy !== undefined &&
264
+ projection.state === "paused" &&
265
+ hasWorkCapacity &&
266
+ resourceBudget &&
267
+ failureSequence !== undefined &&
268
+ Number.isSafeInteger(failureSequence) &&
269
+ failureSequence > 0
270
+ ? {
271
+ incidentKey: recovery.key,
272
+ failureSequence,
273
+ reason: "parent-directed-retry" as const,
274
+ }
275
+ : undefined;
276
+ const recoveryContinuation =
277
+ projection.state === "paused" &&
278
+ hasWorkCapacity &&
279
+ guidanceStrategy &&
280
+ (!recoveryExhausted || recoveryGrant)
281
+ ? {
282
+ owner: "parent" as const,
283
+ automatic: true,
284
+ kind: "inspect-recovery" as const,
285
+ reason: guidanceCode,
286
+ stage: projection.stage,
287
+ change: projection.change,
288
+ metadata: {
289
+ ...(guidanceRow
290
+ ? { taskId: guidanceRow.task_id, phase: guidanceRow.phase }
291
+ : {}),
292
+ diagnostic: {
293
+ code: guidanceCode,
294
+ strategy:
295
+ guidanceDiagnostic?.recovery?.feedback.strategy ??
296
+ guidanceStrategy,
297
+ ...(guidanceDiagnostic?.recovery
298
+ ? {
299
+ inspection: guidanceStrategy,
300
+ attempts: guidanceDiagnostic.recovery.failures,
301
+ maxAttempts:
302
+ guidanceDiagnostic.recovery.feedback.maxAttempts,
303
+ ...(guidanceDiagnostic.recovery.feedback.failureIdentities
304
+ ? {
305
+ failureIdentities:
306
+ guidanceDiagnostic.recovery.feedback
307
+ .failureIdentities,
308
+ }
309
+ : {}),
310
+ }
311
+ : {}),
312
+ ...(guidanceDiagnostic?.prerequisite
313
+ ? {
314
+ prerequisite: normalizeVerificationPrerequisite(
315
+ guidanceDiagnostic.prerequisite,
316
+ ),
317
+ }
318
+ : {}),
319
+ ...(guidanceRow?.route_id || engineRun?.route_id
320
+ ? {
321
+ route: {
322
+ routeId:
323
+ guidanceRow?.route_id ?? engineRun?.route_id ?? "",
324
+ ...((guidanceRow?.route_fingerprint ??
325
+ engineRun?.route_fingerprint)
326
+ ? {
327
+ routeFingerprint:
328
+ guidanceRow?.route_fingerprint ??
329
+ engineRun?.route_fingerprint,
330
+ }
331
+ : {}),
332
+ },
333
+ }
334
+ : {}),
335
+ },
336
+ ...(recoveryGrant
337
+ ? {
338
+ recommendation: {
339
+ kind: "bounded-additional-attempt" as const,
340
+ resume: { recovery: recoveryGrant },
341
+ },
342
+ }
343
+ : {}),
344
+ },
345
+ }
346
+ : undefined;
347
+ const amendmentContinuation =
348
+ decisionBatch && !amendmentBudget.exhausted && hasWorkCapacity
349
+ ? {
350
+ owner: "parent" as const,
351
+ automatic: true,
352
+ ...decisionBatch.continuation,
353
+ }
354
+ : undefined;
355
+ const interruptedContinuation =
356
+ projection.stage === "abel-implement" &&
357
+ projection.change &&
358
+ projection.pauseCode === "operation-interrupted" &&
359
+ legalControlCommands(projection.state).includes("resume") &&
360
+ (projection.state === "recovering" ||
361
+ (projection.state === "paused" && hasWorkCapacity))
362
+ ? {
363
+ owner: "parent" as const,
364
+ automatic: true,
365
+ command: "resume" as const,
366
+ kind:
367
+ projection.state === "recovering"
368
+ ? ("settle-apply-recovery" as const)
369
+ : ("resume-interrupted-operation" as const),
370
+ reason: "operation-interrupted" as const,
371
+ stage: projection.stage,
372
+ change: projection.change,
373
+ ...(guidanceRow?.pause_code === "operation-interrupted"
374
+ ? {
375
+ metadata: {
376
+ taskId: guidanceRow.task_id,
377
+ phase: guidanceRow.phase,
378
+ },
379
+ }
380
+ : {}),
381
+ }
382
+ : undefined;
383
+ const continuation =
384
+ interruptedContinuation ?? recoveryContinuation ?? amendmentContinuation;
188
385
  const currentRevision = projection.deliveryRevision ?? 0;
189
386
  return {
190
387
  runId: projection.runId,
@@ -220,17 +417,6 @@ export function projectWorkflowStatus({
220
417
  ? {
221
418
  decisionBatch,
222
419
  amendmentBudget,
223
- ...(!amendmentBudget.exhausted &&
224
- !budgetExhausted &&
225
- (!resourceBudget || resourceBudget.used < resourceBudget.maximum)
226
- ? {
227
- continuation: {
228
- owner: "parent",
229
- automatic: true,
230
- ...decisionBatch.continuation,
231
- },
232
- }
233
- : {}),
234
420
  }
235
421
  : {}),
236
422
  ...(recoveryExhausted
@@ -242,20 +428,11 @@ export function projectWorkflowStatus({
242
428
  attempts: recovery.failures,
243
429
  maxAttempts: recovery.feedback.maxAttempts,
244
430
  automaticRetryExhausted: true,
245
- ...(!budgetExhausted &&
246
- resourceBudget &&
247
- resourceBudget.used < resourceBudget.maximum
248
- ? {
249
- additionalAttempt: {
250
- incidentKey: recovery.key,
251
- failureSequence: failureSequences.get(recovery.key) ?? 0,
252
- reason: "parent-directed-retry",
253
- },
254
- }
255
- : {}),
431
+ ...(recoveryGrant ? { additionalAttempt: recoveryGrant } : {}),
256
432
  },
257
433
  }
258
434
  : {}),
435
+ ...(continuation ? { continuation } : {}),
259
436
  ...(projection.terminal ? { terminal: projection.terminal } : {}),
260
437
  ...(projection.pauseCode || firstPaused?.pause_code
261
438
  ? {
@@ -298,6 +475,10 @@ export function projectWorkflowStatus({
298
475
  receiptHash: "matching-ready-receipt",
299
476
  },
300
477
  },
478
+ }
479
+ : {}),
480
+ ...(approval && projection.change
481
+ ? {
301
482
  conditionalCommands: [
302
483
  {
303
484
  command: "resume",
@@ -310,7 +491,18 @@ export function projectWorkflowStatus({
310
491
  },
311
492
  ],
312
493
  }
313
- : {}),
494
+ : recoveryGrant && projection.change
495
+ ? {
496
+ conditionalCommands: [
497
+ {
498
+ command: "resume",
499
+ stage: "abel-implement",
500
+ change: projection.change,
501
+ requires: { recovery: recoveryGrant },
502
+ },
503
+ ],
504
+ }
505
+ : {}),
314
506
  tasks: rows.map((row) => ({
315
507
  taskId: row.task_id,
316
508
  state: row.state,
@@ -1,6 +1,6 @@
1
- import { rmSync } from "node:fs";
2
1
  import { parentPort, workerData } from "node:worker_threads";
3
2
  import { ArtifactStore } from "./artifact-store.ts";
3
+ import { removePathSync } from "./remove-path.ts";
4
4
  import { prepareVerificationEnvironmentIo } from "./verification-environment-io.ts";
5
5
  import { verificationEnvironmentDigest } from "./verification-identity.ts";
6
6
  import { WorkspaceStore } from "./workspace-store.ts";
@@ -26,7 +26,7 @@ try {
26
26
  );
27
27
  parentPort.postMessage({ ok: true, result, files: 0, bytes: 0 });
28
28
  } else if (workerData.operation === "cleanupVerification") {
29
- rmSync(workerData.args[0], { recursive: true, force: true });
29
+ removePathSync(workerData.args[0], { recursive: true, force: true });
30
30
  parentPort.postMessage({ ok: true, files: 0, bytes: 0 });
31
31
  } else if (workerData.operation === "verificationIdentity") {
32
32
  const result = verificationEnvironmentDigest(