@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.
- package/README.md +25 -4
- package/config/.env.example +6 -0
- 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/apply-transaction.ts +28 -8
- package/src/build-windows-job.mjs +95 -0
- package/src/candidate-workspace.ts +6 -0
- package/src/change-verification.ts +764 -67
- 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 +21 -3
- package/src/durable-workflow.ts +55 -40
- package/src/execution-profile.ts +28 -2
- package/src/execution-retention.ts +77 -0
- 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/isolation-backend.ts +2 -1
- package/src/operator-cli.mjs +664 -104
- package/src/operator-tools.ts +7 -1
- package/src/package-state.ts +11 -0
- package/src/package-verification.ts +66 -18
- package/src/package-workflow.ts +6 -0
- package/src/phase-execution.ts +18 -7
- package/src/plan-draft-summary.ts +385 -7
- package/src/plan-draft.ts +9 -0
- package/src/remove-path.ts +32 -0
- package/src/subagent-activity.ts +3 -1
- package/src/verification-capability.ts +18 -6
- package/src/verification-environment-io.ts +123 -14
- package/src/verification-environment.ts +50 -3
- package/src/windows-job-backend.ts +419 -0
- package/src/windows-job.c +149 -0
- 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/workspace-io-entry.mjs +2 -2
- package/src/workspace-io-worker.mjs +207 -110
package/src/workflow-policy.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { WorkflowActivityUpdate } from "./activity-contracts.ts";
|
|
|
5
5
|
import { compareCanonicalStrings } from "./canonical.ts";
|
|
6
6
|
import {
|
|
7
7
|
type ApprovalBoundaryCode,
|
|
8
|
+
CHILD_TRANSPORT_CODES,
|
|
8
9
|
isValidRelativePath,
|
|
9
10
|
type StructuredVerificationContract,
|
|
10
11
|
verificationBoundInputPaths,
|
|
@@ -232,8 +233,257 @@ export type WorkflowApprovalCode = ApprovalBoundaryCode | InternalApprovalCode;
|
|
|
232
233
|
// These pauses need a new executable plan, not a new user decision. Environment,
|
|
233
234
|
// integrity and cancellation codes deliberately do not grant this continuation.
|
|
234
235
|
export function permitsPlanAmendment(code: string): boolean {
|
|
235
|
-
return ["
|
|
236
|
-
|
|
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)
|
|
237
487
|
);
|
|
238
488
|
}
|
|
239
489
|
|
|
@@ -309,6 +559,7 @@ export type WorkflowAttemptOutcome = (
|
|
|
309
559
|
code: string;
|
|
310
560
|
untrustedCandidate?: Uint8Array;
|
|
311
561
|
verification?: WorkflowVerificationStatus;
|
|
562
|
+
prerequisite?: WorkflowVerificationPrerequisite;
|
|
312
563
|
contextRequest?: WorkflowContextRequest;
|
|
313
564
|
retryPolicy?: WorkflowRetryPolicy;
|
|
314
565
|
}
|
|
@@ -320,6 +571,12 @@ export type WorkflowAttemptOutcome = (
|
|
|
320
571
|
};
|
|
321
572
|
|
|
322
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
|
+
>;
|
|
323
580
|
hasPendingVerification?(
|
|
324
581
|
input: Parameters<WorkflowWorker["runAttempt"]>[0],
|
|
325
582
|
): Promise<boolean>;
|
|
@@ -565,13 +822,14 @@ export interface SafeAttemptDiagnostic {
|
|
|
565
822
|
sameFailureCount?: number;
|
|
566
823
|
action?: "rebind-or-revise-delivery";
|
|
567
824
|
recovery?: WorkflowRecoveryFact;
|
|
825
|
+
prerequisite?: WorkflowVerificationPrerequisite;
|
|
568
826
|
}
|
|
569
827
|
|
|
570
828
|
export function recoveryKey(
|
|
571
829
|
task: PlanTaskDraft,
|
|
572
830
|
phase: string,
|
|
573
|
-
_row
|
|
574
|
-
_run
|
|
831
|
+
_row?: EngineTaskRow,
|
|
832
|
+
_run?: EngineRunRow,
|
|
575
833
|
): string {
|
|
576
834
|
// Keep the Red witness and the active phase's obligation across replanning.
|
|
577
835
|
// Execution identities, verifier names, and wording are not progress.
|
|
@@ -598,6 +856,30 @@ export function recoveryKey(
|
|
|
598
856
|
);
|
|
599
857
|
}
|
|
600
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
|
+
|
|
601
883
|
export interface EngineOperationRow {
|
|
602
884
|
command: string;
|
|
603
885
|
state: string;
|
|
@@ -1245,6 +1527,12 @@ export function taskConflicts(
|
|
|
1245
1527
|
};
|
|
1246
1528
|
}
|
|
1247
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
|
+
|
|
1248
1536
|
export function taskApprovalBoundary(
|
|
1249
1537
|
plan: ImplementPlan,
|
|
1250
1538
|
task: PlanTaskDraft,
|
|
@@ -1260,13 +1548,14 @@ export function taskApprovalBoundary(
|
|
|
1260
1548
|
}
|
|
1261
1549
|
// Retry limits and presentation do not invalidate verified product facts.
|
|
1262
1550
|
if (isRecord(planBoundary.verification)) {
|
|
1551
|
+
delete planBoundary.verification.baseline;
|
|
1263
1552
|
delete planBoundary.verification.artifactCorrection;
|
|
1264
1553
|
if (isRecord(planBoundary.verification.repair))
|
|
1265
1554
|
delete planBoundary.verification.repair.maxAttempts;
|
|
1266
1555
|
}
|
|
1267
1556
|
return canonicalJson({
|
|
1268
1557
|
plan: planBoundary,
|
|
1269
|
-
task,
|
|
1558
|
+
task: taskExecutionContract(task),
|
|
1270
1559
|
outputs: plan.outputs.filter(
|
|
1271
1560
|
(output) => output.producer.taskId === task.taskId,
|
|
1272
1561
|
),
|
|
@@ -1350,6 +1639,8 @@ export function deliveryBoundPaths(plan: ImplementPlan): string[] {
|
|
|
1350
1639
|
bindVerification(phase.verification);
|
|
1351
1640
|
}
|
|
1352
1641
|
if (hasVerificationLifecycle(plan)) {
|
|
1642
|
+
if (task.baselineVerification)
|
|
1643
|
+
bindVerification(task.baselineVerification);
|
|
1353
1644
|
bindVerification(task.affectedVerification);
|
|
1354
1645
|
bindVerification(task.repairVerification);
|
|
1355
1646
|
}
|
|
@@ -46,6 +46,8 @@ export interface WorkflowRecoveryFeedback {
|
|
|
46
46
|
| "repair-verification"
|
|
47
47
|
| "compact-patch";
|
|
48
48
|
failureIdentities?: string[];
|
|
49
|
+
/** Last observed baseline condition; execution/display renaming cannot refund failures. */
|
|
50
|
+
prerequisiteIdentity?: string;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
export interface WorkflowRecoveryFact {
|