@fitlab-ai/agent-infra 0.9.5 → 0.9.7

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 (172) hide show
  1. package/dist/bin/internal-cli.js +8 -3
  2. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +171 -20
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -2
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +68 -8
  8. package/dist/lib/agent-clients/adapters/codex.js +1 -1
  9. package/dist/lib/defaults.json +10 -2
  10. package/dist/lib/git/workflow.js +30 -4
  11. package/dist/lib/internal/codex-lifecycle.js +169 -3
  12. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  13. package/dist/lib/internal/release-workflow.js +108 -11
  14. package/dist/lib/internal/sandbox-control.js +43 -6
  15. package/dist/lib/internal/task-create.js +12 -4
  16. package/dist/lib/internal/task-orchestration.js +40 -7
  17. package/dist/lib/platform/context.js +6 -4
  18. package/dist/lib/platform/github-client.js +55 -2
  19. package/dist/lib/platform/issue-comments.js +2 -1
  20. package/dist/lib/process-data/checkpoint.js +319 -0
  21. package/dist/lib/process-data/index.js +128 -60
  22. package/dist/lib/process-data/materialize.js +84 -0
  23. package/dist/lib/process-data/normalize.js +34 -1
  24. package/dist/lib/process-data/reconcile.js +55 -1
  25. package/dist/lib/process-data/sources.js +422 -1
  26. package/dist/lib/process-data/store.js +341 -11
  27. package/dist/lib/release/workflow.js +29 -21
  28. package/dist/lib/sandbox/commands/create.js +111 -60
  29. package/dist/lib/sandbox/commands/prune.js +40 -16
  30. package/dist/lib/sandbox/commands/rm.js +372 -94
  31. package/dist/lib/sandbox/commands/start.js +4 -3
  32. package/dist/lib/sandbox/control/client.js +138 -22
  33. package/dist/lib/sandbox/control/container-identity.js +64 -0
  34. package/dist/lib/sandbox/control/executor.js +142 -0
  35. package/dist/lib/sandbox/control/lifecycle.js +557 -0
  36. package/dist/lib/sandbox/control/protocol.js +69 -26
  37. package/dist/lib/sandbox/control/server.js +395 -99
  38. package/dist/lib/sandbox/control/state.js +191 -0
  39. package/dist/lib/sandbox/control/timing.js +8 -0
  40. package/dist/lib/sandbox/index.js +2 -2
  41. package/dist/lib/sandbox/managed-fs.js +14 -2
  42. package/dist/lib/sandbox/mounts.js +22 -19
  43. package/dist/lib/sandbox/recovery.js +284 -50
  44. package/dist/lib/sandbox/workspace-view.js +90 -10
  45. package/dist/lib/sandbox/worktree-safety.js +196 -0
  46. package/dist/lib/server/daemon.js +1 -1
  47. package/dist/lib/server/process-control.js +17 -1
  48. package/dist/lib/server/process-state.js +57 -20
  49. package/dist/lib/task/artifact-lifecycle.js +87 -15
  50. package/dist/lib/task/artifact-receipts.js +128 -0
  51. package/dist/lib/task/codex-orchestration.js +236 -0
  52. package/dist/lib/task/commands/status.js +3 -0
  53. package/dist/lib/task/commands/validate.js +289 -0
  54. package/dist/lib/task/commit-finalization.js +36 -9
  55. package/dist/lib/task/create.js +10 -2
  56. package/dist/lib/task/delegation-receipts.js +109 -2
  57. package/dist/lib/task/events.js +149 -6
  58. package/dist/lib/task/finalization-artifacts.js +2 -1
  59. package/dist/lib/task/implementation-inputs.js +1 -7
  60. package/dist/lib/task/index.js +9 -0
  61. package/dist/lib/task/ledger.js +13 -1
  62. package/dist/lib/task/orchestration.js +365 -15
  63. package/dist/lib/task/sections.js +5 -1
  64. package/dist/lib/task/task-execution-lock.js +5 -4
  65. package/dist/lib/task/verification-engine.js +78 -14
  66. package/dist/lib/task/verification.js +1 -0
  67. package/dist/lifecycle-build-manifest.json +6 -0
  68. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +202 -18
  69. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  70. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
  71. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -3
  72. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  73. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
  74. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +83 -6
  75. package/lib/agent-clients/adapters/codex.ts +1 -1
  76. package/lib/defaults.json +10 -2
  77. package/lib/git/workflow.ts +25 -3
  78. package/lib/internal/codex-lifecycle.ts +175 -4
  79. package/lib/internal/codex-sandbox-controller.ts +109 -0
  80. package/lib/internal/release-workflow.ts +107 -11
  81. package/lib/internal/sandbox-control.ts +38 -6
  82. package/lib/internal/task-create.ts +16 -4
  83. package/lib/internal/task-orchestration.ts +34 -7
  84. package/lib/platform/context.ts +8 -5
  85. package/lib/platform/github-client.ts +61 -3
  86. package/lib/platform/issue-comments.ts +2 -1
  87. package/lib/process-data/checkpoint.ts +335 -0
  88. package/lib/process-data/index.ts +112 -60
  89. package/lib/process-data/materialize.ts +83 -0
  90. package/lib/process-data/normalize.ts +29 -1
  91. package/lib/process-data/reconcile.ts +64 -2
  92. package/lib/process-data/sources.ts +449 -2
  93. package/lib/process-data/store.ts +304 -11
  94. package/lib/process-data/types.ts +124 -2
  95. package/lib/release/workflow.ts +69 -20
  96. package/lib/sandbox/commands/create.ts +70 -25
  97. package/lib/sandbox/commands/prune.ts +53 -15
  98. package/lib/sandbox/commands/rm.ts +423 -108
  99. package/lib/sandbox/commands/start.ts +4 -3
  100. package/lib/sandbox/control/client.ts +152 -28
  101. package/lib/sandbox/control/container-identity.ts +93 -0
  102. package/lib/sandbox/control/executor.ts +167 -0
  103. package/lib/sandbox/control/lifecycle.ts +573 -0
  104. package/lib/sandbox/control/protocol.ts +121 -68
  105. package/lib/sandbox/control/server.ts +398 -109
  106. package/lib/sandbox/control/state.ts +213 -0
  107. package/lib/sandbox/control/timing.ts +12 -0
  108. package/lib/sandbox/index.ts +2 -2
  109. package/lib/sandbox/managed-fs.ts +17 -2
  110. package/lib/sandbox/mounts.ts +16 -8
  111. package/lib/sandbox/recovery.ts +335 -65
  112. package/lib/sandbox/workspace-view.ts +102 -11
  113. package/lib/sandbox/worktree-safety.ts +234 -0
  114. package/lib/server/daemon.ts +1 -1
  115. package/lib/server/process-control.ts +24 -3
  116. package/lib/server/process-state.ts +82 -27
  117. package/lib/task/artifact-lifecycle.ts +78 -14
  118. package/lib/task/artifact-receipts.ts +161 -0
  119. package/lib/task/codex-orchestration.ts +319 -0
  120. package/lib/task/commands/status.ts +2 -0
  121. package/lib/task/commands/validate.ts +282 -0
  122. package/lib/task/commit-finalization.ts +52 -9
  123. package/lib/task/create.ts +11 -2
  124. package/lib/task/delegation-receipts.ts +208 -7
  125. package/lib/task/events.ts +131 -5
  126. package/lib/task/finalization-artifacts.ts +2 -1
  127. package/lib/task/implementation-inputs.ts +1 -5
  128. package/lib/task/index.ts +8 -0
  129. package/lib/task/ledger.ts +16 -1
  130. package/lib/task/orchestration.ts +552 -18
  131. package/lib/task/sections.ts +6 -2
  132. package/lib/task/task-execution-lock.ts +11 -9
  133. package/lib/task/verification-engine.ts +93 -24
  134. package/lib/task/verification.ts +2 -1
  135. package/package.json +1 -1
  136. package/templates/.agents/README.en.md +7 -0
  137. package/templates/.agents/README.zh-CN.md +5 -0
  138. package/templates/.agents/hooks/lifecycle-delegation.js +72 -12
  139. package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
  140. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
  141. package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
  142. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
  143. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  144. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  145. package/templates/.agents/rules/review-handshake.en.md +3 -1
  146. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  147. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  148. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  149. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  150. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  151. package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
  152. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
  153. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
  154. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
  155. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +25 -0
  156. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +25 -0
  157. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  158. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  159. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +69 -0
  160. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +69 -0
  161. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  162. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  163. package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -3
  164. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -3
  165. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  166. package/templates/.claude/commands/run-manual-validation.en.md +9 -0
  167. package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
  168. package/templates/.codex/hooks.json +8 -8
  169. package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
  170. package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
  171. package/templates/.agents/workspace/README.en.md +0 -26
  172. package/templates/.agents/workspace/README.zh-CN.md +0 -26
@@ -10,13 +10,20 @@ 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,
15
17
  managedDelegationRole,
16
18
  prepareDelegation,
17
19
  sealDelegation
18
20
  } from './delegation-receipts.ts';
19
- import type { DelegationReceipt, DelegationRole, DelegationStage } from './delegation-receipts.ts';
21
+ import type {
22
+ DelegationLifecycleProvenance,
23
+ DelegationReceipt,
24
+ DelegationRole,
25
+ DelegationStage
26
+ } from './delegation-receipts.ts';
20
27
  import { normalizeAgentClients } from '../agent-clients/config.ts';
21
28
  import { inspectPlatformPullRequest } from '../platform/pull-requests.ts';
22
29
  import {
@@ -39,6 +46,7 @@ import type { RepositorySnapshot } from './workspace-snapshot.ts';
39
46
  import type { WorkspaceSnapshotContext } from './workspace-snapshot.ts';
40
47
  import { assertGitRepositoryBinding } from '../git/worktree-identity.ts';
41
48
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
49
+ import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.ts';
42
50
  import {
43
51
  CommitIntentError,
44
52
  commitIntentPath,
@@ -74,7 +82,7 @@ type ModelPolicySource = Readonly<{
74
82
  client: AgentClientId;
75
83
  resolvedAt: string;
76
84
  }>;
77
- type OrchestrationRecovery = Readonly<{
85
+ type ModelPolicyRecovery = Readonly<{
78
86
  code: 'MODEL_POLICY_SUPPLEMENTED';
79
87
  recoveredAt: string;
80
88
  previousSchemaVersion: 1;
@@ -85,6 +93,44 @@ type OrchestrationRecovery = Readonly<{
85
93
  pendingDelegation: false;
86
94
  resultingStatus: OrchestrationStatus;
87
95
  }>;
96
+ type ClientCapabilityRecovery = Readonly<{
97
+ code: 'CLIENT_CAPABILITY_ENABLED';
98
+ recoveredAt: string;
99
+ previousSchemaVersion: 2;
100
+ previousStatus: 'paused';
101
+ previousPause: Readonly<{ code: 'ORCHESTRATION_CLIENT_UNSUPPORTED'; message: string; recoverable: boolean }>;
102
+ client: 'codex';
103
+ guards: Readonly<{
104
+ stepCount: 0;
105
+ nextStage: null;
106
+ baselineEmpty: true;
107
+ receiptCount: 0;
108
+ pendingDelegation: false;
109
+ commitAuthorizationUnused: true;
110
+ completionEvidenceAbsent: true;
111
+ commitIntentAbsent: true;
112
+ }>;
113
+ resultingStatus: 'running';
114
+ }>;
115
+ type SchemaMigrationRecovery = Readonly<{
116
+ code: 'SCHEMA_V3_MIGRATED';
117
+ recoveredAt: string;
118
+ previousSchemaVersion: 2;
119
+ previousStatus: 'paused';
120
+ previousPause: Readonly<{ code: string; message: string; recoverable: boolean }>;
121
+ guards: Readonly<{
122
+ stepCount: 0;
123
+ nextStage: null;
124
+ baselineEmpty: true;
125
+ receiptCount: 0;
126
+ pendingDelegation: false;
127
+ commitAuthorizationUnused: true;
128
+ completionEvidenceAbsent: true;
129
+ commitIntentAbsent: true;
130
+ }>;
131
+ resultingStatus: 'running';
132
+ }>;
133
+ type OrchestrationRecovery = ModelPolicyRecovery | ClientCapabilityRecovery | SchemaMigrationRecovery;
88
134
  type CleanCompletionEvidence = Readonly<{
89
135
  kind: 'reviewed-head-clean';
90
136
  observedAt: string;
@@ -96,7 +142,7 @@ type CleanCompletionEvidence = Readonly<{
96
142
  prHead: string;
97
143
  }>;
98
144
  type OrchestrationRun = Readonly<{
99
- schemaVersion: 1 | 2;
145
+ schemaVersion: 1 | 2 | 3;
100
146
  taskId: string;
101
147
  runId: string;
102
148
  status: OrchestrationStatus;
@@ -173,6 +219,9 @@ type OrchestrationOptions = {
173
219
  diffWorkspace?: (repoRoot: string, before: string, after: string) => string[];
174
220
  supportsLifecycleDelegation?: (client: AgentClientId) => boolean;
175
221
  token?: () => string;
222
+ monotonicNow?: () => number;
223
+ sleep?: (milliseconds: number) => Promise<void>;
224
+ hasActiveLifecycleEvidence?: (receipt: DelegationReceipt) => boolean;
176
225
  };
177
226
 
178
227
  type CommitIntentResult = Readonly<{
@@ -218,7 +267,7 @@ function readRun(taskDir: string): OrchestrationRun | null {
218
267
  const file = orchestrationPath(taskDir);
219
268
  if (!fs.existsSync(file)) return null;
220
269
  const run = JSON.parse(fs.readFileSync(file, 'utf8')) as OrchestrationRun;
221
- if (run.schemaVersion !== 2 || !isV2Policy(run.modelPolicy)) return run;
270
+ if (![2, 3].includes(run.schemaVersion) || !isV2Policy(run.modelPolicy)) return run;
222
271
  return {
223
272
  ...run,
224
273
  modelPolicy: {
@@ -309,7 +358,7 @@ function commitFinalizationView(inspection: CommitFinalizationInspection): NonNu
309
358
 
310
359
  function validCommitRun(run: OrchestrationRun | null, taskId: string): run is OrchestrationRun {
311
360
  return run !== null
312
- && [1, 2].includes(run.schemaVersion)
361
+ && [1, 2, 3].includes(run.schemaVersion)
313
362
  && run.taskId === taskId
314
363
  && ['running', 'paused', 'completed'].includes(run.status)
315
364
  && Array.isArray(run.receipts)
@@ -369,6 +418,38 @@ function resolveProjectPolicy(repoRoot: string, client: AgentClientId): Orchestr
369
418
  return normalizeAgentClients(raw).state[client].orchestration;
370
419
  }
371
420
 
421
+ function canRecoverCodexUnsupportedPause(run: OrchestrationRun, taskDir: string): boolean {
422
+ return run.schemaVersion === 2
423
+ && run.status === 'paused'
424
+ && run.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
425
+ && run.modelPolicySource?.client === 'codex'
426
+ && run.stepCount === 0
427
+ && run.nextStage === null
428
+ && run.baseline === ''
429
+ && run.pendingDelegation === null
430
+ && run.receipts.length === 0
431
+ && run.commitAuthorization?.issuedAt === null
432
+ && run.commitAuthorization?.consumedAt === null
433
+ && run.completionEvidence == null
434
+ && !fs.existsSync(commitIntentPath(taskDir))
435
+ && (run.recoveryHistory ?? []).every((entry) => entry.code === 'MODEL_POLICY_SUPPLEMENTED');
436
+ }
437
+
438
+ function canMigrateRecoverableV2Pause(run: OrchestrationRun, taskDir: string): boolean {
439
+ return run.schemaVersion === 2
440
+ && run.status === 'paused'
441
+ && run.pause?.recoverable === true
442
+ && run.stepCount === 0
443
+ && run.nextStage === null
444
+ && run.baseline === ''
445
+ && run.pendingDelegation === null
446
+ && run.receipts.length === 0
447
+ && run.commitAuthorization?.issuedAt === null
448
+ && run.commitAuthorization?.consumedAt === null
449
+ && run.completionEvidence == null
450
+ && !fs.existsSync(commitIntentPath(taskDir));
451
+ }
452
+
372
453
  function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
373
454
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
374
455
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
@@ -383,7 +464,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
383
464
  }
384
465
  if (existing) {
385
466
  if (
386
- ![1, 2].includes(existing.schemaVersion)
467
+ ![1, 2, 3].includes(existing.schemaVersion)
387
468
  || !Array.isArray(existing.receipts)
388
469
  || !Object.prototype.hasOwnProperty.call(existing, 'pendingDelegation')
389
470
  ) {
@@ -392,7 +473,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
392
473
  if (existing.status === 'completed' && existing.schemaVersion === 1) {
393
474
  return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
394
475
  }
395
- if (existing.schemaVersion === 2) {
476
+ if (existing.schemaVersion === 2 || existing.schemaVersion === 3) {
396
477
  const policyError = validateModelPolicy(isV2Policy(existing.modelPolicy) ? existing.modelPolicy : undefined);
397
478
  if (
398
479
  policyError
@@ -400,11 +481,18 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
400
481
  || !isAgentClientId(existing.modelPolicySource.client)
401
482
  || !Array.isArray(existing.recoveryHistory)
402
483
  ) {
403
- return failed('ORCHESTRATION_STATE_INVALID', 'schemaVersion 2 orchestration state is incomplete', resolved.taskId);
484
+ return failed('ORCHESTRATION_STATE_INVALID', `schemaVersion ${existing.schemaVersion} orchestration state is incomplete`, resolved.taskId);
404
485
  }
405
486
  if (existing.status === 'completed') {
406
487
  return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
407
488
  }
489
+ if (existing.schemaVersion === 2 && existing.status === 'running') {
490
+ return failed(
491
+ 'ORCHESTRATION_STATE_INVALID',
492
+ 'active schemaVersion 2 runs cannot be advanced by lifecycle protocol v3',
493
+ resolved.taskId
494
+ );
495
+ }
408
496
  if (existing.modelPolicySource.client !== options.client) {
409
497
  return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
410
498
  }
@@ -415,8 +503,77 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
415
503
  return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
416
504
  }
417
505
  }
506
+ if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
507
+ if (!canRecoverCodexUnsupportedPause(existing, resolved.taskDir)) {
508
+ return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
509
+ }
510
+ const now = (options.now ?? (() => new Date().toISOString()))();
511
+ const previousPause = existing.pause as ClientCapabilityRecovery['previousPause'];
512
+ const recovery: ClientCapabilityRecovery = {
513
+ code: 'CLIENT_CAPABILITY_ENABLED',
514
+ recoveredAt: now,
515
+ previousSchemaVersion: 2,
516
+ previousStatus: 'paused',
517
+ previousPause,
518
+ client: 'codex',
519
+ guards: {
520
+ stepCount: 0,
521
+ nextStage: null,
522
+ baselineEmpty: true,
523
+ receiptCount: 0,
524
+ pendingDelegation: false,
525
+ commitAuthorizationUnused: true,
526
+ completionEvidenceAbsent: true,
527
+ commitIntentAbsent: true
528
+ },
529
+ resultingStatus: 'running'
530
+ };
531
+ const resumed = withUpdatedRun(existing, {
532
+ schemaVersion: 3,
533
+ status: 'running',
534
+ pause: null,
535
+ recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
536
+ }, () => now);
537
+ saveRun(resolved.taskDir, resumed);
538
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
539
+ }
418
540
  if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
419
- const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
541
+ if (existing.schemaVersion === 2 && !canMigrateRecoverableV2Pause(existing, resolved.taskDir)) {
542
+ const paused = withUpdatedRun(existing, {
543
+ pause: {
544
+ code: 'ORCHESTRATION_SCHEMA_MIGRATION_REQUIRED',
545
+ message: 'schemaVersion 2 recoverable pause contains execution evidence and cannot migrate automatically',
546
+ recoverable: false
547
+ }
548
+ });
549
+ saveRun(resolved.taskDir, paused);
550
+ return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
551
+ }
552
+ const now = (options.now ?? (() => new Date().toISOString()))();
553
+ const recovery: SchemaMigrationRecovery | null = existing.schemaVersion === 2 ? {
554
+ code: 'SCHEMA_V3_MIGRATED',
555
+ recoveredAt: now,
556
+ previousSchemaVersion: 2,
557
+ previousStatus: 'paused',
558
+ previousPause: existing.pause,
559
+ guards: {
560
+ stepCount: 0,
561
+ nextStage: null,
562
+ baselineEmpty: true,
563
+ receiptCount: 0,
564
+ pendingDelegation: false,
565
+ commitAuthorizationUnused: true,
566
+ completionEvidenceAbsent: true,
567
+ commitIntentAbsent: true
568
+ },
569
+ resultingStatus: 'running'
570
+ } : null;
571
+ const resumed = withUpdatedRun(existing, {
572
+ schemaVersion: 3,
573
+ status: 'running',
574
+ pause: null,
575
+ ...(recovery ? { recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery]) } : {})
576
+ }, () => now);
420
577
  saveRun(resolved.taskDir, resumed);
421
578
  return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
422
579
  }
@@ -492,7 +649,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
492
649
  };
493
650
  const recovered: OrchestrationRun = Object.freeze({
494
651
  ...existing,
495
- schemaVersion: 2,
652
+ schemaVersion: 3,
496
653
  status: resultingStatus,
497
654
  modelPolicy: policy!,
498
655
  modelPolicySource: source,
@@ -523,7 +680,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
523
680
  const now = (options.now ?? (() => new Date().toISOString()))();
524
681
  const source: ModelPolicySource = { kind: sourceKind, client: options.client, resolvedAt: now };
525
682
  const run: OrchestrationRun = {
526
- schemaVersion: 2,
683
+ schemaVersion: 3,
527
684
  taskId: resolved.taskId,
528
685
  runId: (options.id ?? randomUUID)(),
529
686
  status: 'running',
@@ -1262,7 +1419,12 @@ function statusOrchestration(taskRef: string, options: OrchestrationOptions = {}
1262
1419
 
1263
1420
  function prepareOrchestrationDelegationUnlocked(
1264
1421
  taskRef: string,
1265
- input: Readonly<{ client: AgentClientId; requestedModel?: string; requestedReasoningEffort?: string }>,
1422
+ input: Readonly<{
1423
+ client: AgentClientId;
1424
+ requestedModel?: string;
1425
+ requestedReasoningEffort?: string;
1426
+ lifecycleProvenance?: DelegationLifecycleProvenance;
1427
+ }>,
1266
1428
  options: OrchestrationOptions = {}
1267
1429
  ): OrchestrationResult {
1268
1430
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
@@ -1284,6 +1446,9 @@ function prepareOrchestrationDelegationUnlocked(
1284
1446
  }
1285
1447
  const run = readRun(resolved.taskDir);
1286
1448
  if (!run || run.status !== 'running') return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
1449
+ if (run.schemaVersion !== 3) {
1450
+ return failed('ORCHESTRATION_STATE_INVALID', 'delegation preparation requires orchestration schemaVersion 3', resolved.taskId);
1451
+ }
1287
1452
  if (!isV2Policy(run.modelPolicy)) return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
1288
1453
  if (run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
1289
1454
  const repositoryPending = matchingDelegations(() => true, options);
@@ -1300,6 +1465,9 @@ function prepareOrchestrationDelegationUnlocked(
1300
1465
  if (!validModel(input.requestedReasoningEffort)) {
1301
1466
  return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
1302
1467
  }
1468
+ if (input.client === 'codex' && !input.lifecycleProvenance) {
1469
+ return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
1470
+ }
1303
1471
  const expectedPolicy = run.modelPolicy[next.role];
1304
1472
  if (input.requestedModel !== expectedPolicy.model) {
1305
1473
  return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
@@ -1328,8 +1496,9 @@ function prepareOrchestrationDelegationUnlocked(
1328
1496
  requestedModel: input.requestedModel,
1329
1497
  requestedReasoningEffort: input.requestedReasoningEffort,
1330
1498
  workspaceSnapshotScope: 'task',
1499
+ lifecycleProvenance: input.lifecycleProvenance ?? null,
1331
1500
  beforeFingerprint
1332
- }, { id: options.id, now: options.now });
1501
+ }, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
1333
1502
  const updated = withUpdatedRun(run, {
1334
1503
  nextStage: next.stage,
1335
1504
  pendingDelegation: receipt,
@@ -1343,7 +1512,12 @@ function prepareOrchestrationDelegationUnlocked(
1343
1512
 
1344
1513
  function prepareOrchestrationDelegation(
1345
1514
  taskRef: string,
1346
- input: Readonly<{ client: AgentClientId; requestedModel?: string; requestedReasoningEffort?: string }>,
1515
+ input: Readonly<{
1516
+ client: AgentClientId;
1517
+ requestedModel?: string;
1518
+ requestedReasoningEffort?: string;
1519
+ lifecycleProvenance?: DelegationLifecycleProvenance;
1520
+ }>,
1347
1521
  options: OrchestrationOptions = {}
1348
1522
  ): OrchestrationResult {
1349
1523
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
@@ -1366,6 +1540,51 @@ function prepareOrchestrationDelegation(
1366
1540
  }
1367
1541
  }
1368
1542
 
1543
+ function dispatchOrchestrationDelegationUnlocked(
1544
+ taskRef: string,
1545
+ options: OrchestrationOptions = {}
1546
+ ): OrchestrationResult {
1547
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1548
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1549
+ const run = readRun(resolved.taskDir);
1550
+ if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1551
+ if (run.schemaVersion !== 3 || run.status !== 'running') {
1552
+ return failed('ORCHESTRATION_STATE_INVALID', 'spawn dispatch requires a running schemaVersion 3 orchestration', resolved.taskId);
1553
+ }
1554
+ const result = dispatchDelegation(run.pendingDelegation, {
1555
+ now: options.now,
1556
+ monotonicNow: options.monotonicNow
1557
+ });
1558
+ if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
1559
+ const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
1560
+ saveRun(resolved.taskDir, updated);
1561
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1562
+ }
1563
+
1564
+ function dispatchOrchestrationDelegation(
1565
+ taskRef: string,
1566
+ options: OrchestrationOptions = {}
1567
+ ): OrchestrationResult {
1568
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1569
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1570
+ try {
1571
+ return withTaskExecutionLock(
1572
+ resolved.repoRoot,
1573
+ '__repository__',
1574
+ 'task-orchestration.dispatch.repository',
1575
+ () => withTaskExecutionLock(
1576
+ resolved.repoRoot,
1577
+ resolved.taskId,
1578
+ 'task-orchestration.dispatch.task',
1579
+ () => dispatchOrchestrationDelegationUnlocked(taskRef, options)
1580
+ )
1581
+ );
1582
+ } catch (error) {
1583
+ if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
1584
+ throw error;
1585
+ }
1586
+ }
1587
+
1369
1588
  function matchingDelegations(
1370
1589
  predicate: (receipt: DelegationReceipt) => boolean,
1371
1590
  options: OrchestrationOptions
@@ -1377,7 +1596,7 @@ function matchingDelegations(
1377
1596
  .filter((entry) => entry.isDirectory())
1378
1597
  .flatMap((entry) => {
1379
1598
  const run = readRun(path.join(activeRoot, entry.name));
1380
- if (!run?.pendingDelegation) return [];
1599
+ if (run?.schemaVersion !== 3 || run.status !== 'running' || !run.pendingDelegation) return [];
1381
1600
  const receipt = run.pendingDelegation;
1382
1601
  return predicate(receipt)
1383
1602
  ? [{ taskId: entry.name, run }]
@@ -1399,6 +1618,33 @@ function uniqueMatchingDelegation(
1399
1618
  return matches[0]!;
1400
1619
  }
1401
1620
 
1621
+ function hasActivatableOrchestrationDelegation(
1622
+ client: AgentClientId,
1623
+ childId: string,
1624
+ options: OrchestrationOptions = {}
1625
+ ): boolean {
1626
+ return matchingDelegations(
1627
+ (receipt) => receipt.client === client && (
1628
+ receipt.status === 'prepared'
1629
+ || (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))
1630
+ ),
1631
+ options
1632
+ ).length > 0;
1633
+ }
1634
+
1635
+ function hasSealableOrchestrationDelegation(
1636
+ client: AgentClientId,
1637
+ childId: string,
1638
+ options: OrchestrationOptions = {}
1639
+ ): boolean {
1640
+ return matchingDelegations(
1641
+ (receipt) => receipt.client === client
1642
+ && receipt.childId === childId
1643
+ && ['stage-completed', 'sealed'].includes(receipt.status),
1644
+ options
1645
+ ).length > 0;
1646
+ }
1647
+
1402
1648
  function activateMatchingOrchestrationDelegation(
1403
1649
  client: AgentClientId,
1404
1650
  event: Parameters<typeof activateDelegation>[1],
@@ -1411,13 +1657,44 @@ function activateMatchingOrchestrationDelegation(
1411
1657
  (receipt) => receipt.status === 'prepared',
1412
1658
  options
1413
1659
  );
1414
- if ('status' in matched) return matched;
1660
+ if ('status' in matched) {
1661
+ if (matched.error?.code !== 'ORCHESTRATION_DELEGATION_MISSING') return matched;
1662
+ const replay = uniqueMatchingDelegation(
1663
+ client,
1664
+ (receipt) => receipt.childId === event.childId && ['activated', 'stage-completed'].includes(receipt.status),
1665
+ options
1666
+ );
1667
+ if ('status' in replay) return matched;
1668
+ const receipt = replay.run.pendingDelegation!;
1669
+ const sameEvidence = receipt.parentId === event.parentId
1670
+ && receipt.spawnMode === event.spawnMode
1671
+ && receipt.actualModel === event.actualModel
1672
+ && receipt.actualReasoningEffort === event.actualReasoningEffort
1673
+ && receipt.modelFallbackReason === (event.modelFallbackReason ?? null)
1674
+ && receipt.reasoningEffortFallbackReason === (event.reasoningEffortFallbackReason ?? null)
1675
+ && receipt.hostEvidence?.hookDefinitionHash === event.hostEvidence?.hookDefinitionHash;
1676
+ if (!sameEvidence) {
1677
+ return pauseOrchestration(replay.taskId, 'DELEGATION_REPLAY_CONFLICT', 'replayed native start evidence conflicts with the active receipt', true, options);
1678
+ }
1679
+ return { status: replay.run.status, changed: false, taskId: replay.taskId, run: replay.run, next: null, error: null };
1680
+ }
1415
1681
  if (matched.run.receipts.some((receipt) => receipt.childId === event.childId)) {
1416
1682
  return pauseOrchestration(matched.taskId, 'DELEGATION_IDENTITY_REUSED', 'native child identity was already used by this run', true, options);
1417
1683
  }
1418
1684
  return activateOrchestrationDelegation(matched.taskId, event, options);
1419
1685
  }
1420
1686
 
1687
+ function pauseMatchingOrchestrationDelegation(
1688
+ client: AgentClientId,
1689
+ code: string,
1690
+ message: string,
1691
+ options: OrchestrationOptions = {}
1692
+ ): OrchestrationResult {
1693
+ const matched = uniqueMatchingDelegation(client, () => true, options);
1694
+ if ('status' in matched) return matched;
1695
+ return pauseOrchestration(matched.taskId, code, message, true, options);
1696
+ }
1697
+
1421
1698
  function sealMatchingOrchestrationDelegation(
1422
1699
  client: AgentClientId,
1423
1700
  event: Readonly<{ nativeAgent: string; childId: string }>,
@@ -1462,6 +1739,96 @@ function sealMatchingOrchestrationDelegation(
1462
1739
  }
1463
1740
  }
1464
1741
 
1742
+ function sealMatchingOrchestrationDelegationWithHostEvidence(
1743
+ client: AgentClientId,
1744
+ event: Readonly<{ nativeAgent: string; childId: string }>,
1745
+ consumeEvidence: (receipt: DelegationReceipt) => Readonly<{
1746
+ stopRevision: number;
1747
+ consumer: string;
1748
+ consumedAt: string;
1749
+ }>,
1750
+ options: OrchestrationOptions = {}
1751
+ ): OrchestrationResult {
1752
+ const role = managedDelegationRole(event.nativeAgent);
1753
+ if (!role) return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
1754
+ const matched = uniqueMatchingDelegation(
1755
+ client,
1756
+ (receipt) => (
1757
+ (receipt.status === 'stage-completed' || receipt.status === 'sealed')
1758
+ && receipt.childId === event.childId
1759
+ ),
1760
+ options
1761
+ );
1762
+ if ('status' in matched) return matched;
1763
+ const receipt = matched.run.pendingDelegation!;
1764
+ if (receipt.status === 'sealed') {
1765
+ return reconcileMatchingOrchestrationDelegation(client, event.childId, options);
1766
+ }
1767
+ if (receipt.role !== role) {
1768
+ return pauseOrchestration(matched.taskId, 'DELEGATION_ROLE_MISMATCH', `managed role ${role} does not match ${receipt.role}`, true, options);
1769
+ }
1770
+ const repoRoot = options.repoRoot ?? process.cwd();
1771
+ try {
1772
+ const gitRoot = gitRootFor(repoRoot, options);
1773
+ const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
1774
+ const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
1775
+ gitRoot,
1776
+ stateRoot: repoRoot,
1777
+ taskId: snapshotTaskId
1778
+ });
1779
+ const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
1780
+ const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
1781
+ const validated = sealDelegation(receipt, baseEvent, { now: options.now, requireHostEvidence: false });
1782
+ if (!validated.ok) {
1783
+ return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
1784
+ }
1785
+ const hostEvidence = consumeEvidence(receipt);
1786
+ const sealed = sealDelegation(receipt, { ...baseEvent, hostEvidence }, { now: options.now });
1787
+ if (!sealed.ok) return pauseOrchestration(matched.taskId, sealed.code, sealed.message, true, options);
1788
+ const resolved = resolveTaskRef(matched.taskId, { repoRoot });
1789
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1790
+ const updated = withUpdatedRun(matched.run, { pendingDelegation: sealed.receipt }, options.now);
1791
+ saveRun(resolved.taskDir, updated);
1792
+ return { status: 'running', changed: true, taskId: matched.taskId, run: updated, next: null, error: null };
1793
+ } catch (error) {
1794
+ return pauseOrchestration(
1795
+ matched.taskId,
1796
+ 'ORCHESTRATION_CODEX_EVIDENCE_FAILED',
1797
+ error instanceof Error ? error.message : String(error),
1798
+ true,
1799
+ options
1800
+ );
1801
+ }
1802
+ }
1803
+
1804
+ function reconcileMatchingOrchestrationDelegation(
1805
+ client: AgentClientId,
1806
+ childId: string,
1807
+ options: OrchestrationOptions = {}
1808
+ ): OrchestrationResult {
1809
+ const matched = uniqueMatchingDelegation(
1810
+ client,
1811
+ (receipt) => receipt.childId === childId,
1812
+ options
1813
+ );
1814
+ if ('status' in matched) return matched;
1815
+ const receipt = matched.run.pendingDelegation!;
1816
+ if (
1817
+ receipt.status !== 'sealed'
1818
+ || receipt.hostEvidence?.consumer !== receipt.id
1819
+ || receipt.hostEvidence.consumedAt === null
1820
+ ) {
1821
+ return pauseOrchestration(
1822
+ matched.taskId,
1823
+ 'ORCHESTRATION_CODEX_RECONCILIATION_FAILED',
1824
+ 'Codex native spawn did not produce one sealed receipt with consumed host evidence',
1825
+ true,
1826
+ options
1827
+ );
1828
+ }
1829
+ return { status: matched.run.status, changed: false, taskId: matched.taskId, run: matched.run, next: null, error: null };
1830
+ }
1831
+
1465
1832
  function activateOrchestrationDelegation(
1466
1833
  taskRef: string,
1467
1834
  event: Parameters<typeof activateDelegation>[1],
@@ -1471,7 +1838,14 @@ function activateOrchestrationDelegation(
1471
1838
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1472
1839
  const run = readRun(resolved.taskDir);
1473
1840
  if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1474
- const result = activateDelegation(run.pendingDelegation, event, { now: options.now });
1841
+ if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'active delegation requires schemaVersion 3', resolved.taskId);
1842
+ if (run.status !== 'running') {
1843
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1844
+ }
1845
+ const result = activateDelegation(run.pendingDelegation, event, {
1846
+ now: options.now,
1847
+ monotonicNow: options.monotonicNow
1848
+ });
1475
1849
  if (!result.ok) {
1476
1850
  if (result.code === 'DELEGATION_IGNORED') return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1477
1851
  return pauseOrchestration(taskRef, result.code, result.message, true, options);
@@ -1481,6 +1855,153 @@ function activateOrchestrationDelegation(
1481
1855
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1482
1856
  }
1483
1857
 
1858
+ async function awaitOrchestrationDelegationActivation(
1859
+ taskRef: string,
1860
+ event: OrchestrationStageIdentity,
1861
+ options: OrchestrationOptions = {}
1862
+ ): Promise<OrchestrationResult> {
1863
+ const sleep = options.sleep
1864
+ ?? ((milliseconds: number) => new Promise<void>((resolve) => setTimeout(resolve, milliseconds)));
1865
+ while (true) {
1866
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1867
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1868
+ const run = readRun(resolved.taskDir);
1869
+ if (!run?.pendingDelegation) {
1870
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1871
+ }
1872
+ if (run.schemaVersion !== 3) {
1873
+ return failed('ORCHESTRATION_STATE_INVALID', 'activation barrier requires schemaVersion 3', resolved.taskId);
1874
+ }
1875
+ const receipt = run.pendingDelegation;
1876
+ const matches = receipt.stage === event.stage
1877
+ && receipt.round === event.round
1878
+ && receipt.artifact === event.artifact
1879
+ && receipt.role === event.role;
1880
+ if (!matches) {
1881
+ return pauseOrchestration(
1882
+ taskRef,
1883
+ 'ORCHESTRATION_PROVENANCE_MISMATCH',
1884
+ 'activation barrier identity does not match the pending delegation',
1885
+ true,
1886
+ options
1887
+ );
1888
+ }
1889
+ if (receipt.status === 'activated' || receipt.status === 'stage-completed') {
1890
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1891
+ }
1892
+ if (run.status !== 'running') {
1893
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1894
+ }
1895
+ if (receipt.status !== 'prepared') {
1896
+ return pauseOrchestration(
1897
+ taskRef,
1898
+ 'ORCHESTRATION_ACTIVATION_STATE_INVALID',
1899
+ `activation barrier found receipt status '${receipt.status}'`,
1900
+ true,
1901
+ options
1902
+ );
1903
+ }
1904
+ if (
1905
+ receipt.activationDeadlineAt == null
1906
+ || !Number.isFinite(Date.parse(receipt.activationDeadlineAt))
1907
+ ) {
1908
+ return pauseOrchestration(
1909
+ taskRef,
1910
+ 'ORCHESTRATION_DELEGATION_NOT_DISPATCHED',
1911
+ 'activation barrier was entered before the native spawn dispatch boundary',
1912
+ true,
1913
+ options
1914
+ );
1915
+ }
1916
+ if (Date.parse((options.now ?? (() => new Date().toISOString()))()) >= Date.parse(receipt.activationDeadlineAt)) {
1917
+ return pauseOrchestration(
1918
+ taskRef,
1919
+ 'ORCHESTRATION_ACTIVATION_TIMEOUT',
1920
+ 'fresh child did not receive trusted activation evidence before the deadline',
1921
+ true,
1922
+ options
1923
+ );
1924
+ }
1925
+ await sleep(100);
1926
+ }
1927
+ }
1928
+
1929
+ function recoverPreparedOrchestrationDelegation(
1930
+ taskRef: string,
1931
+ options: OrchestrationOptions = {}
1932
+ ): OrchestrationResult {
1933
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1934
+ if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1935
+ try {
1936
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.recover-prepared', () => {
1937
+ const run = readRun(resolved.taskDir);
1938
+ const receipt = run?.pendingDelegation;
1939
+ if (!run || !receipt) {
1940
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1941
+ }
1942
+ if (receipt.status !== 'prepared') {
1943
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'only a never-activated prepared receipt can recover', resolved.taskId);
1944
+ }
1945
+ const wallNow = Date.parse((options.now ?? (() => new Date().toISOString()))());
1946
+ if (
1947
+ receipt.activationDeadlineAt != null
1948
+ && Number.isFinite(Date.parse(receipt.activationDeadlineAt))
1949
+ && wallNow <= Date.parse(receipt.activationDeadlineAt)
1950
+ ) {
1951
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_EARLY', 'prepared receipt activation deadline has not elapsed', resolved.taskId);
1952
+ }
1953
+ const activeLifecycleEvidence = options.hasActiveLifecycleEvidence?.(receipt)
1954
+ ?? (
1955
+ receipt.client === 'codex'
1956
+ && receipt.lifecycleProvenance
1957
+ && hasActiveCodexLifecycleEvidence(
1958
+ path.join(resolved.repoRoot, '.agents', 'workspace', '.runtime', 'codex-lifecycle'),
1959
+ {
1960
+ nativeAgent: `agent-infra-lifecycle-${receipt.role}`,
1961
+ hookDefinitionHash: receipt.lifecycleProvenance.hookDefinitionHash
1962
+ }
1963
+ )
1964
+ );
1965
+ if (activeLifecycleEvidence) {
1966
+ return failed(
1967
+ 'ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE',
1968
+ 'active Codex lifecycle evidence blocks prepared receipt recovery',
1969
+ resolved.taskId
1970
+ );
1971
+ }
1972
+ if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
1973
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'active commit intent blocks prepared recovery', resolved.taskId);
1974
+ }
1975
+ const current = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
1976
+ gitRoot: gitRootFor(resolved.repoRoot, options),
1977
+ stateRoot: resolved.repoRoot,
1978
+ taskId: receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null
1979
+ });
1980
+ if (current !== receipt.beforeFingerprint) {
1981
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_DRIFT', 'workspace changed after delegation preparation', resolved.taskId);
1982
+ }
1983
+ const aborted = abortPreparedDelegation(receipt);
1984
+ if (!aborted.ok) return failed(aborted.code, aborted.message, resolved.taskId);
1985
+ const updated = withUpdatedRun(run, {
1986
+ status: 'running',
1987
+ pause: null,
1988
+ nextStage: null,
1989
+ pendingDelegation: null,
1990
+ receipts: Object.freeze([...run.receipts, aborted.receipt])
1991
+ }, options.now);
1992
+ saveRun(resolved.taskDir, updated);
1993
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1994
+ });
1995
+ } catch (error) {
1996
+ if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
1997
+ return failed(
1998
+ 'ORCHESTRATION_PREPARED_RECOVERY_FAILED',
1999
+ error instanceof Error ? error.message : String(error),
2000
+ resolved.taskId
2001
+ );
2002
+ }
2003
+ }
2004
+
1484
2005
  function completeOrchestrationStage(
1485
2006
  taskRef: string,
1486
2007
  event: Parameters<typeof completeDelegationStage>[1],
@@ -1490,6 +2011,7 @@ function completeOrchestrationStage(
1490
2011
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1491
2012
  const run = readRun(resolved.taskDir);
1492
2013
  if (!run) return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
2014
+ if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'stage completion requires schemaVersion 3', resolved.taskId);
1493
2015
  if (!run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
1494
2016
  const result = completeDelegationStage(run.pendingDelegation, event);
1495
2017
  if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
@@ -1512,6 +2034,7 @@ function inspectOrchestrationStage(
1512
2034
  return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
1513
2035
  }
1514
2036
  if (!run) return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
2037
+ if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'stage inspection requires schemaVersion 3', resolved.taskId);
1515
2038
  const receipt = run.pendingDelegation;
1516
2039
  if (
1517
2040
  !receipt
@@ -1570,6 +2093,7 @@ function sealOrchestrationDelegation(
1570
2093
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1571
2094
  const run = readRun(resolved.taskDir);
1572
2095
  if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
2096
+ if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'delegation sealing requires schemaVersion 3', resolved.taskId);
1573
2097
  const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
1574
2098
  if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
1575
2099
  const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
@@ -1582,6 +2106,7 @@ function advanceOrchestration(taskRef: string, options: OrchestrationOptions = {
1582
2106
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
1583
2107
  const run = readRun(resolved.taskDir);
1584
2108
  if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
2109
+ if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'orchestration advance requires schemaVersion 3', resolved.taskId);
1585
2110
  const result = consumeDelegation(run.pendingDelegation, { now: options.now });
1586
2111
  if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
1587
2112
  const completed = result.receipt.stage === 'commit';
@@ -1618,6 +2143,7 @@ function pauseOrchestration(
1618
2143
  export {
1619
2144
  activateMatchingOrchestrationDelegation,
1620
2145
  activateOrchestrationDelegation,
2146
+ awaitOrchestrationDelegationActivation,
1621
2147
  advanceOrchestration,
1622
2148
  abortCommitIntent,
1623
2149
  beginCommitIntent,
@@ -1626,15 +2152,22 @@ export {
1626
2152
  commitOrchestrationStageCompletion,
1627
2153
  completeCommitIntent,
1628
2154
  completeOrchestrationStage,
2155
+ dispatchOrchestrationDelegation,
1629
2156
  inspectOrchestrationStage,
2157
+ hasActivatableOrchestrationDelegation,
2158
+ hasSealableOrchestrationDelegation,
1630
2159
  orchestrationPath,
2160
+ pauseMatchingOrchestrationDelegation,
1631
2161
  pauseOrchestration,
1632
2162
  planOrchestrationStageCompletion,
1633
2163
  prepareOrchestrationDelegation,
1634
2164
  readRun,
2165
+ reconcileMatchingOrchestrationDelegation,
1635
2166
  recoverCommitIntent,
2167
+ recoverPreparedOrchestrationDelegation,
1636
2168
  routeOrchestration,
1637
2169
  sealMatchingOrchestrationDelegation,
2170
+ sealMatchingOrchestrationDelegationWithHostEvidence,
1638
2171
  sealOrchestrationDelegation,
1639
2172
  startCommitAttempt,
1640
2173
  statusCommitIntent,
@@ -1650,5 +2183,6 @@ export type {
1650
2183
  OrchestrationRun,
1651
2184
  OrchestrationStageCompletion,
1652
2185
  OrchestrationStageIdentity,
1653
- OrchestrationStatus
2186
+ OrchestrationStatus,
2187
+ OrchestrationOptions
1654
2188
  };