@fitlab-ai/agent-infra 0.9.6 → 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 (127) hide show
  1. package/dist/bin/internal-cli.js +5 -0
  2. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +45 -4
  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/sandbox-controller.js +370 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +21 -5
  7. package/dist/lib/defaults.json +10 -2
  8. package/dist/lib/git/workflow.js +30 -3
  9. package/dist/lib/internal/codex-lifecycle.js +69 -1
  10. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  11. package/dist/lib/internal/task-orchestration.js +37 -5
  12. package/dist/lib/platform/context.js +6 -4
  13. package/dist/lib/platform/github-client.js +55 -2
  14. package/dist/lib/process-data/checkpoint.js +319 -0
  15. package/dist/lib/process-data/index.js +128 -60
  16. package/dist/lib/process-data/materialize.js +84 -0
  17. package/dist/lib/process-data/normalize.js +34 -1
  18. package/dist/lib/process-data/reconcile.js +55 -1
  19. package/dist/lib/process-data/sources.js +422 -1
  20. package/dist/lib/process-data/store.js +341 -11
  21. package/dist/lib/sandbox/commands/create.js +103 -51
  22. package/dist/lib/sandbox/commands/rm.js +64 -15
  23. package/dist/lib/sandbox/commands/start.js +4 -3
  24. package/dist/lib/sandbox/control/container-identity.js +64 -0
  25. package/dist/lib/sandbox/control/executor.js +13 -7
  26. package/dist/lib/sandbox/control/lifecycle.js +238 -29
  27. package/dist/lib/sandbox/control/protocol.js +7 -0
  28. package/dist/lib/sandbox/control/server.js +202 -26
  29. package/dist/lib/sandbox/control/state.js +25 -15
  30. package/dist/lib/sandbox/control/timing.js +8 -0
  31. package/dist/lib/sandbox/mounts.js +5 -13
  32. package/dist/lib/sandbox/recovery.js +59 -42
  33. package/dist/lib/sandbox/workspace-view.js +57 -40
  34. package/dist/lib/server/daemon.js +1 -1
  35. package/dist/lib/server/process-control.js +11 -1
  36. package/dist/lib/server/process-state.js +57 -20
  37. package/dist/lib/task/artifact-lifecycle.js +82 -11
  38. package/dist/lib/task/artifact-receipts.js +128 -0
  39. package/dist/lib/task/codex-orchestration.js +81 -6
  40. package/dist/lib/task/commands/validate.js +1 -1
  41. package/dist/lib/task/create.js +10 -2
  42. package/dist/lib/task/delegation-receipts.js +84 -2
  43. package/dist/lib/task/events.js +139 -2
  44. package/dist/lib/task/ledger.js +13 -1
  45. package/dist/lib/task/orchestration.js +235 -14
  46. package/dist/lib/task/sections.js +5 -1
  47. package/dist/lib/task/task-execution-lock.js +5 -4
  48. package/dist/lib/task/verification-engine.js +40 -8
  49. package/dist/lifecycle-build-manifest.json +6 -0
  50. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +60 -3
  51. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  52. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
  53. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  54. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
  55. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +33 -4
  56. package/lib/defaults.json +10 -2
  57. package/lib/git/workflow.ts +25 -2
  58. package/lib/internal/codex-lifecycle.ts +68 -1
  59. package/lib/internal/codex-sandbox-controller.ts +109 -0
  60. package/lib/internal/task-orchestration.ts +31 -4
  61. package/lib/platform/context.ts +8 -5
  62. package/lib/platform/github-client.ts +61 -3
  63. package/lib/process-data/checkpoint.ts +335 -0
  64. package/lib/process-data/index.ts +112 -60
  65. package/lib/process-data/materialize.ts +83 -0
  66. package/lib/process-data/normalize.ts +29 -1
  67. package/lib/process-data/reconcile.ts +64 -2
  68. package/lib/process-data/sources.ts +449 -2
  69. package/lib/process-data/store.ts +304 -11
  70. package/lib/process-data/types.ts +124 -2
  71. package/lib/sandbox/commands/create.ts +56 -7
  72. package/lib/sandbox/commands/rm.ts +64 -14
  73. package/lib/sandbox/commands/start.ts +4 -3
  74. package/lib/sandbox/control/container-identity.ts +93 -0
  75. package/lib/sandbox/control/executor.ts +14 -10
  76. package/lib/sandbox/control/lifecycle.ts +264 -33
  77. package/lib/sandbox/control/protocol.ts +31 -5
  78. package/lib/sandbox/control/server.ts +184 -24
  79. package/lib/sandbox/control/state.ts +34 -17
  80. package/lib/sandbox/control/timing.ts +12 -0
  81. package/lib/sandbox/mounts.ts +5 -15
  82. package/lib/sandbox/recovery.ts +71 -42
  83. package/lib/sandbox/workspace-view.ts +61 -40
  84. package/lib/server/daemon.ts +1 -1
  85. package/lib/server/process-control.ts +13 -3
  86. package/lib/server/process-state.ts +82 -27
  87. package/lib/task/artifact-lifecycle.ts +73 -10
  88. package/lib/task/artifact-receipts.ts +161 -0
  89. package/lib/task/codex-orchestration.ts +91 -5
  90. package/lib/task/commands/validate.ts +1 -1
  91. package/lib/task/create.ts +11 -2
  92. package/lib/task/delegation-receipts.ts +160 -8
  93. package/lib/task/events.ts +122 -1
  94. package/lib/task/ledger.ts +16 -1
  95. package/lib/task/orchestration.ts +356 -17
  96. package/lib/task/sections.ts +6 -2
  97. package/lib/task/task-execution-lock.ts +11 -9
  98. package/lib/task/verification-engine.ts +51 -16
  99. package/package.json +1 -1
  100. package/templates/.agents/README.en.md +7 -0
  101. package/templates/.agents/README.zh-CN.md +5 -0
  102. package/templates/.agents/hooks/lifecycle-delegation.js +25 -1
  103. package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
  104. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
  105. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  106. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  107. package/templates/.agents/rules/review-handshake.en.md +3 -1
  108. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  109. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  110. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  111. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  112. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  113. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
  114. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
  115. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
  116. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
  117. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  118. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  119. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
  120. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
  121. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  122. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  123. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -0
  124. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -0
  125. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  126. package/templates/.agents/workspace/README.en.md +0 -26
  127. package/templates/.agents/workspace/README.zh-CN.md +0 -26
@@ -7,7 +7,7 @@ import { parseReviewSummary } from './review-artifacts.js';
7
7
  import { inspectArtifactDirectory } from './artifact-lifecycle.js';
8
8
  import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
9
9
  import { resolveTaskRef } from './resolve-ref.js';
10
- import { activateDelegation, completeDelegationStage, consumeDelegation, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
10
+ import { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
11
11
  import { normalizeAgentClients } from '../agent-clients/config.js';
12
12
  import { inspectPlatformPullRequest } from '../platform/pull-requests.js';
13
13
  import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
@@ -15,6 +15,7 @@ import { isAgentClientId } from '../agent-clients/types.js';
15
15
  import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
16
16
  import { assertGitRepositoryBinding } from '../git/worktree-identity.js';
17
17
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
18
+ import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.js';
18
19
  import { CommitIntentError, commitIntentPath, createCommitIntent, digest, readCommitIntent, removeCommitIntent, removeCommitIntentByDigest, serialize, updateCommitIntent } from './commit-intent.js';
19
20
  import { inspectCommitFinalization, planCommitTaskFinalization } from './commit-finalization.js';
20
21
  import { captureTaskWriteMetadata, writeTask } from './write.js';
@@ -33,7 +34,7 @@ function readRun(taskDir) {
33
34
  if (!fs.existsSync(file))
34
35
  return null;
35
36
  const run = JSON.parse(fs.readFileSync(file, 'utf8'));
36
- if (run.schemaVersion !== 2 || !isV2Policy(run.modelPolicy))
37
+ if (![2, 3].includes(run.schemaVersion) || !isV2Policy(run.modelPolicy))
37
38
  return run;
38
39
  return {
39
40
  ...run,
@@ -110,7 +111,7 @@ function commitFinalizationView(inspection) {
110
111
  }
111
112
  function validCommitRun(run, taskId) {
112
113
  return run !== null
113
- && [1, 2].includes(run.schemaVersion)
114
+ && [1, 2, 3].includes(run.schemaVersion)
114
115
  && run.taskId === taskId
115
116
  && ['running', 'paused', 'completed'].includes(run.status)
116
117
  && Array.isArray(run.receipts)
@@ -175,6 +176,20 @@ function canRecoverCodexUnsupportedPause(run, taskDir) {
175
176
  && !fs.existsSync(commitIntentPath(taskDir))
176
177
  && (run.recoveryHistory ?? []).every((entry) => entry.code === 'MODEL_POLICY_SUPPLEMENTED');
177
178
  }
179
+ function canMigrateRecoverableV2Pause(run, taskDir) {
180
+ return run.schemaVersion === 2
181
+ && run.status === 'paused'
182
+ && run.pause?.recoverable === true
183
+ && run.stepCount === 0
184
+ && run.nextStage === null
185
+ && run.baseline === ''
186
+ && run.pendingDelegation === null
187
+ && run.receipts.length === 0
188
+ && run.commitAuthorization?.issuedAt === null
189
+ && run.commitAuthorization?.consumedAt === null
190
+ && run.completionEvidence == null
191
+ && !fs.existsSync(commitIntentPath(taskDir));
192
+ }
178
193
  function beginOrResumeOrchestration(taskRef, options = {}) {
179
194
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
180
195
  if (!resolved.ok)
@@ -190,7 +205,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
190
205
  return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
191
206
  }
192
207
  if (existing) {
193
- if (![1, 2].includes(existing.schemaVersion)
208
+ if (![1, 2, 3].includes(existing.schemaVersion)
194
209
  || !Array.isArray(existing.receipts)
195
210
  || !Object.prototype.hasOwnProperty.call(existing, 'pendingDelegation')) {
196
211
  return failed('ORCHESTRATION_STATE_INVALID', 'orchestration state has an invalid schema', resolved.taskId);
@@ -198,17 +213,20 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
198
213
  if (existing.status === 'completed' && existing.schemaVersion === 1) {
199
214
  return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
200
215
  }
201
- if (existing.schemaVersion === 2) {
216
+ if (existing.schemaVersion === 2 || existing.schemaVersion === 3) {
202
217
  const policyError = validateModelPolicy(isV2Policy(existing.modelPolicy) ? existing.modelPolicy : undefined);
203
218
  if (policyError
204
219
  || !existing.modelPolicySource
205
220
  || !isAgentClientId(existing.modelPolicySource.client)
206
221
  || !Array.isArray(existing.recoveryHistory)) {
207
- return failed('ORCHESTRATION_STATE_INVALID', 'schemaVersion 2 orchestration state is incomplete', resolved.taskId);
222
+ return failed('ORCHESTRATION_STATE_INVALID', `schemaVersion ${existing.schemaVersion} orchestration state is incomplete`, resolved.taskId);
208
223
  }
209
224
  if (existing.status === 'completed') {
210
225
  return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
211
226
  }
227
+ if (existing.schemaVersion === 2 && existing.status === 'running') {
228
+ return failed('ORCHESTRATION_STATE_INVALID', 'active schemaVersion 2 runs cannot be advanced by lifecycle protocol v3', resolved.taskId);
229
+ }
212
230
  if (existing.modelPolicySource.client !== options.client) {
213
231
  return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
214
232
  }
@@ -246,6 +264,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
246
264
  resultingStatus: 'running'
247
265
  };
248
266
  const resumed = withUpdatedRun(existing, {
267
+ schemaVersion: 3,
249
268
  status: 'running',
250
269
  pause: null,
251
270
  recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
@@ -254,7 +273,42 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
254
273
  return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
255
274
  }
256
275
  if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
257
- const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
276
+ if (existing.schemaVersion === 2 && !canMigrateRecoverableV2Pause(existing, resolved.taskDir)) {
277
+ const paused = withUpdatedRun(existing, {
278
+ pause: {
279
+ code: 'ORCHESTRATION_SCHEMA_MIGRATION_REQUIRED',
280
+ message: 'schemaVersion 2 recoverable pause contains execution evidence and cannot migrate automatically',
281
+ recoverable: false
282
+ }
283
+ });
284
+ saveRun(resolved.taskDir, paused);
285
+ return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
286
+ }
287
+ const now = (options.now ?? (() => new Date().toISOString()))();
288
+ const recovery = existing.schemaVersion === 2 ? {
289
+ code: 'SCHEMA_V3_MIGRATED',
290
+ recoveredAt: now,
291
+ previousSchemaVersion: 2,
292
+ previousStatus: 'paused',
293
+ previousPause: existing.pause,
294
+ guards: {
295
+ stepCount: 0,
296
+ nextStage: null,
297
+ baselineEmpty: true,
298
+ receiptCount: 0,
299
+ pendingDelegation: false,
300
+ commitAuthorizationUnused: true,
301
+ completionEvidenceAbsent: true,
302
+ commitIntentAbsent: true
303
+ },
304
+ resultingStatus: 'running'
305
+ } : null;
306
+ const resumed = withUpdatedRun(existing, {
307
+ schemaVersion: 3,
308
+ status: 'running',
309
+ pause: null,
310
+ ...(recovery ? { recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery]) } : {})
311
+ }, () => now);
258
312
  saveRun(resolved.taskDir, resumed);
259
313
  return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
260
314
  }
@@ -328,7 +382,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
328
382
  };
329
383
  const recovered = Object.freeze({
330
384
  ...existing,
331
- schemaVersion: 2,
385
+ schemaVersion: 3,
332
386
  status: resultingStatus,
333
387
  modelPolicy: policy,
334
388
  modelPolicySource: source,
@@ -360,7 +414,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
360
414
  const now = (options.now ?? (() => new Date().toISOString()))();
361
415
  const source = { kind: sourceKind, client: options.client, resolvedAt: now };
362
416
  const run = {
363
- schemaVersion: 2,
417
+ schemaVersion: 3,
364
418
  taskId: resolved.taskId,
365
419
  runId: (options.id ?? randomUUID)(),
366
420
  status: 'running',
@@ -1025,6 +1079,9 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
1025
1079
  const run = readRun(resolved.taskDir);
1026
1080
  if (!run || run.status !== 'running')
1027
1081
  return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
1082
+ if (run.schemaVersion !== 3) {
1083
+ return failed('ORCHESTRATION_STATE_INVALID', 'delegation preparation requires orchestration schemaVersion 3', resolved.taskId);
1084
+ }
1028
1085
  if (!isV2Policy(run.modelPolicy))
1029
1086
  return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
1030
1087
  if (run.pendingDelegation)
@@ -1045,6 +1102,9 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
1045
1102
  if (!validModel(input.requestedReasoningEffort)) {
1046
1103
  return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
1047
1104
  }
1105
+ if (input.client === 'codex' && !input.lifecycleProvenance) {
1106
+ return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
1107
+ }
1048
1108
  const expectedPolicy = run.modelPolicy[next.role];
1049
1109
  if (input.requestedModel !== expectedPolicy.model) {
1050
1110
  return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
@@ -1074,8 +1134,9 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
1074
1134
  requestedModel: input.requestedModel,
1075
1135
  requestedReasoningEffort: input.requestedReasoningEffort,
1076
1136
  workspaceSnapshotScope: 'task',
1137
+ lifecycleProvenance: input.lifecycleProvenance ?? null,
1077
1138
  beforeFingerprint
1078
- }, { id: options.id, now: options.now });
1139
+ }, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
1079
1140
  const updated = withUpdatedRun(run, {
1080
1141
  nextStage: next.stage,
1081
1142
  pendingDelegation: receipt,
@@ -1099,6 +1160,39 @@ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
1099
1160
  throw error;
1100
1161
  }
1101
1162
  }
1163
+ function dispatchOrchestrationDelegationUnlocked(taskRef, options = {}) {
1164
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1165
+ if (!resolved.ok)
1166
+ return failed(resolved.code, resolved.message, resolved.taskId);
1167
+ const run = readRun(resolved.taskDir);
1168
+ if (!run?.pendingDelegation)
1169
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1170
+ if (run.schemaVersion !== 3 || run.status !== 'running') {
1171
+ return failed('ORCHESTRATION_STATE_INVALID', 'spawn dispatch requires a running schemaVersion 3 orchestration', resolved.taskId);
1172
+ }
1173
+ const result = dispatchDelegation(run.pendingDelegation, {
1174
+ now: options.now,
1175
+ monotonicNow: options.monotonicNow
1176
+ });
1177
+ if (!result.ok)
1178
+ return pauseOrchestration(taskRef, result.code, result.message, true, options);
1179
+ const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
1180
+ saveRun(resolved.taskDir, updated);
1181
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1182
+ }
1183
+ function dispatchOrchestrationDelegation(taskRef, options = {}) {
1184
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1185
+ if (!resolved.ok)
1186
+ return failed(resolved.code, resolved.message, resolved.taskId);
1187
+ try {
1188
+ return withTaskExecutionLock(resolved.repoRoot, '__repository__', 'task-orchestration.dispatch.repository', () => withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.dispatch.task', () => dispatchOrchestrationDelegationUnlocked(taskRef, options)));
1189
+ }
1190
+ catch (error) {
1191
+ if (error instanceof TaskExecutionLockError)
1192
+ return failed(error.code, error.message, resolved.taskId);
1193
+ throw error;
1194
+ }
1195
+ }
1102
1196
  function matchingDelegations(predicate, options) {
1103
1197
  const repoRoot = options.repoRoot ?? process.cwd();
1104
1198
  const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
@@ -1108,7 +1202,7 @@ function matchingDelegations(predicate, options) {
1108
1202
  .filter((entry) => entry.isDirectory())
1109
1203
  .flatMap((entry) => {
1110
1204
  const run = readRun(path.join(activeRoot, entry.name));
1111
- if (run?.status !== 'running' || !run.pendingDelegation)
1205
+ if (run?.schemaVersion !== 3 || run.status !== 'running' || !run.pendingDelegation)
1112
1206
  return [];
1113
1207
  const receipt = run.pendingDelegation;
1114
1208
  return predicate(receipt)
@@ -1124,6 +1218,15 @@ function uniqueMatchingDelegation(client, predicate, options) {
1124
1218
  return failed('ORCHESTRATION_DELEGATION_AMBIGUOUS', 'multiple lifecycle delegations match the native hook event');
1125
1219
  return matches[0];
1126
1220
  }
1221
+ function hasActivatableOrchestrationDelegation(client, childId, options = {}) {
1222
+ return matchingDelegations((receipt) => receipt.client === client && (receipt.status === 'prepared'
1223
+ || (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))), options).length > 0;
1224
+ }
1225
+ function hasSealableOrchestrationDelegation(client, childId, options = {}) {
1226
+ return matchingDelegations((receipt) => receipt.client === client
1227
+ && receipt.childId === childId
1228
+ && ['stage-completed', 'sealed'].includes(receipt.status), options).length > 0;
1229
+ }
1127
1230
  function activateMatchingOrchestrationDelegation(client, event, options = {}) {
1128
1231
  const role = managedDelegationRole(event.nativeAgent);
1129
1232
  if (!role)
@@ -1217,7 +1320,7 @@ function sealMatchingOrchestrationDelegationWithHostEvidence(client, event, cons
1217
1320
  });
1218
1321
  const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
1219
1322
  const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
1220
- const validated = sealDelegation(receipt, baseEvent, { now: options.now });
1323
+ const validated = sealDelegation(receipt, baseEvent, { now: options.now, requireHostEvidence: false });
1221
1324
  if (!validated.ok) {
1222
1325
  return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
1223
1326
  }
@@ -1255,7 +1358,15 @@ function activateOrchestrationDelegation(taskRef, event, options = {}) {
1255
1358
  const run = readRun(resolved.taskDir);
1256
1359
  if (!run?.pendingDelegation)
1257
1360
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1258
- const result = activateDelegation(run.pendingDelegation, event, { now: options.now });
1361
+ if (run.schemaVersion !== 3)
1362
+ return failed('ORCHESTRATION_STATE_INVALID', 'active delegation requires schemaVersion 3', resolved.taskId);
1363
+ if (run.status !== 'running') {
1364
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1365
+ }
1366
+ const result = activateDelegation(run.pendingDelegation, event, {
1367
+ now: options.now,
1368
+ monotonicNow: options.monotonicNow
1369
+ });
1259
1370
  if (!result.ok) {
1260
1371
  if (result.code === 'DELEGATION_IGNORED')
1261
1372
  return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
@@ -1265,6 +1376,108 @@ function activateOrchestrationDelegation(taskRef, event, options = {}) {
1265
1376
  saveRun(resolved.taskDir, updated);
1266
1377
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1267
1378
  }
1379
+ async function awaitOrchestrationDelegationActivation(taskRef, event, options = {}) {
1380
+ const sleep = options.sleep
1381
+ ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
1382
+ while (true) {
1383
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1384
+ if (!resolved.ok)
1385
+ return failed(resolved.code, resolved.message, resolved.taskId);
1386
+ const run = readRun(resolved.taskDir);
1387
+ if (!run?.pendingDelegation) {
1388
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1389
+ }
1390
+ if (run.schemaVersion !== 3) {
1391
+ return failed('ORCHESTRATION_STATE_INVALID', 'activation barrier requires schemaVersion 3', resolved.taskId);
1392
+ }
1393
+ const receipt = run.pendingDelegation;
1394
+ const matches = receipt.stage === event.stage
1395
+ && receipt.round === event.round
1396
+ && receipt.artifact === event.artifact
1397
+ && receipt.role === event.role;
1398
+ if (!matches) {
1399
+ return pauseOrchestration(taskRef, 'ORCHESTRATION_PROVENANCE_MISMATCH', 'activation barrier identity does not match the pending delegation', true, options);
1400
+ }
1401
+ if (receipt.status === 'activated' || receipt.status === 'stage-completed') {
1402
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1403
+ }
1404
+ if (run.status !== 'running') {
1405
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1406
+ }
1407
+ if (receipt.status !== 'prepared') {
1408
+ return pauseOrchestration(taskRef, 'ORCHESTRATION_ACTIVATION_STATE_INVALID', `activation barrier found receipt status '${receipt.status}'`, true, options);
1409
+ }
1410
+ if (receipt.activationDeadlineAt == null
1411
+ || !Number.isFinite(Date.parse(receipt.activationDeadlineAt))) {
1412
+ return pauseOrchestration(taskRef, 'ORCHESTRATION_DELEGATION_NOT_DISPATCHED', 'activation barrier was entered before the native spawn dispatch boundary', true, options);
1413
+ }
1414
+ if (Date.parse((options.now ?? (() => new Date().toISOString()))()) >= Date.parse(receipt.activationDeadlineAt)) {
1415
+ return pauseOrchestration(taskRef, 'ORCHESTRATION_ACTIVATION_TIMEOUT', 'fresh child did not receive trusted activation evidence before the deadline', true, options);
1416
+ }
1417
+ await sleep(100);
1418
+ }
1419
+ }
1420
+ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
1421
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1422
+ if (!resolved.ok)
1423
+ return failed(resolved.code, resolved.message, resolved.taskId);
1424
+ try {
1425
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.recover-prepared', () => {
1426
+ const run = readRun(resolved.taskDir);
1427
+ const receipt = run?.pendingDelegation;
1428
+ if (!run || !receipt) {
1429
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1430
+ }
1431
+ if (receipt.status !== 'prepared') {
1432
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'only a never-activated prepared receipt can recover', resolved.taskId);
1433
+ }
1434
+ const wallNow = Date.parse((options.now ?? (() => new Date().toISOString()))());
1435
+ if (receipt.activationDeadlineAt != null
1436
+ && Number.isFinite(Date.parse(receipt.activationDeadlineAt))
1437
+ && wallNow <= Date.parse(receipt.activationDeadlineAt)) {
1438
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_EARLY', 'prepared receipt activation deadline has not elapsed', resolved.taskId);
1439
+ }
1440
+ const activeLifecycleEvidence = options.hasActiveLifecycleEvidence?.(receipt)
1441
+ ?? (receipt.client === 'codex'
1442
+ && receipt.lifecycleProvenance
1443
+ && hasActiveCodexLifecycleEvidence(path.join(resolved.repoRoot, '.agents', 'workspace', '.runtime', 'codex-lifecycle'), {
1444
+ nativeAgent: `agent-infra-lifecycle-${receipt.role}`,
1445
+ hookDefinitionHash: receipt.lifecycleProvenance.hookDefinitionHash
1446
+ }));
1447
+ if (activeLifecycleEvidence) {
1448
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE', 'active Codex lifecycle evidence blocks prepared receipt recovery', resolved.taskId);
1449
+ }
1450
+ if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
1451
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'active commit intent blocks prepared recovery', resolved.taskId);
1452
+ }
1453
+ const current = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
1454
+ gitRoot: gitRootFor(resolved.repoRoot, options),
1455
+ stateRoot: resolved.repoRoot,
1456
+ taskId: receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null
1457
+ });
1458
+ if (current !== receipt.beforeFingerprint) {
1459
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_DRIFT', 'workspace changed after delegation preparation', resolved.taskId);
1460
+ }
1461
+ const aborted = abortPreparedDelegation(receipt);
1462
+ if (!aborted.ok)
1463
+ return failed(aborted.code, aborted.message, resolved.taskId);
1464
+ const updated = withUpdatedRun(run, {
1465
+ status: 'running',
1466
+ pause: null,
1467
+ nextStage: null,
1468
+ pendingDelegation: null,
1469
+ receipts: Object.freeze([...run.receipts, aborted.receipt])
1470
+ }, options.now);
1471
+ saveRun(resolved.taskDir, updated);
1472
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1473
+ });
1474
+ }
1475
+ catch (error) {
1476
+ if (error instanceof TaskExecutionLockError)
1477
+ return failed(error.code, error.message, resolved.taskId);
1478
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
1479
+ }
1480
+ }
1268
1481
  function completeOrchestrationStage(taskRef, event, options = {}) {
1269
1482
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1270
1483
  if (!resolved.ok)
@@ -1272,6 +1485,8 @@ function completeOrchestrationStage(taskRef, event, options = {}) {
1272
1485
  const run = readRun(resolved.taskDir);
1273
1486
  if (!run)
1274
1487
  return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
1488
+ if (run.schemaVersion !== 3)
1489
+ return failed('ORCHESTRATION_STATE_INVALID', 'stage completion requires schemaVersion 3', resolved.taskId);
1275
1490
  if (!run.pendingDelegation)
1276
1491
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
1277
1492
  const result = completeDelegationStage(run.pendingDelegation, event);
@@ -1294,6 +1509,8 @@ function inspectOrchestrationStage(taskRef, event, options = {}) {
1294
1509
  }
1295
1510
  if (!run)
1296
1511
  return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
1512
+ if (run.schemaVersion !== 3)
1513
+ return failed('ORCHESTRATION_STATE_INVALID', 'stage inspection requires schemaVersion 3', resolved.taskId);
1297
1514
  const receipt = run.pendingDelegation;
1298
1515
  if (!receipt
1299
1516
  || receipt.status !== 'activated'
@@ -1338,6 +1555,8 @@ function sealOrchestrationDelegation(taskRef, event, options = {}) {
1338
1555
  const run = readRun(resolved.taskDir);
1339
1556
  if (!run?.pendingDelegation)
1340
1557
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1558
+ if (run.schemaVersion !== 3)
1559
+ return failed('ORCHESTRATION_STATE_INVALID', 'delegation sealing requires schemaVersion 3', resolved.taskId);
1341
1560
  const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
1342
1561
  if (!result.ok)
1343
1562
  return pauseOrchestration(taskRef, result.code, result.message, true, options);
@@ -1352,6 +1571,8 @@ function advanceOrchestration(taskRef, options = {}) {
1352
1571
  const run = readRun(resolved.taskDir);
1353
1572
  if (!run?.pendingDelegation)
1354
1573
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1574
+ if (run.schemaVersion !== 3)
1575
+ return failed('ORCHESTRATION_STATE_INVALID', 'orchestration advance requires schemaVersion 3', resolved.taskId);
1355
1576
  const result = consumeDelegation(run.pendingDelegation, { now: options.now });
1356
1577
  if (!result.ok)
1357
1578
  return pauseOrchestration(taskRef, result.code, result.message, true, options);
@@ -1380,5 +1601,5 @@ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
1380
1601
  saveRun(resolved.taskDir, updated);
1381
1602
  return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1382
1603
  }
1383
- export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, abortCommitIntent, beginCommitIntent, beginOrResumeOrchestration, checkpointCommitIntent, commitOrchestrationStageCompletion, completeCommitIntent, completeOrchestrationStage, inspectOrchestrationStage, orchestrationPath, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverCommitIntent, routeOrchestration, sealMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence, sealOrchestrationDelegation, startCommitAttempt, statusCommitIntent, statusOrchestration, terminateCommitAttempt };
1604
+ export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, abortCommitIntent, beginCommitIntent, beginOrResumeOrchestration, checkpointCommitIntent, commitOrchestrationStageCompletion, completeCommitIntent, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, orchestrationPath, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverCommitIntent, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence, sealOrchestrationDelegation, startCommitAttempt, statusCommitIntent, statusOrchestration, terminateCommitAttempt };
1384
1605
  //# sourceMappingURL=orchestration.js.map
@@ -16,6 +16,10 @@ function parseTable(content, input) {
16
16
  new Set(input.columns).size !== input.columns.length) {
17
17
  throw new DocumentMutationError('MUTATION_INVALID', 'table columns are invalid');
18
18
  }
19
+ const keyColumn = input.keyColumn ?? input.columns[0];
20
+ if (!input.columns.includes(keyColumn)) {
21
+ throw new DocumentMutationError('MUTATION_INVALID', 'table key column is invalid');
22
+ }
19
23
  const sections = matchingSections(content, input.sectionAliases);
20
24
  if (sections.length === 0)
21
25
  return null;
@@ -60,7 +64,7 @@ function parseTable(content, input) {
60
64
  column,
61
65
  decodeCell(parsed.cells[columnIndex]).trim()
62
66
  ]));
63
- const key = values[input.columns[0]];
67
+ const key = values[keyColumn];
64
68
  if (seen.has(key)) {
65
69
  throw new DocumentMutationError('TABLE_DUPLICATE_KEY', `duplicate table key '${key}'`);
66
70
  }
@@ -38,11 +38,12 @@ function parseOwner(raw, key) {
38
38
  try {
39
39
  const value = JSON.parse(raw);
40
40
  if (value !== null
41
- && value.version === 1
41
+ && value.version === 2
42
42
  && Number.isSafeInteger(value.pid)
43
43
  && (value.pid ?? 0) > 0
44
- && typeof value.startTime === 'string'
45
- && value.startTime.length > 0
44
+ && typeof value.startTime === 'number'
45
+ && Number.isSafeInteger(value.startTime)
46
+ && value.startTime >= 0
46
47
  && typeof value.token === 'string'
47
48
  && value.token.length > 0
48
49
  && typeof value.owner === 'string'
@@ -148,7 +149,7 @@ function withTaskExecutionLock(repoRoot, taskId, ownerName, callback, options =
148
149
  throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'current process start time is unavailable', { operation: 'identity', errno: null, key });
149
150
  }
150
151
  const owner = {
151
- version: 1,
152
+ version: 2,
152
153
  pid: process.pid,
153
154
  startTime,
154
155
  token,
@@ -151,7 +151,7 @@ function checkOrchestrationState({ taskDir }) {
151
151
  if (isFailClosedLegacyPause(run)) {
152
152
  return passResult('orchestration-state', 'Legacy orchestration run is safely paused');
153
153
  }
154
- if (run.schemaVersion !== 2 || !['paused', 'completed'].includes(run.status)) {
154
+ if (![2, 3].includes(run.schemaVersion) || !['paused', 'completed'].includes(run.status)) {
155
155
  return failResult('orchestration-state', `Expected paused or completed run, received '${run.status}'`);
156
156
  }
157
157
  if (run.status === 'paused' && (!run.pause?.code || !run.pause?.message)) {
@@ -916,7 +916,13 @@ function checkPostReviewCommit({ taskDir, config }) {
916
916
  return blockedResult("post-review-commit", relation.message, relation.code);
917
917
  }
918
918
  if (relation.status === "strict") {
919
- return failResult("post-review-commit", "Merged PR evidence did not establish squash merge equivalence", "PR_MERGE_IDENTITY_INVALID");
919
+ return resolvePostReviewIdentityFailure(task.content, {
920
+ code: "PR_MERGE_IDENTITY_INVALID",
921
+ message: "Merged PR evidence did not establish squash merge equivalence"
922
+ });
923
+ }
924
+ if (relation.code === "PR_MERGE_IDENTITY_INVALID") {
925
+ return resolvePostReviewIdentityFailure(task.content, relation);
920
926
  }
921
927
  return failResult("post-review-commit", relation.message, relation.code);
922
928
  }
@@ -981,21 +987,47 @@ function checkPostReviewCommit({ taskDir, config }) {
981
987
  }
982
988
  const exemption = resolvePostReviewExemption(task.content);
983
989
  if (!exemption.ok)
984
- return failResult("post-review-commit", exemption.message);
985
- if (exemption.exempt) {
986
- return passResult("post-review-commit", `${commits.length} post-review commit(s) covered by a human-decided exemption`);
990
+ return failResult("post-review-commit", exemption.message, exemption.code);
991
+ if (exemption.exemptions.length > 0) {
992
+ return passResult("post-review-commit", `${commits.length} post-review commit(s) covered by a human-decided exemption: ${formatPostReviewExemptions(exemption.exemptions)}`);
987
993
  }
988
994
  return failResult("post-review-commit", `${commits.length} commit(s) to code/rule paths after reviewed commit ${sha.slice(0, 8)}; re-run review-code or record a human-decided exemption`);
989
995
  }
996
+ function resolvePostReviewIdentityFailure(content, failure) {
997
+ const exemption = resolvePostReviewExemption(content);
998
+ if (!exemption.ok) {
999
+ return failResult("post-review-commit", exemption.message, exemption.code);
1000
+ }
1001
+ if (exemption.exemptions.length === 0) {
1002
+ return failResult("post-review-commit", failure.message, failure.code);
1003
+ }
1004
+ return passResult("post-review-commit", `Human-decided post-review exemption overrode ${failure.code}: ${failure.message}; ${formatPostReviewExemptions(exemption.exemptions)}`);
1005
+ }
1006
+ function formatPostReviewExemptions(exemptions) {
1007
+ return exemptions.map((row) => `${row.id}: ${row.evidence}`).join("; ");
1008
+ }
990
1009
  function resolvePostReviewExemption(content) {
991
1010
  try {
1011
+ const rows = parseLedger(content);
1012
+ const invalid = validateLedgerRows(rows);
1013
+ if (invalid) {
1014
+ return {
1015
+ ok: false,
1016
+ code: "POST_REVIEW_EXEMPTION_INVALID",
1017
+ message: `${invalid.code}: ${invalid.message}`
1018
+ };
1019
+ }
992
1020
  return {
993
1021
  ok: true,
994
- exempt: parseLedger(content).some((row) => row.stage === POST_REVIEW_COMMIT_STAGE && row.status === "human-decided")
1022
+ exemptions: rows.filter((row) => row.stage === POST_REVIEW_COMMIT_STAGE)
995
1023
  };
996
1024
  }
997
- catch {
998
- return { ok: false, message: "Invalid disagreement ledger" };
1025
+ catch (error) {
1026
+ return {
1027
+ ok: false,
1028
+ code: "POST_REVIEW_EXEMPTION_INVALID",
1029
+ message: `Invalid disagreement ledger: ${error instanceof Error ? error.message : String(error)}`
1030
+ };
999
1031
  }
1000
1032
  }
1001
1033
  function checkReviewFact({ taskDir, artifactFile }) {
@@ -0,0 +1,6 @@
1
+ {
2
+ "protocolVersion": 3,
3
+ "packageVersion": "0.9.7",
4
+ "sourceInputHash": "ee12d1ec7a586eeaba04deb4253ccd7f5d16552915ca141e493248c0728459a9",
5
+ "internalExecutableBuildHash": "0eceb914e6c1cb9937b86d34bfbf933d1010f499b9976813556146d2e6fa0132"
6
+ }