@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
@@ -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 ["delivery-invalid", "needs-task-split", "task-split-needed"].includes(
236
- code,
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: EngineTaskRow,
574
- _run: EngineRunRow,
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 {