@abelxiaoxing/cadence 1.6.0 → 1.7.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.
- package/README.md +18 -1
- package/config/plan-draft.example.json +3 -0
- package/config/plan-draft.multiple-tasks.example.json +30 -0
- package/package.json +1 -1
- package/prompts/abel-design.md +33 -3
- package/prompts/abel-implement.md +36 -4
- package/src/change-verification.ts +758 -65
- package/src/contracts.ts +8 -0
- package/src/delivery-compiler.ts +452 -9
- package/src/design-control.ts +2 -2
- package/src/design-diagnostics.ts +6 -4
- package/src/durable-contracts.ts +19 -3
- package/src/durable-workflow.ts +47 -38
- package/src/implement-continuation.ts +559 -0
- package/src/implement-graph.ts +114 -0
- package/src/implement-plan.ts +2 -0
- package/src/index.ts +151 -1
- package/src/operator-cli.mjs +12 -7
- package/src/package-verification.ts +17 -1
- package/src/package-workflow.ts +3 -0
- package/src/phase-execution.ts +7 -1
- package/src/plan-draft-summary.ts +385 -7
- package/src/plan-draft.ts +9 -0
- package/src/subagent-activity.ts +3 -1
- package/src/verification-capability.ts +17 -5
- package/src/verification-environment.ts +20 -2
- package/src/workflow-policy.ts +296 -5
- package/src/workflow-recovery-policy.ts +2 -0
- package/src/workflow-state-machine.ts +314 -80
- package/src/workflow-status.ts +251 -59
package/src/workflow-status.ts
CHANGED
|
@@ -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
|
-
|
|
83
|
-
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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 =
|
|
127
|
-
|
|
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
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
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
|
-
...(
|
|
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,
|