@fitlab-ai/agent-infra 0.9.6 → 0.9.8

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 (178) hide show
  1. package/dist/bin/internal-cli.js +10 -0
  2. package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
  8. package/dist/lib/decide.js +68 -14
  9. package/dist/lib/defaults.json +10 -2
  10. package/dist/lib/git/workflow.js +30 -3
  11. package/dist/lib/init.js +1 -1
  12. package/dist/lib/internal/codex-lifecycle.js +69 -1
  13. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  14. package/dist/lib/internal/release-workflow.js +1 -3
  15. package/dist/lib/internal/task-activity.js +57 -7
  16. package/dist/lib/internal/task-event.js +57 -4
  17. package/dist/lib/internal/task-ledger.js +59 -5
  18. package/dist/lib/internal/task-lifecycle.js +59 -8
  19. package/dist/lib/internal/task-orchestration.js +48 -8
  20. package/dist/lib/internal/task-override.js +131 -0
  21. package/dist/lib/internal/task-review.js +64 -6
  22. package/dist/lib/internal/task-verify.js +65 -3
  23. package/dist/lib/internal/task-warning.js +57 -4
  24. package/dist/lib/platform/context.js +6 -4
  25. package/dist/lib/platform/github-client.js +55 -2
  26. package/dist/lib/platform/pr-summary.js +5 -3
  27. package/dist/lib/process-data/checkpoint.js +319 -0
  28. package/dist/lib/process-data/index.js +128 -60
  29. package/dist/lib/process-data/materialize.js +84 -0
  30. package/dist/lib/process-data/normalize.js +34 -1
  31. package/dist/lib/process-data/reconcile.js +55 -1
  32. package/dist/lib/process-data/sources.js +422 -1
  33. package/dist/lib/process-data/store.js +341 -11
  34. package/dist/lib/runtime/agent-runtime.js +56 -0
  35. package/dist/lib/sandbox/commands/create.js +394 -264
  36. package/dist/lib/sandbox/commands/rm.js +64 -15
  37. package/dist/lib/sandbox/commands/start.js +4 -3
  38. package/dist/lib/sandbox/control/container-identity.js +64 -0
  39. package/dist/lib/sandbox/control/executor.js +23 -9
  40. package/dist/lib/sandbox/control/lifecycle.js +590 -61
  41. package/dist/lib/sandbox/control/protocol.js +7 -0
  42. package/dist/lib/sandbox/control/server.js +202 -26
  43. package/dist/lib/sandbox/control/state.js +51 -25
  44. package/dist/lib/sandbox/control/timing.js +8 -0
  45. package/dist/lib/sandbox/engine.js +14 -7
  46. package/dist/lib/sandbox/mounts.js +7 -0
  47. package/dist/lib/sandbox/recovery.js +135 -50
  48. package/dist/lib/sandbox/workspace-view.js +59 -20
  49. package/dist/lib/server/daemon.js +1 -1
  50. package/dist/lib/server/process-control.js +11 -1
  51. package/dist/lib/server/process-state.js +107 -20
  52. package/dist/lib/task/activity-intent.js +4 -1
  53. package/dist/lib/task/artifact-lifecycle.js +82 -11
  54. package/dist/lib/task/artifact-receipts.js +128 -0
  55. package/dist/lib/task/codex-orchestration.js +81 -6
  56. package/dist/lib/task/commands/validate.js +1 -1
  57. package/dist/lib/task/create.js +10 -2
  58. package/dist/lib/task/decision-intents.js +4 -2
  59. package/dist/lib/task/delegation-receipts.js +120 -17
  60. package/dist/lib/task/events.js +148 -7
  61. package/dist/lib/task/guard-override.js +31 -0
  62. package/dist/lib/task/human-override.js +1240 -0
  63. package/dist/lib/task/ledger-intents.js +6 -3
  64. package/dist/lib/task/ledger.js +13 -1
  65. package/dist/lib/task/lifecycle.js +69 -14
  66. package/dist/lib/task/orchestration.js +293 -18
  67. package/dist/lib/task/review-finalization.js +3 -2
  68. package/dist/lib/task/sections.js +5 -1
  69. package/dist/lib/task/task-execution-lock.js +5 -4
  70. package/dist/lib/task/verification-engine.js +80 -22
  71. package/dist/lib/task/verification.js +13 -1
  72. package/dist/lib/task/workflow-warning-intents.js +4 -2
  73. package/dist/lib/task/write.js +2 -1
  74. package/dist/lifecycle-build-manifest.json +6 -0
  75. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  76. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
  77. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  78. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  80. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
  81. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
  82. package/lib/decide.ts +59 -14
  83. package/lib/defaults.json +10 -2
  84. package/lib/git/workflow.ts +25 -2
  85. package/lib/init.ts +1 -1
  86. package/lib/internal/codex-lifecycle.ts +68 -1
  87. package/lib/internal/codex-sandbox-controller.ts +109 -0
  88. package/lib/internal/release-workflow.ts +1 -3
  89. package/lib/internal/task-activity.ts +46 -6
  90. package/lib/internal/task-event.ts +53 -4
  91. package/lib/internal/task-ledger.ts +48 -4
  92. package/lib/internal/task-lifecycle.ts +56 -12
  93. package/lib/internal/task-orchestration.ts +43 -8
  94. package/lib/internal/task-override.ts +138 -0
  95. package/lib/internal/task-review.ts +51 -6
  96. package/lib/internal/task-verify.ts +52 -3
  97. package/lib/internal/task-warning.ts +44 -3
  98. package/lib/platform/context.ts +8 -5
  99. package/lib/platform/github-client.ts +61 -3
  100. package/lib/platform/pr-summary.ts +10 -3
  101. package/lib/process-data/checkpoint.ts +335 -0
  102. package/lib/process-data/index.ts +112 -60
  103. package/lib/process-data/materialize.ts +83 -0
  104. package/lib/process-data/normalize.ts +29 -1
  105. package/lib/process-data/reconcile.ts +64 -2
  106. package/lib/process-data/sources.ts +449 -2
  107. package/lib/process-data/store.ts +304 -11
  108. package/lib/process-data/types.ts +124 -2
  109. package/lib/runtime/agent-runtime.ts +76 -0
  110. package/lib/sandbox/commands/create.ts +139 -6
  111. package/lib/sandbox/commands/rm.ts +64 -14
  112. package/lib/sandbox/commands/start.ts +4 -3
  113. package/lib/sandbox/config.ts +2 -1
  114. package/lib/sandbox/control/container-identity.ts +93 -0
  115. package/lib/sandbox/control/executor.ts +24 -12
  116. package/lib/sandbox/control/lifecycle.ts +685 -67
  117. package/lib/sandbox/control/protocol.ts +35 -6
  118. package/lib/sandbox/control/server.ts +184 -24
  119. package/lib/sandbox/control/state.ts +67 -33
  120. package/lib/sandbox/control/timing.ts +12 -0
  121. package/lib/sandbox/engine.ts +20 -9
  122. package/lib/sandbox/mounts.ts +8 -0
  123. package/lib/sandbox/recovery.ts +159 -49
  124. package/lib/sandbox/workspace-view.ts +73 -22
  125. package/lib/server/daemon.ts +1 -1
  126. package/lib/server/process-control.ts +13 -3
  127. package/lib/server/process-state.ts +129 -27
  128. package/lib/task/activity-intent.ts +4 -2
  129. package/lib/task/artifact-lifecycle.ts +73 -10
  130. package/lib/task/artifact-receipts.ts +161 -0
  131. package/lib/task/codex-orchestration.ts +91 -5
  132. package/lib/task/commands/validate.ts +1 -1
  133. package/lib/task/create.ts +11 -2
  134. package/lib/task/decision-intents.ts +4 -2
  135. package/lib/task/delegation-receipts.ts +205 -25
  136. package/lib/task/events.ts +132 -6
  137. package/lib/task/guard-override.ts +50 -0
  138. package/lib/task/human-override.ts +1503 -0
  139. package/lib/task/ledger-intents.ts +6 -3
  140. package/lib/task/ledger.ts +16 -1
  141. package/lib/task/lifecycle.ts +71 -14
  142. package/lib/task/orchestration.ts +443 -22
  143. package/lib/task/review-finalization.ts +6 -2
  144. package/lib/task/sections.ts +6 -2
  145. package/lib/task/task-execution-lock.ts +11 -9
  146. package/lib/task/verification-engine.ts +93 -32
  147. package/lib/task/verification.ts +15 -1
  148. package/lib/task/workflow-warning-intents.ts +4 -2
  149. package/lib/task/write.ts +4 -1
  150. package/package.json +1 -1
  151. package/templates/.agents/README.en.md +7 -0
  152. package/templates/.agents/README.zh-CN.md +5 -0
  153. package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
  154. package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
  155. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
  156. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  157. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  158. package/templates/.agents/rules/review-handshake.en.md +3 -1
  159. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  160. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  161. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  162. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  163. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  164. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
  165. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
  166. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
  167. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
  168. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  169. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  170. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
  171. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
  172. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  173. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  174. package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
  175. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
  176. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  177. package/templates/.agents/workspace/README.en.md +0 -26
  178. package/templates/.agents/workspace/README.zh-CN.md +0 -26
@@ -10,14 +10,23 @@ import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.
10
10
  import { resolveTaskRef } from './resolve-ref.ts';
11
11
  import {
12
12
  activateDelegation,
13
+ abortPreparedDelegation,
13
14
  completeDelegationStage,
14
15
  consumeDelegation,
16
+ dispatchDelegation,
17
+ foldBlankToNull,
15
18
  managedDelegationRole,
16
19
  prepareDelegation,
17
20
  sealDelegation
18
21
  } from './delegation-receipts.ts';
19
- import type { DelegationReceipt, DelegationRole, DelegationStage } from './delegation-receipts.ts';
22
+ import type {
23
+ DelegationLifecycleProvenance,
24
+ DelegationReceipt,
25
+ DelegationRole,
26
+ DelegationStage
27
+ } from './delegation-receipts.ts';
20
28
  import { normalizeAgentClients } from '../agent-clients/config.ts';
29
+ import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.ts';
21
30
  import { inspectPlatformPullRequest } from '../platform/pull-requests.ts';
22
31
  import {
23
32
  getAgentClientCapability,
@@ -39,6 +48,7 @@ import type { RepositorySnapshot } from './workspace-snapshot.ts';
39
48
  import type { WorkspaceSnapshotContext } from './workspace-snapshot.ts';
40
49
  import { assertGitRepositoryBinding } from '../git/worktree-identity.ts';
41
50
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
51
+ import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.ts';
42
52
  import {
43
53
  CommitIntentError,
44
54
  commitIntentPath,
@@ -104,7 +114,44 @@ type ClientCapabilityRecovery = Readonly<{
104
114
  }>;
105
115
  resultingStatus: 'running';
106
116
  }>;
107
- type OrchestrationRecovery = ModelPolicyRecovery | ClientCapabilityRecovery;
117
+ type ClaudeCodeCapabilityRecovery = Readonly<{
118
+ code: 'CLIENT_CAPABILITY_ENABLED_NO_MIGRATION';
119
+ recoveredAt: string;
120
+ previousSchemaVersion: 3;
121
+ previousStatus: 'paused';
122
+ previousPause: Readonly<{ code: 'ORCHESTRATION_CLIENT_UNSUPPORTED'; message: string; recoverable: boolean }>;
123
+ client: 'claude-code';
124
+ guards: Readonly<{
125
+ stepCount: 0;
126
+ nextStage: null;
127
+ baselineEmpty: true;
128
+ receiptCount: 0;
129
+ pendingDelegation: false;
130
+ commitAuthorizationUnused: true;
131
+ completionEvidenceAbsent: true;
132
+ commitIntentAbsent: true;
133
+ }>;
134
+ resultingStatus: 'running';
135
+ }>;
136
+ type SchemaMigrationRecovery = Readonly<{
137
+ code: 'SCHEMA_V3_MIGRATED';
138
+ recoveredAt: string;
139
+ previousSchemaVersion: 2;
140
+ previousStatus: 'paused';
141
+ previousPause: Readonly<{ code: string; message: string; recoverable: boolean }>;
142
+ guards: Readonly<{
143
+ stepCount: 0;
144
+ nextStage: null;
145
+ baselineEmpty: true;
146
+ receiptCount: 0;
147
+ pendingDelegation: false;
148
+ commitAuthorizationUnused: true;
149
+ completionEvidenceAbsent: true;
150
+ commitIntentAbsent: true;
151
+ }>;
152
+ resultingStatus: 'running';
153
+ }>;
154
+ type OrchestrationRecovery = ModelPolicyRecovery | ClientCapabilityRecovery | ClaudeCodeCapabilityRecovery | SchemaMigrationRecovery;
108
155
  type CleanCompletionEvidence = Readonly<{
109
156
  kind: 'reviewed-head-clean';
110
157
  observedAt: string;
@@ -116,7 +163,7 @@ type CleanCompletionEvidence = Readonly<{
116
163
  prHead: string;
117
164
  }>;
118
165
  type OrchestrationRun = Readonly<{
119
- schemaVersion: 1 | 2;
166
+ schemaVersion: 1 | 2 | 3;
120
167
  taskId: string;
121
168
  runId: string;
122
169
  status: OrchestrationStatus;
@@ -193,6 +240,9 @@ type OrchestrationOptions = {
193
240
  diffWorkspace?: (repoRoot: string, before: string, after: string) => string[];
194
241
  supportsLifecycleDelegation?: (client: AgentClientId) => boolean;
195
242
  token?: () => string;
243
+ monotonicNow?: () => number;
244
+ sleep?: (milliseconds: number) => Promise<void>;
245
+ hasActiveLifecycleEvidence?: (receipt: DelegationReceipt) => boolean;
196
246
  };
197
247
 
198
248
  type CommitIntentResult = Readonly<{
@@ -238,7 +288,7 @@ function readRun(taskDir: string): OrchestrationRun | null {
238
288
  const file = orchestrationPath(taskDir);
239
289
  if (!fs.existsSync(file)) return null;
240
290
  const run = JSON.parse(fs.readFileSync(file, 'utf8')) as OrchestrationRun;
241
- if (run.schemaVersion !== 2 || !isV2Policy(run.modelPolicy)) return run;
291
+ if (![2, 3].includes(run.schemaVersion) || !isV2Policy(run.modelPolicy)) return run;
242
292
  return {
243
293
  ...run,
244
294
  modelPolicy: {
@@ -329,7 +379,7 @@ function commitFinalizationView(inspection: CommitFinalizationInspection): NonNu
329
379
 
330
380
  function validCommitRun(run: OrchestrationRun | null, taskId: string): run is OrchestrationRun {
331
381
  return run !== null
332
- && [1, 2].includes(run.schemaVersion)
382
+ && [1, 2, 3].includes(run.schemaVersion)
333
383
  && run.taskId === taskId
334
384
  && ['running', 'paused', 'completed'].includes(run.status)
335
385
  && Array.isArray(run.receipts)
@@ -406,6 +456,38 @@ function canRecoverCodexUnsupportedPause(run: OrchestrationRun, taskDir: string)
406
456
  && (run.recoveryHistory ?? []).every((entry) => entry.code === 'MODEL_POLICY_SUPPLEMENTED');
407
457
  }
408
458
 
459
+ function canRecoverClaudeCodeUnsupportedPause(run: OrchestrationRun, taskDir: string): boolean {
460
+ return run.schemaVersion === 3
461
+ && run.status === 'paused'
462
+ && run.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
463
+ && run.modelPolicySource?.client === 'claude-code'
464
+ && run.stepCount === 0
465
+ && run.nextStage === null
466
+ && run.baseline === ''
467
+ && run.pendingDelegation === null
468
+ && run.receipts.length === 0
469
+ && run.commitAuthorization?.issuedAt === null
470
+ && run.commitAuthorization?.consumedAt === null
471
+ && run.completionEvidence == null
472
+ && !fs.existsSync(commitIntentPath(taskDir))
473
+ && (run.recoveryHistory ?? []).length === 0;
474
+ }
475
+
476
+ function canMigrateRecoverableV2Pause(run: OrchestrationRun, taskDir: string): boolean {
477
+ return run.schemaVersion === 2
478
+ && run.status === 'paused'
479
+ && run.pause?.recoverable === true
480
+ && run.stepCount === 0
481
+ && run.nextStage === null
482
+ && run.baseline === ''
483
+ && run.pendingDelegation === null
484
+ && run.receipts.length === 0
485
+ && run.commitAuthorization?.issuedAt === null
486
+ && run.commitAuthorization?.consumedAt === null
487
+ && run.completionEvidence == null
488
+ && !fs.existsSync(commitIntentPath(taskDir));
489
+ }
490
+
409
491
  function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
410
492
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
411
493
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
@@ -420,7 +502,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
420
502
  }
421
503
  if (existing) {
422
504
  if (
423
- ![1, 2].includes(existing.schemaVersion)
505
+ ![1, 2, 3].includes(existing.schemaVersion)
424
506
  || !Array.isArray(existing.receipts)
425
507
  || !Object.prototype.hasOwnProperty.call(existing, 'pendingDelegation')
426
508
  ) {
@@ -429,7 +511,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
429
511
  if (existing.status === 'completed' && existing.schemaVersion === 1) {
430
512
  return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
431
513
  }
432
- if (existing.schemaVersion === 2) {
514
+ if (existing.schemaVersion === 2 || existing.schemaVersion === 3) {
433
515
  const policyError = validateModelPolicy(isV2Policy(existing.modelPolicy) ? existing.modelPolicy : undefined);
434
516
  if (
435
517
  policyError
@@ -437,11 +519,18 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
437
519
  || !isAgentClientId(existing.modelPolicySource.client)
438
520
  || !Array.isArray(existing.recoveryHistory)
439
521
  ) {
440
- return failed('ORCHESTRATION_STATE_INVALID', 'schemaVersion 2 orchestration state is incomplete', resolved.taskId);
522
+ return failed('ORCHESTRATION_STATE_INVALID', `schemaVersion ${existing.schemaVersion} orchestration state is incomplete`, resolved.taskId);
441
523
  }
442
524
  if (existing.status === 'completed') {
443
525
  return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
444
526
  }
527
+ if (existing.schemaVersion === 2 && existing.status === 'running') {
528
+ return failed(
529
+ 'ORCHESTRATION_STATE_INVALID',
530
+ 'active schemaVersion 2 runs cannot be advanced by lifecycle protocol v3',
531
+ resolved.taskId
532
+ );
533
+ }
445
534
  if (existing.modelPolicySource.client !== options.client) {
446
535
  return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
447
536
  }
@@ -453,6 +542,39 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
453
542
  }
454
543
  }
455
544
  if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
545
+ if (existing.modelPolicySource.client === 'claude-code') {
546
+ if (!canRecoverClaudeCodeUnsupportedPause(existing, resolved.taskDir)) {
547
+ return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
548
+ }
549
+ const now = (options.now ?? (() => new Date().toISOString()))();
550
+ const previousPause = existing.pause as ClaudeCodeCapabilityRecovery['previousPause'];
551
+ const recovery: ClaudeCodeCapabilityRecovery = {
552
+ code: 'CLIENT_CAPABILITY_ENABLED_NO_MIGRATION',
553
+ recoveredAt: now,
554
+ previousSchemaVersion: 3,
555
+ previousStatus: 'paused',
556
+ previousPause,
557
+ client: 'claude-code',
558
+ guards: {
559
+ stepCount: 0,
560
+ nextStage: null,
561
+ baselineEmpty: true,
562
+ receiptCount: 0,
563
+ pendingDelegation: false,
564
+ commitAuthorizationUnused: true,
565
+ completionEvidenceAbsent: true,
566
+ commitIntentAbsent: true
567
+ },
568
+ resultingStatus: 'running'
569
+ };
570
+ const resumed = withUpdatedRun(existing, {
571
+ status: 'running',
572
+ pause: null,
573
+ recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
574
+ }, () => now);
575
+ saveRun(resolved.taskDir, resumed);
576
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
577
+ }
456
578
  if (!canRecoverCodexUnsupportedPause(existing, resolved.taskDir)) {
457
579
  return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
458
580
  }
@@ -478,6 +600,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
478
600
  resultingStatus: 'running'
479
601
  };
480
602
  const resumed = withUpdatedRun(existing, {
603
+ schemaVersion: 3,
481
604
  status: 'running',
482
605
  pause: null,
483
606
  recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
@@ -486,7 +609,42 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
486
609
  return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
487
610
  }
488
611
  if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
489
- const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
612
+ if (existing.schemaVersion === 2 && !canMigrateRecoverableV2Pause(existing, resolved.taskDir)) {
613
+ const paused = withUpdatedRun(existing, {
614
+ pause: {
615
+ code: 'ORCHESTRATION_SCHEMA_MIGRATION_REQUIRED',
616
+ message: 'schemaVersion 2 recoverable pause contains execution evidence and cannot migrate automatically',
617
+ recoverable: false
618
+ }
619
+ });
620
+ saveRun(resolved.taskDir, paused);
621
+ return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
622
+ }
623
+ const now = (options.now ?? (() => new Date().toISOString()))();
624
+ const recovery: SchemaMigrationRecovery | null = existing.schemaVersion === 2 ? {
625
+ code: 'SCHEMA_V3_MIGRATED',
626
+ recoveredAt: now,
627
+ previousSchemaVersion: 2,
628
+ previousStatus: 'paused',
629
+ previousPause: existing.pause,
630
+ guards: {
631
+ stepCount: 0,
632
+ nextStage: null,
633
+ baselineEmpty: true,
634
+ receiptCount: 0,
635
+ pendingDelegation: false,
636
+ commitAuthorizationUnused: true,
637
+ completionEvidenceAbsent: true,
638
+ commitIntentAbsent: true
639
+ },
640
+ resultingStatus: 'running'
641
+ } : null;
642
+ const resumed = withUpdatedRun(existing, {
643
+ schemaVersion: 3,
644
+ status: 'running',
645
+ pause: null,
646
+ ...(recovery ? { recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery]) } : {})
647
+ }, () => now);
490
648
  saveRun(resolved.taskDir, resumed);
491
649
  return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
492
650
  }
@@ -562,7 +720,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
562
720
  };
563
721
  const recovered: OrchestrationRun = Object.freeze({
564
722
  ...existing,
565
- schemaVersion: 2,
723
+ schemaVersion: 3,
566
724
  status: resultingStatus,
567
725
  modelPolicy: policy!,
568
726
  modelPolicySource: source,
@@ -593,7 +751,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
593
751
  const now = (options.now ?? (() => new Date().toISOString()))();
594
752
  const source: ModelPolicySource = { kind: sourceKind, client: options.client, resolvedAt: now };
595
753
  const run: OrchestrationRun = {
596
- schemaVersion: 2,
754
+ schemaVersion: 3,
597
755
  taskId: resolved.taskId,
598
756
  runId: (options.id ?? randomUUID)(),
599
757
  status: 'running',
@@ -1332,7 +1490,12 @@ function statusOrchestration(taskRef: string, options: OrchestrationOptions = {}
1332
1490
 
1333
1491
  function prepareOrchestrationDelegationUnlocked(
1334
1492
  taskRef: string,
1335
- input: Readonly<{ client: AgentClientId; requestedModel?: string; requestedReasoningEffort?: string }>,
1493
+ input: Readonly<{
1494
+ client: AgentClientId;
1495
+ requestedModel?: string;
1496
+ requestedReasoningEffort?: string;
1497
+ lifecycleProvenance?: DelegationLifecycleProvenance;
1498
+ }>,
1336
1499
  options: OrchestrationOptions = {}
1337
1500
  ): OrchestrationResult {
1338
1501
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
@@ -1354,6 +1517,9 @@ function prepareOrchestrationDelegationUnlocked(
1354
1517
  }
1355
1518
  const run = readRun(resolved.taskDir);
1356
1519
  if (!run || run.status !== 'running') return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
1520
+ if (run.schemaVersion !== 3) {
1521
+ return failed('ORCHESTRATION_STATE_INVALID', 'delegation preparation requires orchestration schemaVersion 3', resolved.taskId);
1522
+ }
1357
1523
  if (!isV2Policy(run.modelPolicy)) return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
1358
1524
  if (run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
1359
1525
  const repositoryPending = matchingDelegations(() => true, options);
@@ -1370,6 +1536,9 @@ function prepareOrchestrationDelegationUnlocked(
1370
1536
  if (!validModel(input.requestedReasoningEffort)) {
1371
1537
  return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
1372
1538
  }
1539
+ if (input.client === 'codex' && !input.lifecycleProvenance) {
1540
+ return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
1541
+ }
1373
1542
  const expectedPolicy = run.modelPolicy[next.role];
1374
1543
  if (input.requestedModel !== expectedPolicy.model) {
1375
1544
  return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
@@ -1398,8 +1567,9 @@ function prepareOrchestrationDelegationUnlocked(
1398
1567
  requestedModel: input.requestedModel,
1399
1568
  requestedReasoningEffort: input.requestedReasoningEffort,
1400
1569
  workspaceSnapshotScope: 'task',
1570
+ lifecycleProvenance: input.lifecycleProvenance ?? null,
1401
1571
  beforeFingerprint
1402
- }, { id: options.id, now: options.now });
1572
+ }, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
1403
1573
  const updated = withUpdatedRun(run, {
1404
1574
  nextStage: next.stage,
1405
1575
  pendingDelegation: receipt,
@@ -1413,7 +1583,12 @@ function prepareOrchestrationDelegationUnlocked(
1413
1583
 
1414
1584
  function prepareOrchestrationDelegation(
1415
1585
  taskRef: string,
1416
- input: Readonly<{ client: AgentClientId; requestedModel?: string; requestedReasoningEffort?: string }>,
1586
+ input: Readonly<{
1587
+ client: AgentClientId;
1588
+ requestedModel?: string;
1589
+ requestedReasoningEffort?: string;
1590
+ lifecycleProvenance?: DelegationLifecycleProvenance;
1591
+ }>,
1417
1592
  options: OrchestrationOptions = {}
1418
1593
  ): OrchestrationResult {
1419
1594
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
@@ -1436,6 +1611,51 @@ function prepareOrchestrationDelegation(
1436
1611
  }
1437
1612
  }
1438
1613
 
1614
+ function dispatchOrchestrationDelegationUnlocked(
1615
+ taskRef: string,
1616
+ options: OrchestrationOptions = {}
1617
+ ): OrchestrationResult {
1618
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1619
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1620
+ const run = readRun(resolved.taskDir);
1621
+ if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1622
+ if (run.schemaVersion !== 3 || run.status !== 'running') {
1623
+ return failed('ORCHESTRATION_STATE_INVALID', 'spawn dispatch requires a running schemaVersion 3 orchestration', resolved.taskId);
1624
+ }
1625
+ const result = dispatchDelegation(run.pendingDelegation, {
1626
+ now: options.now,
1627
+ monotonicNow: options.monotonicNow
1628
+ });
1629
+ if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
1630
+ const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
1631
+ saveRun(resolved.taskDir, updated);
1632
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1633
+ }
1634
+
1635
+ function dispatchOrchestrationDelegation(
1636
+ taskRef: string,
1637
+ options: OrchestrationOptions = {}
1638
+ ): OrchestrationResult {
1639
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1640
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1641
+ try {
1642
+ return withTaskExecutionLock(
1643
+ resolved.repoRoot,
1644
+ '__repository__',
1645
+ 'task-orchestration.dispatch.repository',
1646
+ () => withTaskExecutionLock(
1647
+ resolved.repoRoot,
1648
+ resolved.taskId,
1649
+ 'task-orchestration.dispatch.task',
1650
+ () => dispatchOrchestrationDelegationUnlocked(taskRef, options)
1651
+ )
1652
+ );
1653
+ } catch (error) {
1654
+ if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
1655
+ throw error;
1656
+ }
1657
+ }
1658
+
1439
1659
  function matchingDelegations(
1440
1660
  predicate: (receipt: DelegationReceipt) => boolean,
1441
1661
  options: OrchestrationOptions
@@ -1447,7 +1667,7 @@ function matchingDelegations(
1447
1667
  .filter((entry) => entry.isDirectory())
1448
1668
  .flatMap((entry) => {
1449
1669
  const run = readRun(path.join(activeRoot, entry.name));
1450
- if (run?.status !== 'running' || !run.pendingDelegation) return [];
1670
+ if (run?.schemaVersion !== 3 || run.status !== 'running' || !run.pendingDelegation) return [];
1451
1671
  const receipt = run.pendingDelegation;
1452
1672
  return predicate(receipt)
1453
1673
  ? [{ taskId: entry.name, run }]
@@ -1469,6 +1689,33 @@ function uniqueMatchingDelegation(
1469
1689
  return matches[0]!;
1470
1690
  }
1471
1691
 
1692
+ function hasActivatableOrchestrationDelegation(
1693
+ client: AgentClientId,
1694
+ childId: string,
1695
+ options: OrchestrationOptions = {}
1696
+ ): boolean {
1697
+ return matchingDelegations(
1698
+ (receipt) => receipt.client === client && (
1699
+ receipt.status === 'prepared'
1700
+ || (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))
1701
+ ),
1702
+ options
1703
+ ).length > 0;
1704
+ }
1705
+
1706
+ function hasSealableOrchestrationDelegation(
1707
+ client: AgentClientId,
1708
+ childId: string,
1709
+ options: OrchestrationOptions = {}
1710
+ ): boolean {
1711
+ return matchingDelegations(
1712
+ (receipt) => receipt.client === client
1713
+ && receipt.childId === childId
1714
+ && ['stage-completed', 'sealed'].includes(receipt.status),
1715
+ options
1716
+ ).length > 0;
1717
+ }
1718
+
1472
1719
  function activateMatchingOrchestrationDelegation(
1473
1720
  client: AgentClientId,
1474
1721
  event: Parameters<typeof activateDelegation>[1],
@@ -1491,9 +1738,9 @@ function activateMatchingOrchestrationDelegation(
1491
1738
  if ('status' in replay) return matched;
1492
1739
  const receipt = replay.run.pendingDelegation!;
1493
1740
  const sameEvidence = receipt.parentId === event.parentId
1494
- && receipt.spawnMode === event.spawnMode
1495
- && receipt.actualModel === event.actualModel
1496
- && receipt.actualReasoningEffort === event.actualReasoningEffort
1741
+ && (receipt.spawnMode ?? null) === (event.spawnMode ?? null)
1742
+ && (receipt.actualModel ?? null) === foldBlankToNull(event.actualModel)
1743
+ && (receipt.actualReasoningEffort ?? null) === foldBlankToNull(event.actualReasoningEffort)
1497
1744
  && receipt.modelFallbackReason === (event.modelFallbackReason ?? null)
1498
1745
  && receipt.reasoningEffortFallbackReason === (event.reasoningEffortFallbackReason ?? null)
1499
1746
  && receipt.hostEvidence?.hookDefinitionHash === event.hostEvidence?.hookDefinitionHash;
@@ -1521,7 +1768,14 @@ function pauseMatchingOrchestrationDelegation(
1521
1768
 
1522
1769
  function sealMatchingOrchestrationDelegation(
1523
1770
  client: AgentClientId,
1524
- event: Readonly<{ nativeAgent: string; childId: string }>,
1771
+ event: Readonly<{
1772
+ nativeAgent: string;
1773
+ childId: string;
1774
+ actualModel?: string;
1775
+ actualReasoningEffort?: string;
1776
+ modelFallbackReason?: string;
1777
+ reasoningEffortFallbackReason?: string;
1778
+ }>,
1525
1779
  options: OrchestrationOptions = {}
1526
1780
  ): OrchestrationResult {
1527
1781
  const role = managedDelegationRole(event.nativeAgent);
@@ -1550,7 +1804,11 @@ function sealMatchingOrchestrationDelegation(
1550
1804
  childId: event.childId,
1551
1805
  exitCode: 0,
1552
1806
  afterFingerprint,
1553
- changedPaths
1807
+ changedPaths,
1808
+ actualModel: event.actualModel,
1809
+ actualReasoningEffort: event.actualReasoningEffort,
1810
+ modelFallbackReason: event.modelFallbackReason,
1811
+ reasoningEffortFallbackReason: event.reasoningEffortFallbackReason
1554
1812
  }, options);
1555
1813
  } catch (error) {
1556
1814
  return pauseOrchestration(
@@ -1602,7 +1860,7 @@ function sealMatchingOrchestrationDelegationWithHostEvidence(
1602
1860
  });
1603
1861
  const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
1604
1862
  const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
1605
- const validated = sealDelegation(receipt, baseEvent, { now: options.now });
1863
+ const validated = sealDelegation(receipt, baseEvent, { now: options.now, requireHostEvidence: false });
1606
1864
  if (!validated.ok) {
1607
1865
  return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
1608
1866
  }
@@ -1662,7 +1920,14 @@ function activateOrchestrationDelegation(
1662
1920
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1663
1921
  const run = readRun(resolved.taskDir);
1664
1922
  if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1665
- const result = activateDelegation(run.pendingDelegation, event, { now: options.now });
1923
+ if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'active delegation requires schemaVersion 3', resolved.taskId);
1924
+ if (run.status !== 'running') {
1925
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1926
+ }
1927
+ const result = activateDelegation(run.pendingDelegation, event, {
1928
+ now: options.now,
1929
+ monotonicNow: options.monotonicNow
1930
+ });
1666
1931
  if (!result.ok) {
1667
1932
  if (result.code === 'DELEGATION_IGNORED') return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1668
1933
  return pauseOrchestration(taskRef, result.code, result.message, true, options);
@@ -1672,6 +1937,153 @@ function activateOrchestrationDelegation(
1672
1937
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1673
1938
  }
1674
1939
 
1940
+ async function awaitOrchestrationDelegationActivation(
1941
+ taskRef: string,
1942
+ event: OrchestrationStageIdentity,
1943
+ options: OrchestrationOptions = {}
1944
+ ): Promise<OrchestrationResult> {
1945
+ const sleep = options.sleep
1946
+ ?? ((milliseconds: number) => new Promise<void>((resolve) => setTimeout(resolve, milliseconds)));
1947
+ while (true) {
1948
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1949
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1950
+ const run = readRun(resolved.taskDir);
1951
+ if (!run?.pendingDelegation) {
1952
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1953
+ }
1954
+ if (run.schemaVersion !== 3) {
1955
+ return failed('ORCHESTRATION_STATE_INVALID', 'activation barrier requires schemaVersion 3', resolved.taskId);
1956
+ }
1957
+ const receipt = run.pendingDelegation;
1958
+ const matches = receipt.stage === event.stage
1959
+ && receipt.round === event.round
1960
+ && receipt.artifact === event.artifact
1961
+ && receipt.role === event.role;
1962
+ if (!matches) {
1963
+ return pauseOrchestration(
1964
+ taskRef,
1965
+ 'ORCHESTRATION_PROVENANCE_MISMATCH',
1966
+ 'activation barrier identity does not match the pending delegation',
1967
+ true,
1968
+ options
1969
+ );
1970
+ }
1971
+ if (receipt.status === 'activated' || receipt.status === 'stage-completed') {
1972
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1973
+ }
1974
+ if (run.status !== 'running') {
1975
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1976
+ }
1977
+ if (receipt.status !== 'prepared') {
1978
+ return pauseOrchestration(
1979
+ taskRef,
1980
+ 'ORCHESTRATION_ACTIVATION_STATE_INVALID',
1981
+ `activation barrier found receipt status '${receipt.status}'`,
1982
+ true,
1983
+ options
1984
+ );
1985
+ }
1986
+ if (
1987
+ receipt.activationDeadlineAt == null
1988
+ || !Number.isFinite(Date.parse(receipt.activationDeadlineAt))
1989
+ ) {
1990
+ return pauseOrchestration(
1991
+ taskRef,
1992
+ 'ORCHESTRATION_DELEGATION_NOT_DISPATCHED',
1993
+ 'activation barrier was entered before the native spawn dispatch boundary',
1994
+ true,
1995
+ options
1996
+ );
1997
+ }
1998
+ if (Date.parse((options.now ?? (() => new Date().toISOString()))()) >= Date.parse(receipt.activationDeadlineAt)) {
1999
+ return pauseOrchestration(
2000
+ taskRef,
2001
+ 'ORCHESTRATION_ACTIVATION_TIMEOUT',
2002
+ 'fresh child did not receive trusted activation evidence before the deadline',
2003
+ true,
2004
+ options
2005
+ );
2006
+ }
2007
+ await sleep(100);
2008
+ }
2009
+ }
2010
+
2011
+ function recoverPreparedOrchestrationDelegation(
2012
+ taskRef: string,
2013
+ options: OrchestrationOptions = {}
2014
+ ): OrchestrationResult {
2015
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
2016
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
2017
+ try {
2018
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.recover-prepared', () => {
2019
+ const run = readRun(resolved.taskDir);
2020
+ const receipt = run?.pendingDelegation;
2021
+ if (!run || !receipt) {
2022
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
2023
+ }
2024
+ if (receipt.status !== 'prepared') {
2025
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'only a never-activated prepared receipt can recover', resolved.taskId);
2026
+ }
2027
+ const wallNow = Date.parse((options.now ?? (() => new Date().toISOString()))());
2028
+ if (
2029
+ receipt.activationDeadlineAt != null
2030
+ && Number.isFinite(Date.parse(receipt.activationDeadlineAt))
2031
+ && wallNow <= Date.parse(receipt.activationDeadlineAt)
2032
+ ) {
2033
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_EARLY', 'prepared receipt activation deadline has not elapsed', resolved.taskId);
2034
+ }
2035
+ const activeLifecycleEvidence = options.hasActiveLifecycleEvidence?.(receipt)
2036
+ ?? (
2037
+ receipt.client === 'codex'
2038
+ && receipt.lifecycleProvenance
2039
+ && hasActiveCodexLifecycleEvidence(
2040
+ resolveAgentRuntimeStoreRoot({ repoRoot: resolved.repoRoot, store: 'lifecycle' }),
2041
+ {
2042
+ nativeAgent: `agent-infra-lifecycle-${receipt.role}`,
2043
+ hookDefinitionHash: receipt.lifecycleProvenance.hookDefinitionHash
2044
+ }
2045
+ )
2046
+ );
2047
+ if (activeLifecycleEvidence) {
2048
+ return failed(
2049
+ 'ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE',
2050
+ 'active Codex lifecycle evidence blocks prepared receipt recovery',
2051
+ resolved.taskId
2052
+ );
2053
+ }
2054
+ if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
2055
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'active commit intent blocks prepared recovery', resolved.taskId);
2056
+ }
2057
+ const current = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
2058
+ gitRoot: gitRootFor(resolved.repoRoot, options),
2059
+ stateRoot: resolved.repoRoot,
2060
+ taskId: receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null
2061
+ });
2062
+ if (current !== receipt.beforeFingerprint) {
2063
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_DRIFT', 'workspace changed after delegation preparation', resolved.taskId);
2064
+ }
2065
+ const aborted = abortPreparedDelegation(receipt);
2066
+ if (!aborted.ok) return failed(aborted.code, aborted.message, resolved.taskId);
2067
+ const updated = withUpdatedRun(run, {
2068
+ status: 'running',
2069
+ pause: null,
2070
+ nextStage: null,
2071
+ pendingDelegation: null,
2072
+ receipts: Object.freeze([...run.receipts, aborted.receipt])
2073
+ }, options.now);
2074
+ saveRun(resolved.taskDir, updated);
2075
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
2076
+ });
2077
+ } catch (error) {
2078
+ if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
2079
+ return failed(
2080
+ 'ORCHESTRATION_PREPARED_RECOVERY_FAILED',
2081
+ error instanceof Error ? error.message : String(error),
2082
+ resolved.taskId
2083
+ );
2084
+ }
2085
+ }
2086
+
1675
2087
  function completeOrchestrationStage(
1676
2088
  taskRef: string,
1677
2089
  event: Parameters<typeof completeDelegationStage>[1],
@@ -1681,6 +2093,7 @@ function completeOrchestrationStage(
1681
2093
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1682
2094
  const run = readRun(resolved.taskDir);
1683
2095
  if (!run) return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
2096
+ if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'stage completion requires schemaVersion 3', resolved.taskId);
1684
2097
  if (!run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
1685
2098
  const result = completeDelegationStage(run.pendingDelegation, event);
1686
2099
  if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
@@ -1703,6 +2116,7 @@ function inspectOrchestrationStage(
1703
2116
  return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
1704
2117
  }
1705
2118
  if (!run) return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
2119
+ if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'stage inspection requires schemaVersion 3', resolved.taskId);
1706
2120
  const receipt = run.pendingDelegation;
1707
2121
  if (
1708
2122
  !receipt
@@ -1761,6 +2175,7 @@ function sealOrchestrationDelegation(
1761
2175
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1762
2176
  const run = readRun(resolved.taskDir);
1763
2177
  if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
2178
+ if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'delegation sealing requires schemaVersion 3', resolved.taskId);
1764
2179
  const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
1765
2180
  if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
1766
2181
  const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
@@ -1773,6 +2188,7 @@ function advanceOrchestration(taskRef: string, options: OrchestrationOptions = {
1773
2188
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1774
2189
  const run = readRun(resolved.taskDir);
1775
2190
  if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
2191
+ if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'orchestration advance requires schemaVersion 3', resolved.taskId);
1776
2192
  const result = consumeDelegation(run.pendingDelegation, { now: options.now });
1777
2193
  if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
1778
2194
  const completed = result.receipt.stage === 'commit';
@@ -1809,6 +2225,7 @@ function pauseOrchestration(
1809
2225
  export {
1810
2226
  activateMatchingOrchestrationDelegation,
1811
2227
  activateOrchestrationDelegation,
2228
+ awaitOrchestrationDelegationActivation,
1812
2229
  advanceOrchestration,
1813
2230
  abortCommitIntent,
1814
2231
  beginCommitIntent,
@@ -1817,7 +2234,10 @@ export {
1817
2234
  commitOrchestrationStageCompletion,
1818
2235
  completeCommitIntent,
1819
2236
  completeOrchestrationStage,
2237
+ dispatchOrchestrationDelegation,
1820
2238
  inspectOrchestrationStage,
2239
+ hasActivatableOrchestrationDelegation,
2240
+ hasSealableOrchestrationDelegation,
1821
2241
  orchestrationPath,
1822
2242
  pauseMatchingOrchestrationDelegation,
1823
2243
  pauseOrchestration,
@@ -1826,6 +2246,7 @@ export {
1826
2246
  readRun,
1827
2247
  reconcileMatchingOrchestrationDelegation,
1828
2248
  recoverCommitIntent,
2249
+ recoverPreparedOrchestrationDelegation,
1829
2250
  routeOrchestration,
1830
2251
  sealMatchingOrchestrationDelegation,
1831
2252
  sealMatchingOrchestrationDelegationWithHostEvidence,