@fitlab-ai/agent-infra 0.9.6 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/bin/internal-cli.js +10 -0
  2. package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
  8. package/dist/lib/decide.js +68 -14
  9. package/dist/lib/defaults.json +10 -2
  10. package/dist/lib/git/workflow.js +30 -3
  11. package/dist/lib/init.js +1 -1
  12. package/dist/lib/internal/codex-lifecycle.js +69 -1
  13. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  14. package/dist/lib/internal/release-workflow.js +1 -3
  15. package/dist/lib/internal/task-activity.js +57 -7
  16. package/dist/lib/internal/task-event.js +57 -4
  17. package/dist/lib/internal/task-ledger.js +59 -5
  18. package/dist/lib/internal/task-lifecycle.js +59 -8
  19. package/dist/lib/internal/task-orchestration.js +48 -8
  20. package/dist/lib/internal/task-override.js +131 -0
  21. package/dist/lib/internal/task-review.js +64 -6
  22. package/dist/lib/internal/task-verify.js +65 -3
  23. package/dist/lib/internal/task-warning.js +57 -4
  24. package/dist/lib/platform/context.js +6 -4
  25. package/dist/lib/platform/github-client.js +55 -2
  26. package/dist/lib/platform/pr-summary.js +5 -3
  27. package/dist/lib/process-data/checkpoint.js +319 -0
  28. package/dist/lib/process-data/index.js +128 -60
  29. package/dist/lib/process-data/materialize.js +84 -0
  30. package/dist/lib/process-data/normalize.js +34 -1
  31. package/dist/lib/process-data/reconcile.js +55 -1
  32. package/dist/lib/process-data/sources.js +422 -1
  33. package/dist/lib/process-data/store.js +341 -11
  34. package/dist/lib/runtime/agent-runtime.js +56 -0
  35. package/dist/lib/sandbox/commands/create.js +394 -264
  36. package/dist/lib/sandbox/commands/rm.js +64 -15
  37. package/dist/lib/sandbox/commands/start.js +4 -3
  38. package/dist/lib/sandbox/control/container-identity.js +64 -0
  39. package/dist/lib/sandbox/control/executor.js +23 -9
  40. package/dist/lib/sandbox/control/lifecycle.js +590 -61
  41. package/dist/lib/sandbox/control/protocol.js +7 -0
  42. package/dist/lib/sandbox/control/server.js +202 -26
  43. package/dist/lib/sandbox/control/state.js +51 -25
  44. package/dist/lib/sandbox/control/timing.js +8 -0
  45. package/dist/lib/sandbox/engine.js +14 -7
  46. package/dist/lib/sandbox/mounts.js +7 -0
  47. package/dist/lib/sandbox/recovery.js +135 -50
  48. package/dist/lib/sandbox/workspace-view.js +59 -20
  49. package/dist/lib/server/daemon.js +1 -1
  50. package/dist/lib/server/process-control.js +11 -1
  51. package/dist/lib/server/process-state.js +107 -20
  52. package/dist/lib/task/activity-intent.js +4 -1
  53. package/dist/lib/task/artifact-lifecycle.js +82 -11
  54. package/dist/lib/task/artifact-receipts.js +128 -0
  55. package/dist/lib/task/codex-orchestration.js +81 -6
  56. package/dist/lib/task/commands/validate.js +1 -1
  57. package/dist/lib/task/create.js +10 -2
  58. package/dist/lib/task/decision-intents.js +4 -2
  59. package/dist/lib/task/delegation-receipts.js +120 -17
  60. package/dist/lib/task/events.js +148 -7
  61. package/dist/lib/task/guard-override.js +31 -0
  62. package/dist/lib/task/human-override.js +1240 -0
  63. package/dist/lib/task/ledger-intents.js +6 -3
  64. package/dist/lib/task/ledger.js +13 -1
  65. package/dist/lib/task/lifecycle.js +69 -14
  66. package/dist/lib/task/orchestration.js +293 -18
  67. package/dist/lib/task/review-finalization.js +3 -2
  68. package/dist/lib/task/sections.js +5 -1
  69. package/dist/lib/task/task-execution-lock.js +5 -4
  70. package/dist/lib/task/verification-engine.js +80 -22
  71. package/dist/lib/task/verification.js +13 -1
  72. package/dist/lib/task/workflow-warning-intents.js +4 -2
  73. package/dist/lib/task/write.js +2 -1
  74. package/dist/lifecycle-build-manifest.json +6 -0
  75. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  76. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
  77. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  78. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  80. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
  81. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
  82. package/lib/decide.ts +59 -14
  83. package/lib/defaults.json +10 -2
  84. package/lib/git/workflow.ts +25 -2
  85. package/lib/init.ts +1 -1
  86. package/lib/internal/codex-lifecycle.ts +68 -1
  87. package/lib/internal/codex-sandbox-controller.ts +109 -0
  88. package/lib/internal/release-workflow.ts +1 -3
  89. package/lib/internal/task-activity.ts +46 -6
  90. package/lib/internal/task-event.ts +53 -4
  91. package/lib/internal/task-ledger.ts +48 -4
  92. package/lib/internal/task-lifecycle.ts +56 -12
  93. package/lib/internal/task-orchestration.ts +43 -8
  94. package/lib/internal/task-override.ts +138 -0
  95. package/lib/internal/task-review.ts +51 -6
  96. package/lib/internal/task-verify.ts +52 -3
  97. package/lib/internal/task-warning.ts +44 -3
  98. package/lib/platform/context.ts +8 -5
  99. package/lib/platform/github-client.ts +61 -3
  100. package/lib/platform/pr-summary.ts +10 -3
  101. package/lib/process-data/checkpoint.ts +335 -0
  102. package/lib/process-data/index.ts +112 -60
  103. package/lib/process-data/materialize.ts +83 -0
  104. package/lib/process-data/normalize.ts +29 -1
  105. package/lib/process-data/reconcile.ts +64 -2
  106. package/lib/process-data/sources.ts +449 -2
  107. package/lib/process-data/store.ts +304 -11
  108. package/lib/process-data/types.ts +124 -2
  109. package/lib/runtime/agent-runtime.ts +76 -0
  110. package/lib/sandbox/commands/create.ts +139 -6
  111. package/lib/sandbox/commands/rm.ts +64 -14
  112. package/lib/sandbox/commands/start.ts +4 -3
  113. package/lib/sandbox/config.ts +2 -1
  114. package/lib/sandbox/control/container-identity.ts +93 -0
  115. package/lib/sandbox/control/executor.ts +24 -12
  116. package/lib/sandbox/control/lifecycle.ts +685 -67
  117. package/lib/sandbox/control/protocol.ts +35 -6
  118. package/lib/sandbox/control/server.ts +184 -24
  119. package/lib/sandbox/control/state.ts +67 -33
  120. package/lib/sandbox/control/timing.ts +12 -0
  121. package/lib/sandbox/engine.ts +20 -9
  122. package/lib/sandbox/mounts.ts +8 -0
  123. package/lib/sandbox/recovery.ts +159 -49
  124. package/lib/sandbox/workspace-view.ts +73 -22
  125. package/lib/server/daemon.ts +1 -1
  126. package/lib/server/process-control.ts +13 -3
  127. package/lib/server/process-state.ts +129 -27
  128. package/lib/task/activity-intent.ts +4 -2
  129. package/lib/task/artifact-lifecycle.ts +73 -10
  130. package/lib/task/artifact-receipts.ts +161 -0
  131. package/lib/task/codex-orchestration.ts +91 -5
  132. package/lib/task/commands/validate.ts +1 -1
  133. package/lib/task/create.ts +11 -2
  134. package/lib/task/decision-intents.ts +4 -2
  135. package/lib/task/delegation-receipts.ts +205 -25
  136. package/lib/task/events.ts +132 -6
  137. package/lib/task/guard-override.ts +50 -0
  138. package/lib/task/human-override.ts +1503 -0
  139. package/lib/task/ledger-intents.ts +6 -3
  140. package/lib/task/ledger.ts +16 -1
  141. package/lib/task/lifecycle.ts +71 -14
  142. package/lib/task/orchestration.ts +443 -22
  143. package/lib/task/review-finalization.ts +6 -2
  144. package/lib/task/sections.ts +6 -2
  145. package/lib/task/task-execution-lock.ts +11 -9
  146. package/lib/task/verification-engine.ts +93 -32
  147. package/lib/task/verification.ts +15 -1
  148. package/lib/task/workflow-warning-intents.ts +4 -2
  149. package/lib/task/write.ts +4 -1
  150. package/package.json +1 -1
  151. package/templates/.agents/README.en.md +7 -0
  152. package/templates/.agents/README.zh-CN.md +5 -0
  153. package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
  154. package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
  155. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
  156. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  157. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  158. package/templates/.agents/rules/review-handshake.en.md +3 -1
  159. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  160. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  161. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  162. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  163. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  164. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
  165. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
  166. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
  167. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
  168. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  169. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  170. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
  171. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
  172. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  173. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  174. package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
  175. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
  176. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  177. package/templates/.agents/workspace/README.en.md +0 -26
  178. package/templates/.agents/workspace/README.zh-CN.md +0 -26
@@ -7,14 +7,16 @@ 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, foldBlankToNull, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
11
11
  import { normalizeAgentClients } from '../agent-clients/config.js';
12
+ import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.js';
12
13
  import { inspectPlatformPullRequest } from '../platform/pull-requests.js';
13
14
  import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
14
15
  import { isAgentClientId } from '../agent-clients/types.js';
15
16
  import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
16
17
  import { assertGitRepositoryBinding } from '../git/worktree-identity.js';
17
18
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
19
+ import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.js';
18
20
  import { CommitIntentError, commitIntentPath, createCommitIntent, digest, readCommitIntent, removeCommitIntent, removeCommitIntentByDigest, serialize, updateCommitIntent } from './commit-intent.js';
19
21
  import { inspectCommitFinalization, planCommitTaskFinalization } from './commit-finalization.js';
20
22
  import { captureTaskWriteMetadata, writeTask } from './write.js';
@@ -33,7 +35,7 @@ function readRun(taskDir) {
33
35
  if (!fs.existsSync(file))
34
36
  return null;
35
37
  const run = JSON.parse(fs.readFileSync(file, 'utf8'));
36
- if (run.schemaVersion !== 2 || !isV2Policy(run.modelPolicy))
38
+ if (![2, 3].includes(run.schemaVersion) || !isV2Policy(run.modelPolicy))
37
39
  return run;
38
40
  return {
39
41
  ...run,
@@ -110,7 +112,7 @@ function commitFinalizationView(inspection) {
110
112
  }
111
113
  function validCommitRun(run, taskId) {
112
114
  return run !== null
113
- && [1, 2].includes(run.schemaVersion)
115
+ && [1, 2, 3].includes(run.schemaVersion)
114
116
  && run.taskId === taskId
115
117
  && ['running', 'paused', 'completed'].includes(run.status)
116
118
  && Array.isArray(run.receipts)
@@ -175,6 +177,36 @@ function canRecoverCodexUnsupportedPause(run, taskDir) {
175
177
  && !fs.existsSync(commitIntentPath(taskDir))
176
178
  && (run.recoveryHistory ?? []).every((entry) => entry.code === 'MODEL_POLICY_SUPPLEMENTED');
177
179
  }
180
+ function canRecoverClaudeCodeUnsupportedPause(run, taskDir) {
181
+ return run.schemaVersion === 3
182
+ && run.status === 'paused'
183
+ && run.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
184
+ && run.modelPolicySource?.client === 'claude-code'
185
+ && run.stepCount === 0
186
+ && run.nextStage === null
187
+ && run.baseline === ''
188
+ && run.pendingDelegation === null
189
+ && run.receipts.length === 0
190
+ && run.commitAuthorization?.issuedAt === null
191
+ && run.commitAuthorization?.consumedAt === null
192
+ && run.completionEvidence == null
193
+ && !fs.existsSync(commitIntentPath(taskDir))
194
+ && (run.recoveryHistory ?? []).length === 0;
195
+ }
196
+ function canMigrateRecoverableV2Pause(run, taskDir) {
197
+ return run.schemaVersion === 2
198
+ && run.status === 'paused'
199
+ && run.pause?.recoverable === true
200
+ && run.stepCount === 0
201
+ && run.nextStage === null
202
+ && run.baseline === ''
203
+ && run.pendingDelegation === null
204
+ && run.receipts.length === 0
205
+ && run.commitAuthorization?.issuedAt === null
206
+ && run.commitAuthorization?.consumedAt === null
207
+ && run.completionEvidence == null
208
+ && !fs.existsSync(commitIntentPath(taskDir));
209
+ }
178
210
  function beginOrResumeOrchestration(taskRef, options = {}) {
179
211
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
180
212
  if (!resolved.ok)
@@ -190,7 +222,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
190
222
  return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
191
223
  }
192
224
  if (existing) {
193
- if (![1, 2].includes(existing.schemaVersion)
225
+ if (![1, 2, 3].includes(existing.schemaVersion)
194
226
  || !Array.isArray(existing.receipts)
195
227
  || !Object.prototype.hasOwnProperty.call(existing, 'pendingDelegation')) {
196
228
  return failed('ORCHESTRATION_STATE_INVALID', 'orchestration state has an invalid schema', resolved.taskId);
@@ -198,17 +230,20 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
198
230
  if (existing.status === 'completed' && existing.schemaVersion === 1) {
199
231
  return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
200
232
  }
201
- if (existing.schemaVersion === 2) {
233
+ if (existing.schemaVersion === 2 || existing.schemaVersion === 3) {
202
234
  const policyError = validateModelPolicy(isV2Policy(existing.modelPolicy) ? existing.modelPolicy : undefined);
203
235
  if (policyError
204
236
  || !existing.modelPolicySource
205
237
  || !isAgentClientId(existing.modelPolicySource.client)
206
238
  || !Array.isArray(existing.recoveryHistory)) {
207
- return failed('ORCHESTRATION_STATE_INVALID', 'schemaVersion 2 orchestration state is incomplete', resolved.taskId);
239
+ return failed('ORCHESTRATION_STATE_INVALID', `schemaVersion ${existing.schemaVersion} orchestration state is incomplete`, resolved.taskId);
208
240
  }
209
241
  if (existing.status === 'completed') {
210
242
  return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
211
243
  }
244
+ if (existing.schemaVersion === 2 && existing.status === 'running') {
245
+ return failed('ORCHESTRATION_STATE_INVALID', 'active schemaVersion 2 runs cannot be advanced by lifecycle protocol v3', resolved.taskId);
246
+ }
212
247
  if (existing.modelPolicySource.client !== options.client) {
213
248
  return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
214
249
  }
@@ -221,6 +256,39 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
221
256
  }
222
257
  }
223
258
  if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
259
+ if (existing.modelPolicySource.client === 'claude-code') {
260
+ if (!canRecoverClaudeCodeUnsupportedPause(existing, resolved.taskDir)) {
261
+ return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
262
+ }
263
+ const now = (options.now ?? (() => new Date().toISOString()))();
264
+ const previousPause = existing.pause;
265
+ const recovery = {
266
+ code: 'CLIENT_CAPABILITY_ENABLED_NO_MIGRATION',
267
+ recoveredAt: now,
268
+ previousSchemaVersion: 3,
269
+ previousStatus: 'paused',
270
+ previousPause,
271
+ client: 'claude-code',
272
+ guards: {
273
+ stepCount: 0,
274
+ nextStage: null,
275
+ baselineEmpty: true,
276
+ receiptCount: 0,
277
+ pendingDelegation: false,
278
+ commitAuthorizationUnused: true,
279
+ completionEvidenceAbsent: true,
280
+ commitIntentAbsent: true
281
+ },
282
+ resultingStatus: 'running'
283
+ };
284
+ const resumed = withUpdatedRun(existing, {
285
+ status: 'running',
286
+ pause: null,
287
+ recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
288
+ }, () => now);
289
+ saveRun(resolved.taskDir, resumed);
290
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
291
+ }
224
292
  if (!canRecoverCodexUnsupportedPause(existing, resolved.taskDir)) {
225
293
  return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
226
294
  }
@@ -246,6 +314,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
246
314
  resultingStatus: 'running'
247
315
  };
248
316
  const resumed = withUpdatedRun(existing, {
317
+ schemaVersion: 3,
249
318
  status: 'running',
250
319
  pause: null,
251
320
  recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
@@ -254,7 +323,42 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
254
323
  return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
255
324
  }
256
325
  if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
257
- const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
326
+ if (existing.schemaVersion === 2 && !canMigrateRecoverableV2Pause(existing, resolved.taskDir)) {
327
+ const paused = withUpdatedRun(existing, {
328
+ pause: {
329
+ code: 'ORCHESTRATION_SCHEMA_MIGRATION_REQUIRED',
330
+ message: 'schemaVersion 2 recoverable pause contains execution evidence and cannot migrate automatically',
331
+ recoverable: false
332
+ }
333
+ });
334
+ saveRun(resolved.taskDir, paused);
335
+ return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
336
+ }
337
+ const now = (options.now ?? (() => new Date().toISOString()))();
338
+ const recovery = existing.schemaVersion === 2 ? {
339
+ code: 'SCHEMA_V3_MIGRATED',
340
+ recoveredAt: now,
341
+ previousSchemaVersion: 2,
342
+ previousStatus: 'paused',
343
+ previousPause: existing.pause,
344
+ guards: {
345
+ stepCount: 0,
346
+ nextStage: null,
347
+ baselineEmpty: true,
348
+ receiptCount: 0,
349
+ pendingDelegation: false,
350
+ commitAuthorizationUnused: true,
351
+ completionEvidenceAbsent: true,
352
+ commitIntentAbsent: true
353
+ },
354
+ resultingStatus: 'running'
355
+ } : null;
356
+ const resumed = withUpdatedRun(existing, {
357
+ schemaVersion: 3,
358
+ status: 'running',
359
+ pause: null,
360
+ ...(recovery ? { recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery]) } : {})
361
+ }, () => now);
258
362
  saveRun(resolved.taskDir, resumed);
259
363
  return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
260
364
  }
@@ -328,7 +432,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
328
432
  };
329
433
  const recovered = Object.freeze({
330
434
  ...existing,
331
- schemaVersion: 2,
435
+ schemaVersion: 3,
332
436
  status: resultingStatus,
333
437
  modelPolicy: policy,
334
438
  modelPolicySource: source,
@@ -360,7 +464,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
360
464
  const now = (options.now ?? (() => new Date().toISOString()))();
361
465
  const source = { kind: sourceKind, client: options.client, resolvedAt: now };
362
466
  const run = {
363
- schemaVersion: 2,
467
+ schemaVersion: 3,
364
468
  taskId: resolved.taskId,
365
469
  runId: (options.id ?? randomUUID)(),
366
470
  status: 'running',
@@ -1025,6 +1129,9 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
1025
1129
  const run = readRun(resolved.taskDir);
1026
1130
  if (!run || run.status !== 'running')
1027
1131
  return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
1132
+ if (run.schemaVersion !== 3) {
1133
+ return failed('ORCHESTRATION_STATE_INVALID', 'delegation preparation requires orchestration schemaVersion 3', resolved.taskId);
1134
+ }
1028
1135
  if (!isV2Policy(run.modelPolicy))
1029
1136
  return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
1030
1137
  if (run.pendingDelegation)
@@ -1045,6 +1152,9 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
1045
1152
  if (!validModel(input.requestedReasoningEffort)) {
1046
1153
  return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
1047
1154
  }
1155
+ if (input.client === 'codex' && !input.lifecycleProvenance) {
1156
+ return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
1157
+ }
1048
1158
  const expectedPolicy = run.modelPolicy[next.role];
1049
1159
  if (input.requestedModel !== expectedPolicy.model) {
1050
1160
  return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
@@ -1074,8 +1184,9 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
1074
1184
  requestedModel: input.requestedModel,
1075
1185
  requestedReasoningEffort: input.requestedReasoningEffort,
1076
1186
  workspaceSnapshotScope: 'task',
1187
+ lifecycleProvenance: input.lifecycleProvenance ?? null,
1077
1188
  beforeFingerprint
1078
- }, { id: options.id, now: options.now });
1189
+ }, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
1079
1190
  const updated = withUpdatedRun(run, {
1080
1191
  nextStage: next.stage,
1081
1192
  pendingDelegation: receipt,
@@ -1099,6 +1210,39 @@ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
1099
1210
  throw error;
1100
1211
  }
1101
1212
  }
1213
+ function dispatchOrchestrationDelegationUnlocked(taskRef, options = {}) {
1214
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1215
+ if (!resolved.ok)
1216
+ return failed(resolved.code, resolved.message, resolved.taskId);
1217
+ const run = readRun(resolved.taskDir);
1218
+ if (!run?.pendingDelegation)
1219
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1220
+ if (run.schemaVersion !== 3 || run.status !== 'running') {
1221
+ return failed('ORCHESTRATION_STATE_INVALID', 'spawn dispatch requires a running schemaVersion 3 orchestration', resolved.taskId);
1222
+ }
1223
+ const result = dispatchDelegation(run.pendingDelegation, {
1224
+ now: options.now,
1225
+ monotonicNow: options.monotonicNow
1226
+ });
1227
+ if (!result.ok)
1228
+ return pauseOrchestration(taskRef, result.code, result.message, true, options);
1229
+ const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
1230
+ saveRun(resolved.taskDir, updated);
1231
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1232
+ }
1233
+ function dispatchOrchestrationDelegation(taskRef, options = {}) {
1234
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1235
+ if (!resolved.ok)
1236
+ return failed(resolved.code, resolved.message, resolved.taskId);
1237
+ try {
1238
+ return withTaskExecutionLock(resolved.repoRoot, '__repository__', 'task-orchestration.dispatch.repository', () => withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.dispatch.task', () => dispatchOrchestrationDelegationUnlocked(taskRef, options)));
1239
+ }
1240
+ catch (error) {
1241
+ if (error instanceof TaskExecutionLockError)
1242
+ return failed(error.code, error.message, resolved.taskId);
1243
+ throw error;
1244
+ }
1245
+ }
1102
1246
  function matchingDelegations(predicate, options) {
1103
1247
  const repoRoot = options.repoRoot ?? process.cwd();
1104
1248
  const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
@@ -1108,7 +1252,7 @@ function matchingDelegations(predicate, options) {
1108
1252
  .filter((entry) => entry.isDirectory())
1109
1253
  .flatMap((entry) => {
1110
1254
  const run = readRun(path.join(activeRoot, entry.name));
1111
- if (run?.status !== 'running' || !run.pendingDelegation)
1255
+ if (run?.schemaVersion !== 3 || run.status !== 'running' || !run.pendingDelegation)
1112
1256
  return [];
1113
1257
  const receipt = run.pendingDelegation;
1114
1258
  return predicate(receipt)
@@ -1124,6 +1268,15 @@ function uniqueMatchingDelegation(client, predicate, options) {
1124
1268
  return failed('ORCHESTRATION_DELEGATION_AMBIGUOUS', 'multiple lifecycle delegations match the native hook event');
1125
1269
  return matches[0];
1126
1270
  }
1271
+ function hasActivatableOrchestrationDelegation(client, childId, options = {}) {
1272
+ return matchingDelegations((receipt) => receipt.client === client && (receipt.status === 'prepared'
1273
+ || (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))), options).length > 0;
1274
+ }
1275
+ function hasSealableOrchestrationDelegation(client, childId, options = {}) {
1276
+ return matchingDelegations((receipt) => receipt.client === client
1277
+ && receipt.childId === childId
1278
+ && ['stage-completed', 'sealed'].includes(receipt.status), options).length > 0;
1279
+ }
1127
1280
  function activateMatchingOrchestrationDelegation(client, event, options = {}) {
1128
1281
  const role = managedDelegationRole(event.nativeAgent);
1129
1282
  if (!role)
@@ -1137,9 +1290,9 @@ function activateMatchingOrchestrationDelegation(client, event, options = {}) {
1137
1290
  return matched;
1138
1291
  const receipt = replay.run.pendingDelegation;
1139
1292
  const sameEvidence = receipt.parentId === event.parentId
1140
- && receipt.spawnMode === event.spawnMode
1141
- && receipt.actualModel === event.actualModel
1142
- && receipt.actualReasoningEffort === event.actualReasoningEffort
1293
+ && (receipt.spawnMode ?? null) === (event.spawnMode ?? null)
1294
+ && (receipt.actualModel ?? null) === foldBlankToNull(event.actualModel)
1295
+ && (receipt.actualReasoningEffort ?? null) === foldBlankToNull(event.actualReasoningEffort)
1143
1296
  && receipt.modelFallbackReason === (event.modelFallbackReason ?? null)
1144
1297
  && receipt.reasoningEffortFallbackReason === (event.reasoningEffortFallbackReason ?? null)
1145
1298
  && receipt.hostEvidence?.hookDefinitionHash === event.hostEvidence?.hookDefinitionHash;
@@ -1184,7 +1337,11 @@ function sealMatchingOrchestrationDelegation(client, event, options = {}) {
1184
1337
  childId: event.childId,
1185
1338
  exitCode: 0,
1186
1339
  afterFingerprint,
1187
- changedPaths
1340
+ changedPaths,
1341
+ actualModel: event.actualModel,
1342
+ actualReasoningEffort: event.actualReasoningEffort,
1343
+ modelFallbackReason: event.modelFallbackReason,
1344
+ reasoningEffortFallbackReason: event.reasoningEffortFallbackReason
1188
1345
  }, options);
1189
1346
  }
1190
1347
  catch (error) {
@@ -1217,7 +1374,7 @@ function sealMatchingOrchestrationDelegationWithHostEvidence(client, event, cons
1217
1374
  });
1218
1375
  const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
1219
1376
  const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
1220
- const validated = sealDelegation(receipt, baseEvent, { now: options.now });
1377
+ const validated = sealDelegation(receipt, baseEvent, { now: options.now, requireHostEvidence: false });
1221
1378
  if (!validated.ok) {
1222
1379
  return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
1223
1380
  }
@@ -1255,7 +1412,15 @@ function activateOrchestrationDelegation(taskRef, event, options = {}) {
1255
1412
  const run = readRun(resolved.taskDir);
1256
1413
  if (!run?.pendingDelegation)
1257
1414
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1258
- const result = activateDelegation(run.pendingDelegation, event, { now: options.now });
1415
+ if (run.schemaVersion !== 3)
1416
+ return failed('ORCHESTRATION_STATE_INVALID', 'active delegation requires schemaVersion 3', resolved.taskId);
1417
+ if (run.status !== 'running') {
1418
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1419
+ }
1420
+ const result = activateDelegation(run.pendingDelegation, event, {
1421
+ now: options.now,
1422
+ monotonicNow: options.monotonicNow
1423
+ });
1259
1424
  if (!result.ok) {
1260
1425
  if (result.code === 'DELEGATION_IGNORED')
1261
1426
  return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
@@ -1265,6 +1430,108 @@ function activateOrchestrationDelegation(taskRef, event, options = {}) {
1265
1430
  saveRun(resolved.taskDir, updated);
1266
1431
  return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1267
1432
  }
1433
+ async function awaitOrchestrationDelegationActivation(taskRef, event, options = {}) {
1434
+ const sleep = options.sleep
1435
+ ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
1436
+ while (true) {
1437
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1438
+ if (!resolved.ok)
1439
+ return failed(resolved.code, resolved.message, resolved.taskId);
1440
+ const run = readRun(resolved.taskDir);
1441
+ if (!run?.pendingDelegation) {
1442
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1443
+ }
1444
+ if (run.schemaVersion !== 3) {
1445
+ return failed('ORCHESTRATION_STATE_INVALID', 'activation barrier requires schemaVersion 3', resolved.taskId);
1446
+ }
1447
+ const receipt = run.pendingDelegation;
1448
+ const matches = receipt.stage === event.stage
1449
+ && receipt.round === event.round
1450
+ && receipt.artifact === event.artifact
1451
+ && receipt.role === event.role;
1452
+ if (!matches) {
1453
+ return pauseOrchestration(taskRef, 'ORCHESTRATION_PROVENANCE_MISMATCH', 'activation barrier identity does not match the pending delegation', true, options);
1454
+ }
1455
+ if (receipt.status === 'activated' || receipt.status === 'stage-completed') {
1456
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1457
+ }
1458
+ if (run.status !== 'running') {
1459
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1460
+ }
1461
+ if (receipt.status !== 'prepared') {
1462
+ return pauseOrchestration(taskRef, 'ORCHESTRATION_ACTIVATION_STATE_INVALID', `activation barrier found receipt status '${receipt.status}'`, true, options);
1463
+ }
1464
+ if (receipt.activationDeadlineAt == null
1465
+ || !Number.isFinite(Date.parse(receipt.activationDeadlineAt))) {
1466
+ return pauseOrchestration(taskRef, 'ORCHESTRATION_DELEGATION_NOT_DISPATCHED', 'activation barrier was entered before the native spawn dispatch boundary', true, options);
1467
+ }
1468
+ if (Date.parse((options.now ?? (() => new Date().toISOString()))()) >= Date.parse(receipt.activationDeadlineAt)) {
1469
+ return pauseOrchestration(taskRef, 'ORCHESTRATION_ACTIVATION_TIMEOUT', 'fresh child did not receive trusted activation evidence before the deadline', true, options);
1470
+ }
1471
+ await sleep(100);
1472
+ }
1473
+ }
1474
+ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
1475
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1476
+ if (!resolved.ok)
1477
+ return failed(resolved.code, resolved.message, resolved.taskId);
1478
+ try {
1479
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.recover-prepared', () => {
1480
+ const run = readRun(resolved.taskDir);
1481
+ const receipt = run?.pendingDelegation;
1482
+ if (!run || !receipt) {
1483
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1484
+ }
1485
+ if (receipt.status !== 'prepared') {
1486
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'only a never-activated prepared receipt can recover', resolved.taskId);
1487
+ }
1488
+ const wallNow = Date.parse((options.now ?? (() => new Date().toISOString()))());
1489
+ if (receipt.activationDeadlineAt != null
1490
+ && Number.isFinite(Date.parse(receipt.activationDeadlineAt))
1491
+ && wallNow <= Date.parse(receipt.activationDeadlineAt)) {
1492
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_EARLY', 'prepared receipt activation deadline has not elapsed', resolved.taskId);
1493
+ }
1494
+ const activeLifecycleEvidence = options.hasActiveLifecycleEvidence?.(receipt)
1495
+ ?? (receipt.client === 'codex'
1496
+ && receipt.lifecycleProvenance
1497
+ && hasActiveCodexLifecycleEvidence(resolveAgentRuntimeStoreRoot({ repoRoot: resolved.repoRoot, store: 'lifecycle' }), {
1498
+ nativeAgent: `agent-infra-lifecycle-${receipt.role}`,
1499
+ hookDefinitionHash: receipt.lifecycleProvenance.hookDefinitionHash
1500
+ }));
1501
+ if (activeLifecycleEvidence) {
1502
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE', 'active Codex lifecycle evidence blocks prepared receipt recovery', resolved.taskId);
1503
+ }
1504
+ if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
1505
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'active commit intent blocks prepared recovery', resolved.taskId);
1506
+ }
1507
+ const current = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
1508
+ gitRoot: gitRootFor(resolved.repoRoot, options),
1509
+ stateRoot: resolved.repoRoot,
1510
+ taskId: receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null
1511
+ });
1512
+ if (current !== receipt.beforeFingerprint) {
1513
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_DRIFT', 'workspace changed after delegation preparation', resolved.taskId);
1514
+ }
1515
+ const aborted = abortPreparedDelegation(receipt);
1516
+ if (!aborted.ok)
1517
+ return failed(aborted.code, aborted.message, resolved.taskId);
1518
+ const updated = withUpdatedRun(run, {
1519
+ status: 'running',
1520
+ pause: null,
1521
+ nextStage: null,
1522
+ pendingDelegation: null,
1523
+ receipts: Object.freeze([...run.receipts, aborted.receipt])
1524
+ }, options.now);
1525
+ saveRun(resolved.taskDir, updated);
1526
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1527
+ });
1528
+ }
1529
+ catch (error) {
1530
+ if (error instanceof TaskExecutionLockError)
1531
+ return failed(error.code, error.message, resolved.taskId);
1532
+ return failed('ORCHESTRATION_PREPARED_RECOVERY_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
1533
+ }
1534
+ }
1268
1535
  function completeOrchestrationStage(taskRef, event, options = {}) {
1269
1536
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1270
1537
  if (!resolved.ok)
@@ -1272,6 +1539,8 @@ function completeOrchestrationStage(taskRef, event, options = {}) {
1272
1539
  const run = readRun(resolved.taskDir);
1273
1540
  if (!run)
1274
1541
  return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
1542
+ if (run.schemaVersion !== 3)
1543
+ return failed('ORCHESTRATION_STATE_INVALID', 'stage completion requires schemaVersion 3', resolved.taskId);
1275
1544
  if (!run.pendingDelegation)
1276
1545
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
1277
1546
  const result = completeDelegationStage(run.pendingDelegation, event);
@@ -1294,6 +1563,8 @@ function inspectOrchestrationStage(taskRef, event, options = {}) {
1294
1563
  }
1295
1564
  if (!run)
1296
1565
  return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
1566
+ if (run.schemaVersion !== 3)
1567
+ return failed('ORCHESTRATION_STATE_INVALID', 'stage inspection requires schemaVersion 3', resolved.taskId);
1297
1568
  const receipt = run.pendingDelegation;
1298
1569
  if (!receipt
1299
1570
  || receipt.status !== 'activated'
@@ -1338,6 +1609,8 @@ function sealOrchestrationDelegation(taskRef, event, options = {}) {
1338
1609
  const run = readRun(resolved.taskDir);
1339
1610
  if (!run?.pendingDelegation)
1340
1611
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1612
+ if (run.schemaVersion !== 3)
1613
+ return failed('ORCHESTRATION_STATE_INVALID', 'delegation sealing requires schemaVersion 3', resolved.taskId);
1341
1614
  const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
1342
1615
  if (!result.ok)
1343
1616
  return pauseOrchestration(taskRef, result.code, result.message, true, options);
@@ -1352,6 +1625,8 @@ function advanceOrchestration(taskRef, options = {}) {
1352
1625
  const run = readRun(resolved.taskDir);
1353
1626
  if (!run?.pendingDelegation)
1354
1627
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1628
+ if (run.schemaVersion !== 3)
1629
+ return failed('ORCHESTRATION_STATE_INVALID', 'orchestration advance requires schemaVersion 3', resolved.taskId);
1355
1630
  const result = consumeDelegation(run.pendingDelegation, { now: options.now });
1356
1631
  if (!result.ok)
1357
1632
  return pauseOrchestration(taskRef, result.code, result.message, true, options);
@@ -1380,5 +1655,5 @@ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
1380
1655
  saveRun(resolved.taskDir, updated);
1381
1656
  return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1382
1657
  }
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 };
1658
+ 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
1659
  //# sourceMappingURL=orchestration.js.map
@@ -8,6 +8,7 @@ import { finalizeReviewSummaryContent } from './review-artifacts.js';
8
8
  import { resolveTaskRef } from './resolve-ref.js';
9
9
  import { validateLifecycleExecution } from './lifecycle-execution.js';
10
10
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
11
+ import { allowsManualOverride } from './guard-override.js';
11
12
  const STAGES = {
12
13
  analysis: { family: 'review-analysis', action: 'Review Analysis' },
13
14
  plan: { family: 'review-plan', action: 'Review Plan' },
@@ -60,7 +61,7 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
60
61
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
61
62
  if (!resolved.ok)
62
63
  return failed(request, resolved.code, resolved.message, resolved.taskId);
63
- if (resolved.state !== 'active') {
64
+ if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'review-finalization', 'TASK_STATE_MISMATCH')) {
64
65
  return failed(request, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
65
66
  }
66
67
  const parsedArtifact = parseArtifactName(request.artifact);
@@ -168,7 +169,7 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
168
169
  };
169
170
  }
170
171
  function finalizeReviewSummary(request, options = {}) {
171
- if (request.dryRun)
172
+ if (request.dryRun || options.lockAlreadyHeld)
172
173
  return finalizeReviewSummaryUnlocked(request, options);
173
174
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
174
175
  if (!resolved.ok)
@@ -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,