codex-orchestrator 2.0.11 → 2.0.13

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 (262) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +25 -51
  3. package/dist/src/index.d.ts +2 -8
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js +1 -4
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/v2/acceptance-proof.d.ts +46 -31
  8. package/dist/src/v2/acceptance-proof.d.ts.map +1 -1
  9. package/dist/src/v2/acceptance-proof.js +157 -195
  10. package/dist/src/v2/acceptance-proof.js.map +1 -1
  11. package/dist/src/v2/active-attempt.d.ts +94 -0
  12. package/dist/src/v2/active-attempt.d.ts.map +1 -0
  13. package/dist/src/v2/active-attempt.js +200 -0
  14. package/dist/src/v2/active-attempt.js.map +1 -0
  15. package/dist/src/v2/adapters/command.d.ts +6 -0
  16. package/dist/src/v2/adapters/command.d.ts.map +1 -1
  17. package/dist/src/v2/adapters/command.js +43 -2
  18. package/dist/src/v2/adapters/command.js.map +1 -1
  19. package/dist/src/v2/candidate.d.ts +15 -31
  20. package/dist/src/v2/candidate.d.ts.map +1 -1
  21. package/dist/src/v2/candidate.js +7 -29
  22. package/dist/src/v2/candidate.js.map +1 -1
  23. package/dist/src/v2/checked-change.d.ts +3 -2
  24. package/dist/src/v2/checked-change.d.ts.map +1 -1
  25. package/dist/src/v2/checked-change.js +4 -3
  26. package/dist/src/v2/checked-change.js.map +1 -1
  27. package/dist/src/v2/cli-contract.d.ts +1 -1
  28. package/dist/src/v2/cli-contract.d.ts.map +1 -1
  29. package/dist/src/v2/cli-contract.js +4 -6
  30. package/dist/src/v2/cli-contract.js.map +1 -1
  31. package/dist/src/v2/cli.d.ts +8 -0
  32. package/dist/src/v2/cli.d.ts.map +1 -1
  33. package/dist/src/v2/cli.js +13 -0
  34. package/dist/src/v2/cli.js.map +1 -1
  35. package/dist/src/v2/code-review-report.d.ts +10 -18
  36. package/dist/src/v2/code-review-report.d.ts.map +1 -1
  37. package/dist/src/v2/code-review-report.js +63 -60
  38. package/dist/src/v2/code-review-report.js.map +1 -1
  39. package/dist/src/v2/codex-process.d.ts +6 -2
  40. package/dist/src/v2/codex-process.d.ts.map +1 -1
  41. package/dist/src/v2/codex-process.js +25 -9
  42. package/dist/src/v2/codex-process.js.map +1 -1
  43. package/dist/src/v2/config.d.ts +0 -2
  44. package/dist/src/v2/config.d.ts.map +1 -1
  45. package/dist/src/v2/config.js +3 -6
  46. package/dist/src/v2/config.js.map +1 -1
  47. package/dist/src/v2/contained-report-operation.d.ts +41 -196
  48. package/dist/src/v2/contained-report-operation.d.ts.map +1 -1
  49. package/dist/src/v2/contained-report-operation.js +139 -466
  50. package/dist/src/v2/contained-report-operation.js.map +1 -1
  51. package/dist/src/v2/containment.d.ts +1 -0
  52. package/dist/src/v2/containment.d.ts.map +1 -1
  53. package/dist/src/v2/containment.js +12 -2
  54. package/dist/src/v2/containment.js.map +1 -1
  55. package/dist/src/v2/delivery-authority.d.ts +26 -0
  56. package/dist/src/v2/delivery-authority.d.ts.map +1 -0
  57. package/dist/src/v2/delivery-authority.js +44 -0
  58. package/dist/src/v2/delivery-authority.js.map +1 -0
  59. package/dist/src/v2/direct-delivery.d.ts +16 -36
  60. package/dist/src/v2/direct-delivery.d.ts.map +1 -1
  61. package/dist/src/v2/direct-delivery.js +135 -122
  62. package/dist/src/v2/direct-delivery.js.map +1 -1
  63. package/dist/src/v2/immutable-workflow-publisher.d.ts.map +1 -1
  64. package/dist/src/v2/immutable-workflow-publisher.js +3 -1
  65. package/dist/src/v2/immutable-workflow-publisher.js.map +1 -1
  66. package/dist/src/v2/implementation-report.d.ts +3 -1
  67. package/dist/src/v2/implementation-report.d.ts.map +1 -1
  68. package/dist/src/v2/implementation-report.js +28 -10
  69. package/dist/src/v2/implementation-report.js.map +1 -1
  70. package/dist/src/v2/implementation-reviewer.d.ts +41 -12
  71. package/dist/src/v2/implementation-reviewer.d.ts.map +1 -1
  72. package/dist/src/v2/implementation-reviewer.js +114 -42
  73. package/dist/src/v2/implementation-reviewer.js.map +1 -1
  74. package/dist/src/v2/pending-effect-settlement.d.ts +44 -0
  75. package/dist/src/v2/pending-effect-settlement.d.ts.map +1 -0
  76. package/dist/src/v2/pending-effect-settlement.js +69 -0
  77. package/dist/src/v2/pending-effect-settlement.js.map +1 -0
  78. package/dist/src/v2/process-identity.d.ts +45 -0
  79. package/dist/src/v2/process-identity.d.ts.map +1 -0
  80. package/dist/src/v2/process-identity.js +118 -0
  81. package/dist/src/v2/process-identity.js.map +1 -0
  82. package/dist/src/v2/proof-report.d.ts +2 -1
  83. package/dist/src/v2/proof-report.d.ts.map +1 -1
  84. package/dist/src/v2/proof-report.js +10 -4
  85. package/dist/src/v2/proof-report.js.map +1 -1
  86. package/dist/src/v2/review-feedback-coordinator.d.ts +1 -1
  87. package/dist/src/v2/review-feedback-coordinator.d.ts.map +1 -1
  88. package/dist/src/v2/review-feedback-coordinator.js +1 -1
  89. package/dist/src/v2/review-feedback-coordinator.js.map +1 -1
  90. package/dist/src/v2/review-feedback.d.ts +14 -20
  91. package/dist/src/v2/review-feedback.d.ts.map +1 -1
  92. package/dist/src/v2/review-feedback.js +45 -87
  93. package/dist/src/v2/review-feedback.js.map +1 -1
  94. package/dist/src/v2/run-issue.d.ts +129 -88
  95. package/dist/src/v2/run-issue.d.ts.map +1 -1
  96. package/dist/src/v2/run-issue.js +1965 -2381
  97. package/dist/src/v2/run-issue.js.map +1 -1
  98. package/dist/src/v2/run-state-projections.d.ts +84 -0
  99. package/dist/src/v2/run-state-projections.d.ts.map +1 -0
  100. package/dist/src/v2/run-state-projections.js +142 -0
  101. package/dist/src/v2/run-state-projections.js.map +1 -0
  102. package/dist/src/v2/run-store.d.ts +99 -81
  103. package/dist/src/v2/run-store.d.ts.map +1 -1
  104. package/dist/src/v2/run-store.js +245 -542
  105. package/dist/src/v2/run-store.js.map +1 -1
  106. package/dist/src/v2/runtime-assets.d.ts +3 -0
  107. package/dist/src/v2/runtime-assets.d.ts.map +1 -1
  108. package/dist/src/v2/runtime-assets.js +104 -0
  109. package/dist/src/v2/runtime-assets.js.map +1 -1
  110. package/dist/src/v2/runtime.d.ts +56 -44
  111. package/dist/src/v2/runtime.d.ts.map +1 -1
  112. package/dist/src/v2/runtime.js +383 -503
  113. package/dist/src/v2/runtime.js.map +1 -1
  114. package/dist/src/v2/setup.js +0 -2
  115. package/dist/src/v2/setup.js.map +1 -1
  116. package/dist/src/v2/validation-progression.d.ts +70 -0
  117. package/dist/src/v2/validation-progression.d.ts.map +1 -0
  118. package/dist/src/v2/validation-progression.js +247 -0
  119. package/dist/src/v2/validation-progression.js.map +1 -0
  120. package/dist/src/v2/workflow-assets.d.ts +9 -3
  121. package/dist/src/v2/workflow-assets.d.ts.map +1 -1
  122. package/dist/src/v2/workflow-assets.js +256 -43
  123. package/dist/src/v2/workflow-assets.js.map +1 -1
  124. package/internal-workflow/docs/agents/bug-workflow-routing.md +9 -7
  125. package/internal-workflow/docs/agents/coding-skill-routing.md +170 -120
  126. package/internal-workflow/docs/agents/tool-usage.md +23 -12
  127. package/internal-workflow/manifest.json +1 -1
  128. package/internal-workflow/operations/code-review/SKILL.md +34 -15
  129. package/internal-workflow/operations/implementation/SKILL.md +21 -16
  130. package/internal-workflow/profiles/implementer.toml +9 -0
  131. package/internal-workflow/profiles/review_coordinator.toml +9 -0
  132. package/internal-workflow/profiles/spec_reviewer.toml +9 -0
  133. package/internal-workflow/profiles/standards_reviewer.toml +9 -0
  134. package/internal-workflow/schemas/code-review-v1.json +1 -1
  135. package/internal-workflow/schemas/implementation-report-v1.json +1 -1
  136. package/internal-workflow/schemas/proof-report-v1.json +1 -1
  137. package/internal-workflow/skills/bug-root-cause-explainer/SKILL.md +114 -0
  138. package/internal-workflow/skills/bug-root-cause-explainer/agents/openai.yaml +7 -0
  139. package/internal-workflow/skills/bug-root-cause-explainer/evals/evals.json +18 -0
  140. package/internal-workflow/skills/code-review/SKILL.md +84 -306
  141. package/internal-workflow/skills/code-review/agents/openai.yaml +5 -3
  142. package/internal-workflow/skills/code-review/evals/evals.json +83 -0
  143. package/internal-workflow/skills/code-review/references/standards-smells.md +41 -0
  144. package/internal-workflow/skills/diagnosing-bugs/SKILL.md +69 -32
  145. package/internal-workflow/skills/diagnosing-bugs/agents/openai.yaml +2 -2
  146. package/internal-workflow/skills/diagnosing-bugs/evals/evals.json +63 -0
  147. package/internal-workflow/skills/grilling/SKILL.md +51 -0
  148. package/internal-workflow/skills/grilling/agents/openai.yaml +6 -0
  149. package/internal-workflow/skills/grilling/evals/evals.json +47 -0
  150. package/internal-workflow/skills/implement/SKILL.md +135 -0
  151. package/internal-workflow/skills/implement/agents/openai.yaml +6 -0
  152. package/internal-workflow/skills/implement/evals/evals.json +150 -0
  153. package/internal-workflow/skills/plan/SKILL.md +59 -0
  154. package/internal-workflow/skills/plan/agents/openai.yaml +6 -0
  155. package/internal-workflow/skills/plan/evals/evals.json +36 -0
  156. package/internal-workflow/skills/prototype/LOGIC.md +130 -0
  157. package/internal-workflow/skills/prototype/SKILL.md +69 -0
  158. package/internal-workflow/skills/prototype/UI.md +157 -0
  159. package/internal-workflow/skills/prototype/agents/openai.yaml +6 -0
  160. package/internal-workflow/skills/prototype/evals/evals.json +67 -0
  161. package/internal-workflow/skills/research/SKILL.md +110 -0
  162. package/internal-workflow/skills/research/agents/openai.yaml +6 -0
  163. package/internal-workflow/skills/research/evals/evals.json +49 -0
  164. package/internal-workflow/skills/tdd/SKILL.md +72 -67
  165. package/internal-workflow/skills/tdd/agents/openai.yaml +2 -2
  166. package/internal-workflow/skills/tdd/evals/evals.json +12 -0
  167. package/internal-workflow/skills/tdd/mocking.md +48 -1
  168. package/internal-workflow/skills/tdd/refactoring.md +3 -3
  169. package/internal-workflow/skills/tickets-orchestrator/SKILL.md +199 -0
  170. package/internal-workflow/skills/tickets-orchestrator/agents/openai.yaml +6 -0
  171. package/internal-workflow/skills/tickets-orchestrator/evals/evals.json +126 -0
  172. package/internal-workflow/skills/tickets-orchestrator/references/delegate-integrate.md +83 -0
  173. package/internal-workflow/skills/tickets-orchestrator/references/finish-delivery.md +69 -0
  174. package/internal-workflow/skills/tickets-orchestrator/references/stop-completion.md +63 -0
  175. package/internal-workflow/skills/to-spec/SKILL.md +133 -0
  176. package/internal-workflow/skills/to-spec/agents/openai.yaml +6 -0
  177. package/internal-workflow/skills/to-spec/evals/evals.json +24 -0
  178. package/internal-workflow/skills/to-tickets/SKILL.md +189 -0
  179. package/internal-workflow/skills/to-tickets/agents/openai.yaml +6 -0
  180. package/internal-workflow/skills/to-tickets/evals/evals.json +79 -0
  181. package/internal-workflow/skills/to-tickets/references/publishing-details.md +117 -0
  182. package/package.json +1 -1
  183. package/dist/src/v2/proof-store.d.ts +0 -54
  184. package/dist/src/v2/proof-store.d.ts.map +0 -1
  185. package/dist/src/v2/proof-store.js +0 -301
  186. package/dist/src/v2/proof-store.js.map +0 -1
  187. package/dist/src/v2/route-continuations.d.ts +0 -32
  188. package/dist/src/v2/route-continuations.d.ts.map +0 -1
  189. package/dist/src/v2/route-continuations.js +0 -2
  190. package/dist/src/v2/route-continuations.js.map +0 -1
  191. package/dist/src/v2/route-coordinator.d.ts +0 -72
  192. package/dist/src/v2/route-coordinator.d.ts.map +0 -1
  193. package/dist/src/v2/route-coordinator.js +0 -275
  194. package/dist/src/v2/route-coordinator.js.map +0 -1
  195. package/dist/src/v2/route-decision.d.ts +0 -120
  196. package/dist/src/v2/route-decision.d.ts.map +0 -1
  197. package/dist/src/v2/route-decision.js +0 -380
  198. package/dist/src/v2/route-decision.js.map +0 -1
  199. package/dist/src/v2/spec-coordinator.d.ts +0 -73
  200. package/dist/src/v2/spec-coordinator.d.ts.map +0 -1
  201. package/dist/src/v2/spec-coordinator.js +0 -126
  202. package/dist/src/v2/spec-coordinator.js.map +0 -1
  203. package/dist/src/v2/spec-delivery.d.ts +0 -112
  204. package/dist/src/v2/spec-delivery.d.ts.map +0 -1
  205. package/dist/src/v2/spec-delivery.js +0 -336
  206. package/dist/src/v2/spec-delivery.js.map +0 -1
  207. package/dist/src/v2/triage-route.d.ts +0 -68
  208. package/dist/src/v2/triage-route.d.ts.map +0 -1
  209. package/dist/src/v2/triage-route.js +0 -223
  210. package/dist/src/v2/triage-route.js.map +0 -1
  211. package/dist/src/v2/waiting-human-coordinator.d.ts +0 -49
  212. package/dist/src/v2/waiting-human-coordinator.d.ts.map +0 -1
  213. package/dist/src/v2/waiting-human-coordinator.js +0 -509
  214. package/dist/src/v2/waiting-human-coordinator.js.map +0 -1
  215. package/dist/src/v2/waiting-human.d.ts +0 -143
  216. package/dist/src/v2/waiting-human.d.ts.map +0 -1
  217. package/dist/src/v2/waiting-human.js +0 -408
  218. package/dist/src/v2/waiting-human.js.map +0 -1
  219. package/internal-workflow/docs/agents/contract-test-ledger.md +0 -71
  220. package/internal-workflow/docs/agents/review-gates.md +0 -42
  221. package/internal-workflow/docs/agents/review-protocol.md +0 -98
  222. package/internal-workflow/evals/coding-skill-evals.json +0 -373
  223. package/internal-workflow/operations/ambiguity-review/SKILL.md +0 -5
  224. package/internal-workflow/operations/qualification-repair/SKILL.md +0 -17
  225. package/internal-workflow/operations/spec-author/SKILL.md +0 -12
  226. package/internal-workflow/operations/spec-review/SKILL.md +0 -12
  227. package/internal-workflow/operations/triage/SKILL.md +0 -12
  228. package/internal-workflow/profiles/analyst_deep.toml +0 -9
  229. package/internal-workflow/profiles/implementer_standard.toml +0 -9
  230. package/internal-workflow/profiles/proof_agent.toml +0 -8
  231. package/internal-workflow/profiles/reviewer_deep.toml +0 -9
  232. package/internal-workflow/profiles/reviewer_standard.toml +0 -9
  233. package/internal-workflow/schemas/ambiguity-review-v1.json +0 -1
  234. package/internal-workflow/schemas/spec-author-v1.json +0 -1
  235. package/internal-workflow/schemas/spec-review-v1.json +0 -30
  236. package/internal-workflow/schemas/triage-route-v1.json +0 -1
  237. package/internal-workflow/skills/agent-auto/SKILL.md +0 -19
  238. package/internal-workflow/skills/agent-auto/agents/openai.yaml +0 -6
  239. package/internal-workflow/skills/code-debugger/SKILL.md +0 -122
  240. package/internal-workflow/skills/code-debugger/agents/openai.yaml +0 -7
  241. package/internal-workflow/skills/code-review/references/bug-classes.md +0 -56
  242. package/internal-workflow/skills/code-review/references/cleanup-lens.md +0 -52
  243. package/internal-workflow/skills/code-review/references/framework-lenses.md +0 -34
  244. package/internal-workflow/skills/code-review/references/targeted-recipes.md +0 -49
  245. package/internal-workflow/skills/implementation-spec-maker/SKILL.md +0 -107
  246. package/internal-workflow/skills/implementation-spec-maker/agents/openai.yaml +0 -6
  247. package/internal-workflow/skills/implementation-spec-maker/references/source-modes.md +0 -32
  248. package/internal-workflow/skills/implementation-spec-maker/references/spec-template.md +0 -146
  249. package/internal-workflow/skills/implementation-spec-review/SKILL.md +0 -131
  250. package/internal-workflow/skills/implementation-spec-review/agents/openai.yaml +0 -6
  251. package/internal-workflow/skills/implementation-spec-review/evals/evals.json +0 -78
  252. package/internal-workflow/skills/implementation-spec-review/references/review-loop.md +0 -121
  253. package/internal-workflow/skills/small-task-implementer/SKILL.md +0 -112
  254. package/internal-workflow/skills/small-task-implementer/agents/openai.yaml +0 -6
  255. package/internal-workflow/skills/spec-implementer/SKILL.md +0 -133
  256. package/internal-workflow/skills/spec-implementer/agents/openai.yaml +0 -6
  257. package/internal-workflow/skills/spec-implementer/evals/evals.json +0 -30
  258. package/internal-workflow/skills/spec-implementer/references/review-loop.md +0 -100
  259. package/internal-workflow/skills/triage/AGENT-BRIEF.md +0 -192
  260. package/internal-workflow/skills/triage/OUT-OF-SCOPE.md +0 -101
  261. package/internal-workflow/skills/triage/SKILL.md +0 -134
  262. package/internal-workflow/skills/triage/agents/openai.yaml +0 -6
@@ -1,208 +1,104 @@
1
- import { validateDurableMutableInvocation, validateDurableReportInvocation, } from './contained-report-operation.js';
2
- import { projectTerminalDirectReview, validateDirectReview } from './direct-delivery.js';
3
- import { validateSpecDelivery } from './spec-delivery.js';
4
- import { validateRouteExecution, validateRouteReceipt, validateRouteStateInvariant, } from './route-decision.js';
5
- import { validateWaitingHumanExecution } from './waiting-human.js';
1
+ import { validateActiveAttempt } from './active-attempt.js';
2
+ import { validateDirectReview } from './direct-delivery.js';
3
+ import { validateDeliveryAuthority } from './delivery-authority.js';
4
+ import { constants } from 'node:fs';
6
5
  import { posix } from 'node:path';
7
- import { readFile } from 'node:fs/promises';
6
+ import { open } from 'node:fs/promises';
8
7
  import { AtomicStateFile } from './atomic-store.js';
9
- import { writeDurableAtomicFile } from './adapters/durable-atomic-file.js';
10
8
  import { canonicalJson, sha256 } from './containment.js';
11
- import { validateCandidateBinding, validateCandidateExecutionLease } from './candidate.js';
12
- import { blockReviewFeedback, createReviewFeedbackBootstrap, validateReviewFeedbackExecution, } from './review-feedback.js';
9
+ import { validateCandidateBinding, validateCandidateMaterialization } from './candidate.js';
10
+ import { validateReviewFeedbackRunData, } from './review-feedback.js';
13
11
  const SHA256_PATTERN = /^[0-9a-f]{64}$/;
14
12
  const GIT_SHA_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
15
13
  const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
14
+ export function createPendingEffect(effect) {
15
+ return { ...structuredClone(effect), effectId: sha256(canonicalJson(effect)) };
16
+ }
16
17
  export class WorkflowGenerationUnrecoverableError extends Error {
17
18
  constructor() {
18
19
  super('workflow-generation-unrecoverable');
19
20
  this.name = 'WorkflowGenerationUnrecoverableError';
20
21
  }
21
22
  }
22
- export class RouteInitializationUnrecoverableError extends Error {
23
+ export class IssueInitializationUnrecoverableError extends Error {
23
24
  constructor() {
24
- super('route-initialization-unrecoverable');
25
- this.name = 'RouteInitializationUnrecoverableError';
25
+ super('issue-initialization-unrecoverable');
26
+ this.name = 'IssueInitializationUnrecoverableError';
26
27
  }
27
28
  }
28
29
  export class FileRunRecordWriter {
29
30
  file;
30
- backupPath;
31
- migrationMetadataPath;
32
- reportLifecycleBackupPath;
33
- mutableLifecycleBackupPath;
34
- now;
31
+ maxBytes;
35
32
  constructor(path, options = {}) {
36
33
  this.file = new AtomicStateFile(path, validateRunStateFile, options);
37
- this.backupPath = `${path}.pre-candidate-v3`;
38
- this.migrationMetadataPath = `${this.backupPath}.metadata.json`;
39
- this.reportLifecycleBackupPath = `${path}.pre-report-lifecycle-v1`;
40
- this.mutableLifecycleBackupPath = `${path}.pre-mutable-lifecycle-v1`;
41
- this.now = options.now ?? (() => new Date().toISOString());
42
- }
43
- async read() {
34
+ this.maxBytes = options.maxBytes ?? 1024 * 1024;
35
+ }
36
+ async inspect() {
37
+ const bytes = await readOptionalStateFile(this.file.path);
38
+ if (!bytes)
39
+ return { status: 'absent', rawSha256: null };
40
+ const rawSha256 = sha256(bytes);
41
+ if (bytes.length > this.maxBytes)
42
+ return { status: 'unsupported', rawSha256 };
44
43
  try {
45
- return await this.file.read() ?? emptyRunState();
44
+ return { status: 'supported', rawSha256, state: parseRawState(bytes) };
46
45
  }
47
- catch (error) {
48
- return this.migrateLegacyReportLifecycle(error);
46
+ catch {
47
+ return { status: 'unsupported', rawSha256 };
49
48
  }
50
49
  }
50
+ async read() {
51
+ const inspection = await this.inspect();
52
+ if (inspection.status === 'absent')
53
+ return emptyRunState();
54
+ if (inspection.status === 'supported')
55
+ return inspection.state;
56
+ throw new Error('run state schema is unsupported');
57
+ }
51
58
  async compareAndSwap(expectedGeneration, next) {
52
59
  validateRunStateBody(next);
53
60
  const candidate = { ...structuredClone(next), generation: expectedGeneration + 1 };
54
- return this.file.compareAndSwapWithRaw(expectedGeneration, candidate, async (priorBytes) => {
55
- if (!priorBytes)
56
- return;
57
- const raw = parseRawState(priorBytes);
58
- if (raw.version === 3 && next.version !== 3)
59
- throw new Error('run state V3 downgrade requires explicit rollback');
60
- if (raw.version === 2 && next.version === 3)
61
- await this.ensureCandidateMigrationBackup(priorBytes, raw.generation);
62
- });
63
- }
64
- async markPublicationEffectPossible() {
65
- await this.file.withExclusiveRaw(async () => {
66
- const existing = await readMigrationMetadata(this.migrationMetadataPath);
67
- if (!existing?.publicationEffectPossible) {
68
- const now = this.now();
69
- await writeDurableAtomicFile(this.migrationMetadataPath, `${canonicalJson({
70
- version: 1,
71
- sourceGeneration: existing?.sourceGeneration ?? null,
72
- sourceBytesSha256: existing?.sourceBytesSha256 ?? null,
73
- publicationEffectPossible: true,
74
- createdAt: existing?.createdAt ?? now,
75
- updatedAt: now,
76
- })}\n`);
77
- }
78
- return { result: undefined };
79
- });
80
- }
81
- async rollbackCandidateMigration(input) {
82
- return this.file.withExclusiveRaw(async (_currentBytes, current) => {
83
- const metadata = await readMigrationMetadata(this.migrationMetadataPath);
84
- if (!metadata || metadata.sourceGeneration === null || metadata.sourceBytesSha256 === null)
85
- throw new Error('candidate V3 rollback backup is unavailable');
86
- if (metadata.publicationEffectPossible)
87
- throw new Error('candidate V3 rollback is forbidden after publication boundary');
88
- if (!current || current.version !== 3)
89
- throw new Error('candidate V3 rollback requires active V3 state');
90
- await input.assertNoActiveProcesses();
91
- await input.cleanupCandidateState(structuredClone(current));
92
- const backup = await readOptionalFile(this.backupPath);
93
- if (!backup || sha256(backup) !== metadata.sourceBytesSha256)
94
- throw new Error('candidate V3 rollback backup identity is invalid');
95
- const restored = parseRawState(backup);
96
- if (restored.version !== 2 || restored.generation !== metadata.sourceGeneration)
97
- throw new Error('candidate V3 rollback backup generation is invalid');
98
- return { result: restored, replacementBytes: backup };
99
- });
100
- }
101
- async ensureCandidateMigrationBackup(rawV2Bytes, generation) {
102
- const sourceBytesSha256 = sha256(rawV2Bytes);
103
- const existing = await readMigrationMetadata(this.migrationMetadataPath);
104
- const backup = await readOptionalFile(this.backupPath);
105
- if (existing?.publicationEffectPossible)
106
- throw new Error('candidate V3 migration backup cannot change after publication boundary');
107
- if (existing?.sourceGeneration === generation && existing.sourceBytesSha256 === sourceBytesSha256
108
- && backup?.equals(rawV2Bytes))
109
- return;
110
- await writeDurableAtomicFile(this.backupPath, rawV2Bytes);
111
- const now = this.now();
112
- await writeDurableAtomicFile(this.migrationMetadataPath, `${canonicalJson({
113
- version: 1,
114
- sourceGeneration: generation,
115
- sourceBytesSha256,
116
- publicationEffectPossible: false,
117
- createdAt: now,
118
- updatedAt: now,
119
- })}\n`);
120
- }
121
- async migrateLegacyReportLifecycle(originalError) {
122
- return this.file.withExclusiveUnparsedRaw(async (priorBytes) => {
123
- if (!priorBytes)
124
- return { result: emptyRunState() };
125
- let raw;
126
- try {
127
- raw = JSON.parse(priorBytes.toString('utf8'));
128
- }
129
- catch {
130
- throw originalError;
131
- }
132
- try {
133
- return { result: validateRunStateFile(raw) };
134
- }
135
- catch {
136
- const mutable = hasLegacyMutableLifecycle(raw);
137
- let backupPath = mutable ? this.mutableLifecycleBackupPath : this.reportLifecycleBackupPath;
138
- if (!mutable) {
139
- const priorReportBackup = await readOptionalFile(backupPath);
140
- if (priorReportBackup && !priorReportBackup.equals(priorBytes)) {
141
- const generation = raw.generation;
142
- backupPath = `${backupPath}.g${String(generation)}-${sha256(priorBytes).slice(0, 16)}`;
143
- }
144
- }
145
- const migrated = canonicalizeLegacyReportLifecycle(raw, backupPath);
146
- if (!migrated)
147
- throw originalError;
148
- const existingBackup = await readOptionalFile(backupPath);
149
- if (existingBackup && !existingBackup.equals(priorBytes))
150
- throw new Error('report lifecycle migration backup conflicts with current source bytes');
151
- if (!existingBackup)
152
- await writeDurableAtomicFile(backupPath, priorBytes);
153
- return { result: migrated, replacementBytes: Buffer.from(`${canonicalJson(migrated)}\n`) };
154
- }
155
- });
61
+ return this.file.compareAndSwap(expectedGeneration, candidate);
156
62
  }
157
63
  }
158
64
  export class InMemoryRunRecordWriter {
159
- state = emptyRunState();
65
+ state;
66
+ async inspect() {
67
+ if (!this.state)
68
+ return { status: 'absent', rawSha256: null };
69
+ const state = structuredClone(this.state);
70
+ return {
71
+ status: 'supported',
72
+ rawSha256: sha256(`${canonicalJson(state)}\n`),
73
+ state,
74
+ };
75
+ }
160
76
  async read() {
161
- return structuredClone(this.state);
77
+ return structuredClone(this.state ?? emptyRunState());
162
78
  }
163
79
  async compareAndSwap(expectedGeneration, next) {
164
- if (this.state.generation !== expectedGeneration)
80
+ if ((this.state?.generation ?? 0) !== expectedGeneration)
165
81
  throw new Error('run state generation conflict');
166
- if (this.state.version === 3 && next.version !== 3)
167
- throw new Error('run state V3 downgrade requires explicit rollback');
82
+ validateRunStateBody(next);
168
83
  const value = validateRunStateFile({ ...structuredClone(next), generation: expectedGeneration + 1 });
169
84
  this.state = value;
170
85
  return structuredClone(value);
171
86
  }
172
- async markPublicationEffectPossible() { }
173
87
  }
174
88
  export function validateRunStateFile(value) {
175
- assertExactObject(value, ['schema', 'version', 'generation', 'runs'], 'run state');
176
- if (value.schema !== 'codex-orchestrator.agent-auto-state' || ![1, 2, 3].includes(value.version))
177
- throw new Error('run state schema/version is invalid');
89
+ assertExactObject(value, ['schema', 'generation', 'runs'], 'run state');
90
+ if (value.schema !== 'codex-orchestrator.run-state')
91
+ throw new Error('run state schema is invalid');
178
92
  if (!Number.isSafeInteger(value.generation) || value.generation <= 0)
179
93
  throw new Error('run state generation is invalid');
180
94
  validateRuns(value.runs);
181
- if (value.version === 1) {
182
- for (const run of value.runs) {
183
- if (hasOwn(run, 'reviewFeedback'))
184
- throw new Error('V1 run state cannot contain review feedback');
185
- }
186
- return {
187
- schema: 'codex-orchestrator.agent-auto-state',
188
- version: 2,
189
- generation: value.generation,
190
- runs: value.runs.map((run) => run.lifecycle === 'review-ready'
191
- ? { ...structuredClone(run), reviewFeedback: createReviewFeedbackBootstrap() }
192
- : structuredClone(run)),
193
- };
194
- }
195
- if (value.version === 2)
196
- assertNoCandidateV3Fields(value.runs);
197
95
  return value;
198
96
  }
199
97
  function validateRunStateBody(value) {
200
- assertExactObject(value, ['schema', 'version', 'runs'], 'run state body');
201
- if (value.schema !== 'codex-orchestrator.agent-auto-state' || (value.version !== 2 && value.version !== 3))
202
- throw new Error('run state schema/version is invalid');
98
+ assertExactObject(value, ['schema', 'runs'], 'run state body');
99
+ if (value.schema !== 'codex-orchestrator.run-state')
100
+ throw new Error('run state schema is invalid');
203
101
  validateRuns(value.runs);
204
- if (value.version === 2)
205
- assertNoCandidateV3Fields(value.runs);
206
102
  }
207
103
  function validateRuns(value) {
208
104
  if (!Array.isArray(value) || value.length > 256)
@@ -217,29 +113,24 @@ function validateRuns(value) {
217
113
  }
218
114
  function validateRunRecord(value, field) {
219
115
  const optional = [
116
+ 'activeAttempt',
220
117
  'checkedChangeSha256',
221
118
  'proofId',
119
+ 'proofExecution',
222
120
  'proofReceipt',
223
- 'intent',
121
+ 'pendingEffect',
224
122
  'outcomeEvidenceId',
225
123
  'terminalOutcome',
226
- 'routeExecution',
227
- 'routeReceipt',
228
- 'waitingHuman',
124
+ 'deliveryAuthority',
229
125
  'directReview',
230
- 'specDelivery',
231
126
  'reviewFeedback',
232
- 'checkQualification',
233
- 'baselineChecks',
234
127
  'changeBindingVersion',
235
128
  'candidateBinding',
236
- 'executionLease',
237
- 'reportInvocation',
238
- 'mutableInvocation',
129
+ 'candidateMaterialization',
239
130
  ].filter((key) => hasOwn(value, key));
240
131
  assertExactObject(value, [
241
132
  'runId', 'issueNumber', 'canonicalRepository', 'baseSha', 'branchName', 'worktreePath', 'lifecycle', 'cycle',
242
- 'reportRepairs', 'issueSnapshot', 'frozenCriteria', 'reworkFindings',
133
+ 'reportRepairs', 'transportRetries', 'issueSnapshot', 'frozenCriteria', 'reworkFindings',
243
134
  'packageVersion', 'workflowGeneration', 'skillHashes', 'checks', 'createdAt', 'updatedAt', ...optional,
244
135
  ], field);
245
136
  if (typeof value.runId !== 'string' || !UUID_V4_PATTERN.test(value.runId))
@@ -255,10 +146,12 @@ function validateRunRecord(value, field) {
255
146
  }
256
147
  if (!isLifecycle(value.lifecycle))
257
148
  throw new Error(`${field}.lifecycle is invalid`);
258
- if (!Number.isSafeInteger(value.cycle) || value.cycle < 1 || value.cycle > 5)
149
+ if (!Number.isSafeInteger(value.cycle) || value.cycle < 1)
259
150
  throw new Error(`${field}.cycle is invalid`);
260
- if (value.reportRepairs !== 0 && value.reportRepairs !== 1)
151
+ if (!Number.isSafeInteger(value.reportRepairs) || value.reportRepairs < 0)
261
152
  throw new Error(`${field}.reportRepairs is invalid`);
153
+ if (!Number.isSafeInteger(value.transportRetries) || value.transportRetries < 0)
154
+ throw new Error(`${field}.transportRetries is invalid`);
262
155
  validateIssueSnapshot(value.issueSnapshot, `${field}.issueSnapshot`);
263
156
  validateFrozenCriteria(value.frozenCriteria, `${field}.frozenCriteria`);
264
157
  validateStringList(value.reworkFindings, `${field}.reworkFindings`);
@@ -268,114 +161,91 @@ function validateRunRecord(value, field) {
268
161
  if (workflowGeneration.packageVersion !== value.packageVersion) {
269
162
  throw new Error(`${field}.workflowGeneration package version mismatch`);
270
163
  }
271
- const routeGenerationHash = workflowGeneration.generationHash;
272
- if (hasOwn(value, 'routeExecution'))
273
- validateRouteExecution(value.routeExecution, routeGenerationHash);
274
- if (hasOwn(value, 'routeReceipt'))
275
- validateRouteReceipt(value.routeReceipt, routeGenerationHash);
164
+ if (hasOwn(value, 'deliveryAuthority')) {
165
+ validateDeliveryAuthority(value.deliveryAuthority, {
166
+ issueNumber: value.issueSnapshot.number,
167
+ issueUrl: value.issueSnapshot.url,
168
+ title: value.issueSnapshot.title,
169
+ body: value.issueSnapshot.body,
170
+ authorizationLabel: value.deliveryAuthority.authorizationLabel,
171
+ });
172
+ }
173
+ if (['implementing', 'checking', 'proving', 'reviewing', 'publishing', 'review-ready'].includes(value.lifecycle)
174
+ && !hasOwn(value, 'deliveryAuthority')) {
175
+ throw new Error(`${field}.deliveryAuthority is required for delivery progression`);
176
+ }
276
177
  validateStringShaRecord(value.skillHashes, `${field}.skillHashes`);
277
- if (hasOwn(value, 'baselineChecks'))
278
- validateChecks(value.baselineChecks, `${field}.baselineChecks`, false);
279
178
  validateChecks(value.checks, `${field}.checks`);
280
- let reportInvocation;
281
- if (hasOwn(value, 'reportInvocation')) {
282
- const invocation = validateDurableReportInvocation(value.reportInvocation);
283
- reportInvocation = invocation;
284
- const expectedLifecycle = invocation.operation === 'triage' || invocation.operation === 'ambiguity-review'
285
- ? 'triaging'
286
- : invocation.operation === 'code-review' ? 'implementing' : 'spec-authoring';
287
- if (value.lifecycle !== expectedLifecycle)
288
- throw new Error(`${field}.reportInvocation lifecycle is invalid`);
289
- }
290
- if (hasOwn(value, 'mutableInvocation')) {
291
- const invocation = validateDurableMutableInvocation(value.mutableInvocation);
292
- if (value.lifecycle !== 'implementing')
293
- throw new Error(`${field}.mutableInvocation lifecycle is invalid`);
294
- if (invocation.worktreePath !== value.worktreePath || invocation.generationHash !== routeGenerationHash) {
295
- throw new Error(`${field}.mutableInvocation binding is invalid`);
296
- }
297
- if (invocation.operation === 'qualification-repair' && !hasOwn(value, 'checkQualification')) {
298
- throw new Error(`${field}.qualification mutableInvocation requires check qualification`);
299
- }
300
- const feedbackActive = hasOwn(value, 'reviewFeedback')
301
- && value.reviewFeedback.activeBatch !== null;
302
- if ((invocation.operation === 'review-feedback-implementation') !== feedbackActive) {
303
- throw new Error(`${field}.mutableInvocation feedback binding is invalid`);
304
- }
179
+ if (hasOwn(value, 'activeAttempt')) {
180
+ const attempt = validateActiveAttempt(value.activeAttempt);
181
+ if (attempt.runId !== value.runId)
182
+ throw new Error(`${field}.activeAttempt run identity is invalid`);
305
183
  }
306
184
  if (hasOwn(value, 'checkedChangeSha256'))
307
185
  assertSha256(value.checkedChangeSha256, `${field}.checkedChangeSha256`);
308
186
  if (hasOwn(value, 'proofId'))
309
187
  assertNonEmptyString(value.proofId, `${field}.proofId`);
188
+ if (hasOwn(value, 'proofExecution'))
189
+ validateProofExecution(value.proofExecution, `${field}.proofExecution`);
310
190
  if (hasOwn(value, 'proofReceipt'))
311
191
  validateReceipt(value.proofReceipt, `${field}.proofReceipt`);
312
- if (hasOwn(value, 'intent'))
313
- validateIntent(value.intent, `${field}.intent`);
192
+ if (hasOwn(value, 'pendingEffect'))
193
+ validatePendingEffect(value.pendingEffect, `${field}.pendingEffect`);
314
194
  if (hasOwn(value, 'outcomeEvidenceId'))
315
195
  assertNonEmptyString(value.outcomeEvidenceId, `${field}.outcomeEvidenceId`);
316
196
  if (hasOwn(value, 'terminalOutcome'))
317
197
  validateTerminalOutcome(value.terminalOutcome, `${field}.terminalOutcome`);
318
- if (hasOwn(value, 'waitingHuman')) {
319
- if (!routeGenerationHash)
320
- throw new WorkflowGenerationUnrecoverableError();
321
- validateWaitingHumanExecution(value.waitingHuman, {
322
- runId: value.runId,
323
- lifecycle: value.lifecycle,
324
- workflowGenerationHash: routeGenerationHash,
325
- routeReceipt: hasOwn(value, 'routeReceipt') ? value.routeReceipt : undefined,
326
- terminalOutcome: hasOwn(value, 'terminalOutcome') ? value.terminalOutcome : undefined,
327
- });
328
- }
329
198
  if (hasOwn(value, 'directReview')) {
330
- if (!hasOwn(value, 'routeReceipt') || value.routeReceipt.route !== 'direct') {
331
- throw new Error(`${field}.directReview requires a direct route`);
332
- }
199
+ if (!hasOwn(value, 'deliveryAuthority'))
200
+ throw new Error(`${field}.directReview requires delivery authority`);
333
201
  validateDirectReview(value.directReview, {
334
202
  lifecycle: value.lifecycle,
335
203
  ...(hasOwn(value, 'terminalOutcome') ? { terminalOutcome: directTerminalOutcome(value.terminalOutcome) } : {}),
204
+ ...(hasOwn(value, 'terminalOutcome') && value.terminalOutcome.status === 'internal-error'
205
+ ? { terminalCode: value.terminalOutcome.code }
206
+ : {}),
336
207
  });
337
208
  }
338
- if (hasOwn(value, 'specDelivery')) {
339
- if (!hasOwn(value, 'routeReceipt') || value.routeReceipt.route !== 'spec-required') {
340
- throw new Error(`${field}.specDelivery requires a spec-required route`);
341
- }
342
- const spec = validateSpecDelivery(value.specDelivery);
343
- if (spec.issueNumber !== value.issueNumber || spec.runId !== value.runId
344
- || spec.workflowGenerationSha256 !== routeGenerationHash) {
345
- throw new Error(`${field}.specDelivery identity binding is invalid`);
346
- }
347
- }
348
209
  if (hasOwn(value, 'reviewFeedback')) {
349
- validateReviewFeedbackExecution(value.reviewFeedback);
210
+ validateReviewFeedbackRunData(value.reviewFeedback);
350
211
  validateReviewFeedbackRunInvariant(value, field);
351
212
  }
352
213
  if (hasOwn(value, 'changeBindingVersion') && value.changeBindingVersion !== 2)
353
214
  throw new Error(`${field}.changeBindingVersion is invalid`);
354
215
  if (hasOwn(value, 'candidateBinding'))
355
216
  validateCandidateBinding(value.candidateBinding, `${field}.candidateBinding`, value.runId);
356
- if (hasOwn(value, 'executionLease'))
357
- validateCandidateExecutionLease(value.executionLease, `${field}.executionLease`);
217
+ if (hasOwn(value, 'candidateMaterialization'))
218
+ validateCandidateMaterialization(value.candidateMaterialization, `${field}.candidateMaterialization`);
358
219
  if (hasOwn(value, 'candidateBinding') !== hasOwn(value, 'changeBindingVersion'))
359
220
  throw new Error(`${field} candidate binding version is incomplete`);
360
- if (hasOwn(value, 'executionLease')) {
221
+ if (hasOwn(value, 'candidateMaterialization')) {
361
222
  if (!hasOwn(value, 'candidateBinding'))
362
- throw new Error(`${field}.executionLease requires candidate binding`);
223
+ throw new Error(`${field}.candidateMaterialization requires candidate binding`);
363
224
  const binding = value.candidateBinding;
364
- const lease = value.executionLease;
365
- if (lease.bindingId !== binding.bindingId || lease.candidateCommitSha !== binding.candidateCommitSha)
366
- throw new Error(`${field}.executionLease binding is invalid`);
225
+ const materialization = value.candidateMaterialization;
226
+ if (materialization.bindingId !== binding.bindingId || materialization.candidateCommitSha !== binding.candidateCommitSha) {
227
+ throw new Error(`${field}.candidateMaterialization binding is invalid`);
228
+ }
229
+ }
230
+ if (hasOwn(value, 'pendingEffect') && hasOwn(value.pendingEffect, 'candidateRef')) {
231
+ if (!hasOwn(value, 'candidateBinding'))
232
+ throw new Error(`${field}.pendingEffect candidate ref requires candidate binding`);
233
+ const binding = value.candidateBinding;
234
+ const pendingEffect = value.pendingEffect;
235
+ if (pendingEffect.candidateRef !== binding.candidateRef || pendingEffect.treeSha !== binding.candidateTreeSha) {
236
+ throw new Error(`${field}.pendingEffect candidate binding is invalid`);
237
+ }
367
238
  }
368
- if (hasOwn(value, 'intent') && hasOwn(value.intent, 'candidateRef')) {
239
+ if (value.pendingEffect?.kind === 'candidate-pin-release') {
369
240
  if (!hasOwn(value, 'candidateBinding'))
370
- throw new Error(`${field}.intent candidate ref requires candidate binding`);
241
+ throw new Error(`${field}.candidate pin cleanup requires candidate binding`);
371
242
  const binding = value.candidateBinding;
372
- const intent = value.intent;
373
- if (intent.candidateRef !== binding.candidateRef || intent.treeSha !== binding.candidateTreeSha) {
374
- throw new Error(`${field}.intent candidate binding is invalid`);
243
+ const pendingEffect = value.pendingEffect;
244
+ if (pendingEffect.bindingId !== binding.bindingId
245
+ || pendingEffect.expectedPinnedCommitSha !== binding.candidateCommitSha) {
246
+ throw new Error(`${field}.candidate pin cleanup binding is invalid`);
375
247
  }
376
248
  }
377
- if (hasOwn(value, 'checkQualification'))
378
- validateCheckQualification(value.checkQualification, `${field}.checkQualification`);
379
249
  assertTimestamp(value.createdAt, `${field}.createdAt`);
380
250
  assertTimestamp(value.updatedAt, `${field}.updatedAt`);
381
251
  const terminal = ['review-ready', 'blocked', 'transport-failed', 'cancelled', 'internal-error'].includes(value.lifecycle);
@@ -387,65 +257,31 @@ function validateRunRecord(value, field) {
387
257
  || value.checks.some((check) => check.status === 'failed'))) {
388
258
  throw new Error(`${field} proving requires passed checks and checked change proof identity`);
389
259
  }
390
- if (value.lifecycle === 'publishing' && !hasOwn(value, 'proofReceipt'))
391
- throw new Error(`${field} publishing requires proofReceipt`);
392
- if (value.lifecycle === 'review-ready' && (!hasOwn(value, 'proofReceipt') || hasOwn(value, 'intent'))) {
393
- throw new Error(`${field} review-ready requires proofReceipt and no intent`);
260
+ if ((value.lifecycle === 'reviewing' || value.lifecycle === 'publishing') && !hasOwn(value, 'proofReceipt')) {
261
+ throw new Error(`${field} review and publication require proofReceipt`);
394
262
  }
395
- const retainedCandidateIntent = value.lifecycle === 'blocked'
263
+ if (value.lifecycle === 'safe-halt' && !hasOwn(value, 'activeAttempt'))
264
+ throw new Error(`${field} safe-halt requires an active attempt`);
265
+ const reviewReadyEffect = value.pendingEffect?.kind;
266
+ const reviewReadyEffectAllowed = reviewReadyEffect === undefined
267
+ || ['review-activation-labels', 'blocked-labels', 'continuation-worktree-create', 'outcome-evidence'].includes(reviewReadyEffect);
268
+ if (value.lifecycle === 'review-ready' && (!hasOwn(value, 'proofReceipt') || !reviewReadyEffectAllowed)) {
269
+ throw new Error(`${field} review-ready requires proofReceipt and only a review continuation or terminal effect`);
270
+ }
271
+ const retainedCandidateEffect = value.lifecycle === 'blocked'
396
272
  && value.terminalOutcome?.status === 'blocked'
397
273
  && value.terminalOutcome.kind === 'safety'
398
274
  && !value.terminalOutcome.resumable
399
275
  && hasOwn(value, 'candidateBinding')
400
- && (value.intent?.kind === 'commit' || value.intent?.kind === 'review-update-commit');
401
- if (terminal && hasOwn(value, 'intent') && value.lifecycle !== 'transport-failed' && !retainedCandidateIntent)
402
- throw new Error(`${field} terminal lifecycle cannot retain intent`);
403
- if (value.lifecycle === 'transport-failed' && hasOwn(value, 'intent')
276
+ && (value.pendingEffect?.kind === 'initial-commit' || value.pendingEffect?.kind === 'review-update-commit');
277
+ const settlingOutcomeEvidence = value.pendingEffect?.kind === 'outcome-evidence';
278
+ if (terminal && hasOwn(value, 'pendingEffect') && value.lifecycle !== 'transport-failed'
279
+ && !retainedCandidateEffect && !settlingOutcomeEvidence)
280
+ throw new Error(`${field} terminal lifecycle cannot retain pending effect`);
281
+ if (value.lifecycle === 'transport-failed' && hasOwn(value, 'pendingEffect')
282
+ && !settlingOutcomeEvidence
404
283
  && value.terminalOutcome.resumable) {
405
- throw new Error(`${field} resumable transport failure cannot retain intent`);
406
- }
407
- if (value.lifecycle === 'waiting-human' && !hasOwn(value, 'waitingHuman'))
408
- throw new Error(`${field} waiting-human lifecycle requires waitingHuman execution`);
409
- if (reportInvocation)
410
- validateReportInvocationBinding(value, reportInvocation, routeGenerationHash, field);
411
- validateRouteStateInvariant({
412
- lifecycle: value.lifecycle,
413
- routeExecution: value.routeExecution,
414
- routeReceipt: value.routeReceipt,
415
- generationHash: routeGenerationHash,
416
- });
417
- }
418
- function validateReportInvocationBinding(record, invocation, generationHash, field) {
419
- if (invocation.generationHash !== generationHash)
420
- throw new Error(`${field}.reportInvocation generation is invalid`);
421
- if (invocation.operation === 'triage') {
422
- if (!record.routeExecution || !['triage-in-flight', 'repair-in-flight'].includes(record.routeExecution.phase)) {
423
- throw new Error(`${field}.triage reportInvocation phase is invalid`);
424
- }
425
- return;
426
- }
427
- if (invocation.operation === 'ambiguity-review') {
428
- if (record.routeExecution?.phase !== 'review-in-flight')
429
- throw new Error(`${field}.ambiguity reportInvocation phase is invalid`);
430
- return;
431
- }
432
- if (invocation.operation === 'code-review') {
433
- if (record.directReview?.status !== 'active'
434
- || !['review-full', 'review-closure'].includes(record.directReview.stage ?? '')
435
- || record.executionLease?.operation !== 'direct-review') {
436
- throw new Error(`${field}.code-review reportInvocation binding is invalid`);
437
- }
438
- return;
439
- }
440
- if (invocation.operation === 'spec-author') {
441
- if (!record.specDelivery || !['authoring', 'author-repair'].includes(record.specDelivery.stage)
442
- || record.specDelivery.authorSessionId === null) {
443
- throw new Error(`${field}.spec-author reportInvocation binding is invalid`);
444
- }
445
- return;
446
- }
447
- if (!record.specDelivery || !['review-full', 'review-closure'].includes(record.specDelivery.stage)) {
448
- throw new Error(`${field}.spec-review reportInvocation binding is invalid`);
284
+ throw new Error(`${field} resumable transport failure cannot retain pending effect`);
449
285
  }
450
286
  }
451
287
  function directTerminalOutcome(outcome) {
@@ -467,7 +303,7 @@ function validateWorkflowGeneration(value, field) {
467
303
  }
468
304
  assertSha256(value.contentSha256, `${field}.contentSha256`);
469
305
  }
470
- function validateChecks(value, field, allowUnchangedFailure = true) {
306
+ function validateChecks(value, field) {
471
307
  if (!Array.isArray(value) || value.length > 256)
472
308
  throw new Error(`${field} is invalid`);
473
309
  const ids = new Set();
@@ -479,35 +315,19 @@ function validateChecks(value, field, allowUnchangedFailure = true) {
479
315
  ], `${field}[${index}]`);
480
316
  assertNonEmptyString(check.id, `${field}[${index}].id`);
481
317
  assertNonEmptyString(check.command, `${field}[${index}].command`);
482
- if (check.status !== 'passed' && check.status !== 'failed'
483
- && (check.status !== 'unchanged-failure' || !allowUnchangedFailure))
318
+ if (check.status !== 'passed' && check.status !== 'failed')
484
319
  throw new Error(`${field}[${index}].status is invalid`);
485
320
  assertSha256(check.outputSha256, `${field}[${index}].outputSha256`);
486
321
  if (candidate) {
487
322
  assertSha256(check.bindingId, `${field}[${index}].bindingId`);
488
323
  assertGitSha(check.candidateTreeSha, `${field}[${index}].candidateTreeSha`);
489
324
  assertSha256(check.checkPolicySha256, `${field}[${index}].checkPolicySha256`);
490
- if (check.status === 'unchanged-failure')
491
- throw new Error(`${field}[${index}] candidate status is invalid`);
492
325
  }
493
326
  if (ids.has(check.id))
494
327
  throw new Error(`${field} IDs must be unique`);
495
328
  ids.add(check.id);
496
329
  }
497
330
  }
498
- function validateCheckQualification(value, field) {
499
- const optional = hasOwn(value, 'repairFindings') ? ['repairFindings'] : [];
500
- assertExactObject(value, ['version', 'checkPolicySha256', 'repairAttempts', 'checks', ...optional], field);
501
- if (value.version !== 1)
502
- throw new Error(`${field}.version is invalid`);
503
- assertSha256(value.checkPolicySha256, `${field}.checkPolicySha256`);
504
- if (!Number.isSafeInteger(value.repairAttempts) || value.repairAttempts < 0 || value.repairAttempts > 5) {
505
- throw new Error(`${field}.repairAttempts is invalid`);
506
- }
507
- validateChecks(value.checks, `${field}.checks`, false);
508
- if (hasOwn(value, 'repairFindings'))
509
- validateStringList(value.repairFindings, `${field}.repairFindings`);
510
- }
511
331
  function validateIssueSnapshot(value, field) {
512
332
  const optional = hasOwn(value, 'comments') ? ['comments'] : [];
513
333
  assertExactObject(value, ['number', 'title', 'body', 'url', 'state', 'labels', ...optional], field);
@@ -564,48 +384,49 @@ function validateStringList(value, field) {
564
384
  for (const item of value)
565
385
  assertNonEmptyString(item, field);
566
386
  }
567
- function validateIntent(value, field) {
387
+ function validatePendingEffect(value, field) {
568
388
  if (typeof value !== 'object' || value === null || Array.isArray(value))
569
389
  throw new Error(`${field} is invalid`);
570
390
  const kind = value.kind;
571
- if (kind === 'claim-labels' || kind === 'labels') {
572
- assertExactObject(value, ['kind', 'issueNumber', 'expected'], field);
391
+ const identity = ['effectId', 'kind'];
392
+ if (kind === 'claim-labels' || kind === 'final-labels') {
393
+ assertExactObject(value, [...identity, 'issueNumber', 'expected'], field);
573
394
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
574
395
  validateStringArray(value.expected, `${field}.expected`);
575
396
  }
576
- else if (kind === 'commit') {
577
- assertExactObject(value, ['kind', 'parentSha', 'treeSha', 'message', ...(hasOwn(value, 'candidateRef') ? ['candidateRef'] : [])], field);
397
+ else if (kind === 'initial-commit') {
398
+ assertExactObject(value, [...identity, 'parentSha', 'treeSha', 'message', ...(hasOwn(value, 'candidateRef') ? ['candidateRef'] : [])], field);
578
399
  assertGitSha(value.parentSha, `${field}.parentSha`);
579
400
  assertGitSha(value.treeSha, `${field}.treeSha`);
580
401
  assertNonEmptyString(value.message, `${field}.message`);
581
402
  if (hasOwn(value, 'candidateRef'))
582
403
  assertCandidateRef(value.candidateRef, `${field}.candidateRef`);
583
404
  }
584
- else if (kind === 'push') {
585
- assertExactObject(value, ['kind', 'branch', 'sha'], field);
405
+ else if (kind === 'initial-push') {
406
+ assertExactObject(value, [...identity, 'branch', 'sha'], field);
586
407
  assertNonEmptyString(value.branch, `${field}.branch`);
587
408
  assertGitSha(value.sha, `${field}.sha`);
588
409
  }
589
- else if (kind === 'pr') {
590
- assertExactObject(value, ['kind', 'owner', 'repo', 'head', 'base', 'issueNumber', 'marker'], field);
410
+ else if (kind === 'draft-pr') {
411
+ assertExactObject(value, [...identity, 'owner', 'repo', 'head', 'base', 'issueNumber', 'marker'], field);
591
412
  for (const key of ['owner', 'repo', 'head', 'base', 'marker'])
592
413
  assertNonEmptyString(value[key], `${field}.${key}`);
593
414
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
594
415
  }
595
- else if (kind === 'comment') {
596
- assertExactObject(value, ['kind', 'issueNumber', 'marker', 'bodySha256'], field);
416
+ else if (kind === 'claim-comment' || kind === 'handoff-comment') {
417
+ assertExactObject(value, [...identity, 'issueNumber', 'marker', 'bodySha256'], field);
597
418
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
598
419
  assertNonEmptyString(value.marker, `${field}.marker`);
599
420
  assertSha256(value.bodySha256, `${field}.bodySha256`);
600
421
  }
601
422
  else if (kind === 'review-activation-labels') {
602
- assertExactObject(value, ['kind', 'issueNumber', 'batchId', 'expected'], field);
423
+ assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'expected'], field);
603
424
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
604
425
  assertSha256(value.batchId, `${field}.batchId`);
605
426
  validateStringArray(value.expected, `${field}.expected`);
606
427
  }
607
428
  else if (kind === 'review-update-commit') {
608
- assertExactObject(value, ['kind', 'batchId', 'parentSha', 'treeSha', 'message', ...(hasOwn(value, 'candidateRef') ? ['candidateRef'] : [])], field);
429
+ assertExactObject(value, [...identity, 'batchId', 'parentSha', 'treeSha', 'message', ...(hasOwn(value, 'candidateRef') ? ['candidateRef'] : [])], field);
609
430
  assertSha256(value.batchId, `${field}.batchId`);
610
431
  assertGitSha(value.parentSha, `${field}.parentSha`);
611
432
  assertGitSha(value.treeSha, `${field}.treeSha`);
@@ -614,7 +435,7 @@ function validateIntent(value, field) {
614
435
  assertCandidateRef(value.candidateRef, `${field}.candidateRef`);
615
436
  }
616
437
  else if (kind === 'review-update-push') {
617
- assertExactObject(value, ['kind', 'batchId', 'branch', 'priorRemoteSha', 'sha', 'treeSha'], field);
438
+ assertExactObject(value, [...identity, 'batchId', 'branch', 'priorRemoteSha', 'sha', 'treeSha'], field);
618
439
  assertSha256(value.batchId, `${field}.batchId`);
619
440
  assertNonEmptyString(value.branch, `${field}.branch`);
620
441
  assertGitSha(value.priorRemoteSha, `${field}.priorRemoteSha`);
@@ -622,7 +443,7 @@ function validateIntent(value, field) {
622
443
  assertGitSha(value.treeSha, `${field}.treeSha`);
623
444
  }
624
445
  else if (kind === 'review-summary') {
625
- assertExactObject(value, ['kind', 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'marker', 'bodySha256', 'epochHeadSha'], field);
446
+ assertExactObject(value, [...identity, 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'marker', 'bodySha256', 'epochHeadSha'], field);
626
447
  assertSha256(value.batchId, `${field}.batchId`);
627
448
  assertPositiveInteger(value.pullRequestNumber, `${field}.pullRequestNumber`);
628
449
  assertNonEmptyString(value.pullRequestNodeId, `${field}.pullRequestNodeId`);
@@ -631,7 +452,7 @@ function validateIntent(value, field) {
631
452
  assertGitSha(value.epochHeadSha, `${field}.epochHeadSha`);
632
453
  }
633
454
  else if (kind === 'review-final-labels') {
634
- assertExactObject(value, ['kind', 'issueNumber', 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'epochHeadSha', 'expected'], field);
455
+ assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'epochHeadSha', 'expected'], field);
635
456
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
636
457
  assertSha256(value.batchId, `${field}.batchId`);
637
458
  assertPositiveInteger(value.pullRequestNumber, `${field}.pullRequestNumber`);
@@ -640,27 +461,61 @@ function validateIntent(value, field) {
640
461
  validateStringArray(value.expected, `${field}.expected`);
641
462
  }
642
463
  else if (kind === 'review-blocked-labels') {
643
- assertExactObject(value, ['kind', 'issueNumber', 'batchId', 'expected', 'blockKind', 'evidenceCode'], field);
464
+ assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'expected', 'blockKind', 'evidenceCode'], field);
644
465
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
645
466
  assertSha256(value.batchId, `${field}.batchId`);
646
467
  validateStringArray(value.expected, `${field}.expected`);
647
- if (value.blockKind !== 'safety' && value.blockKind !== 'exhausted')
468
+ if (!['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.blockKind)) {
648
469
  throw new Error(`${field}.blockKind is invalid`);
470
+ }
649
471
  assertNonEmptyString(value.evidenceCode, `${field}.evidenceCode`);
650
472
  }
651
473
  else if (kind === 'blocked-labels') {
652
- assertExactObject(value, ['kind', 'issueNumber', 'expected', 'blockKind', 'resumable', 'evidenceCode'], field);
474
+ assertExactObject(value, [...identity, 'issueNumber', 'expected', 'blockKind', 'resumable', 'evidenceCode'], field);
653
475
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
654
476
  validateStringArray(value.expected, `${field}.expected`);
655
- if (!['external', 'safety', 'exhausted'].includes(value.blockKind))
477
+ if (!['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.blockKind))
656
478
  throw new Error(`${field}.blockKind is invalid`);
657
479
  if (typeof value.resumable !== 'boolean')
658
480
  throw new Error(`${field}.resumable is invalid`);
659
481
  assertNonEmptyString(value.evidenceCode, `${field}.evidenceCode`);
660
482
  }
483
+ else if (kind === 'worktree-create') {
484
+ assertExactObject(value, [...identity, 'worktreePath', 'branchName', 'baseBranch', 'baseSha'], field);
485
+ assertAbsolutePath(value.worktreePath, `${field}.worktreePath`);
486
+ assertNonEmptyString(value.branchName, `${field}.branchName`);
487
+ assertNonEmptyString(value.baseBranch, `${field}.baseBranch`);
488
+ assertGitSha(value.baseSha, `${field}.baseSha`);
489
+ }
490
+ else if (kind === 'continuation-worktree-create') {
491
+ assertExactObject(value, [...identity, 'worktreePath', 'branchName', 'baseBranch', 'publishedHeadSha'], field);
492
+ assertAbsolutePath(value.worktreePath, `${field}.worktreePath`);
493
+ assertNonEmptyString(value.branchName, `${field}.branchName`);
494
+ assertNonEmptyString(value.baseBranch, `${field}.baseBranch`);
495
+ assertGitSha(value.publishedHeadSha, `${field}.publishedHeadSha`);
496
+ }
497
+ else if (kind === 'candidate-pin-release') {
498
+ assertExactObject(value, [...identity, 'bindingId', 'expectedPinnedCommitSha'], field);
499
+ assertSha256(value.bindingId, `${field}.bindingId`);
500
+ assertGitSha(value.expectedPinnedCommitSha, `${field}.expectedPinnedCommitSha`);
501
+ }
502
+ else if (kind === 'outcome-evidence') {
503
+ assertExactObject(value, [...identity, 'path', 'runId', 'code', 'summary', 'recordedAt', 'bytesSha256'], field);
504
+ assertNonEmptyString(value.path, `${field}.path`);
505
+ if (typeof value.runId !== 'string' || !UUID_V4_PATTERN.test(value.runId))
506
+ throw new Error(`${field}.runId is invalid`);
507
+ assertNonEmptyString(value.code, `${field}.code`);
508
+ assertNonEmptyString(value.summary, `${field}.summary`);
509
+ assertTimestamp(value.recordedAt, `${field}.recordedAt`);
510
+ assertSha256(value.bytesSha256, `${field}.bytesSha256`);
511
+ }
661
512
  else {
662
513
  throw new Error(`${field}.kind is invalid`);
663
514
  }
515
+ assertSha256(value.effectId, `${field}.effectId`);
516
+ const { effectId, ...payload } = value;
517
+ if (effectId !== sha256(canonicalJson(payload)))
518
+ throw new Error(`${field}.effectId does not match its payload`);
664
519
  }
665
520
  function validateTerminalOutcome(value, field) {
666
521
  if (typeof value !== 'object' || value === null || Array.isArray(value))
@@ -673,11 +528,13 @@ function validateTerminalOutcome(value, field) {
673
528
  assertGitSha(value.continuationEpoch, `${field}.continuationEpoch`);
674
529
  }
675
530
  else if (status === 'blocked') {
676
- assertExactObject(value, ['status', 'kind', 'resumable', 'evidencePath'], field);
677
- if (!['external', 'safety', 'exhausted'].includes(value.kind))
531
+ assertExactObject(value, ['status', 'kind', 'resumable', 'evidencePath', ...(hasOwn(value, 'blocker') ? ['blocker'] : [])], field);
532
+ if (!['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.kind))
678
533
  throw new Error(`${field}.kind is invalid`);
679
534
  if (typeof value.resumable !== 'boolean')
680
535
  throw new Error(`${field}.resumable is invalid`);
536
+ if (hasOwn(value, 'blocker'))
537
+ validateBlockerDetail(value.blocker, `${field}.blocker`);
681
538
  }
682
539
  else if (status === 'transport-failed') {
683
540
  assertExactObject(value, ['status', 'resumable', 'evidencePath'], field);
@@ -696,6 +553,19 @@ function validateTerminalOutcome(value, field) {
696
553
  }
697
554
  assertNonEmptyString(value.evidencePath, `${field}.evidencePath`);
698
555
  }
556
+ function validateBlockerDetail(value, field) {
557
+ assertExactObject(value, ['kind', 'summary', 'attempted', 'resumable', ...(hasOwn(value, 'reviewerRejectionDetail') ? ['reviewerRejectionDetail'] : [])], field);
558
+ assertNonEmptyString(value.kind, `${field}.kind`);
559
+ assertNonEmptyString(value.summary, `${field}.summary`);
560
+ if (!Array.isArray(value.attempted) || value.attempted.length > 256)
561
+ throw new Error(`${field}.attempted is invalid`);
562
+ for (const attempted of value.attempted)
563
+ assertNonEmptyString(attempted, `${field}.attempted`);
564
+ if (typeof value.resumable !== 'boolean')
565
+ throw new Error(`${field}.resumable is invalid`);
566
+ if (hasOwn(value, 'reviewerRejectionDetail'))
567
+ assertNonEmptyString(value.reviewerRejectionDetail, `${field}.reviewerRejectionDetail`);
568
+ }
699
569
  function validateReceipt(value, field) {
700
570
  assertExactObject(value, ['proofId', 'bindingSha256', 'summary', 'publishableEvidence', 'localEvidenceId'], field);
701
571
  assertNonEmptyString(value.proofId, `${field}.proofId`);
@@ -713,6 +583,18 @@ function validateReceipt(value, field) {
713
583
  assertNonEmptyString(evidence.description, `${field}.publishableEvidence.description`);
714
584
  }
715
585
  }
586
+ function validateProofExecution(value, field) {
587
+ assertExactObject(value, ['startedAt', 'transportRetryCount', 'reportRepairCount', 'reportRepairFindings'], field);
588
+ assertTimestamp(value.startedAt, `${field}.startedAt`);
589
+ if (!Number.isSafeInteger(value.transportRetryCount) || value.transportRetryCount < 0)
590
+ throw new Error(`${field}.transportRetryCount is invalid`);
591
+ if (!Number.isSafeInteger(value.reportRepairCount) || value.reportRepairCount < 0)
592
+ throw new Error(`${field}.reportRepairCount is invalid`);
593
+ validateStringList(value.reportRepairFindings, `${field}.reportRepairFindings`);
594
+ if ((value.reportRepairCount === 0) !== (value.reportRepairFindings.length === 0)) {
595
+ throw new Error(`${field} report repair state is invalid`);
596
+ }
597
+ }
716
598
  function validateStringShaRecord(value, field) {
717
599
  if (typeof value !== 'object' || value === null || Array.isArray(value))
718
600
  throw new Error(`${field} is invalid`);
@@ -733,154 +615,7 @@ function validateStringArray(value, field) {
733
615
  throw new Error(`${field} must be sorted and unique`);
734
616
  }
735
617
  function emptyRunState() {
736
- return { schema: 'codex-orchestrator.agent-auto-state', version: 3, generation: 0, runs: [] };
737
- }
738
- function canonicalizeLegacyReportLifecycle(value, backupPath) {
739
- if (typeof value !== 'object' || value === null || Array.isArray(value))
740
- return undefined;
741
- const raw = value;
742
- if (raw.schema !== 'codex-orchestrator.agent-auto-state' || (raw.version !== 2 && raw.version !== 3)
743
- || !Number.isSafeInteger(raw.generation) || raw.generation <= 0 || !Array.isArray(raw.runs))
744
- return undefined;
745
- const runs = structuredClone(raw.runs);
746
- const changed = runs.map((run) => canonicalizeLegacyReportRun(run, backupPath)).some(Boolean);
747
- if (!changed)
748
- return undefined;
749
- return validateRunStateFile({ schema: raw.schema, version: raw.version, generation: raw.generation + 1, runs });
750
- }
751
- function canonicalizeLegacyReportRun(run, backupPath) {
752
- let changed = false, unsafe = false, routeInFlight = false;
753
- const mutable = canonicalizeLegacyMutableRun(run);
754
- changed ||= mutable.changed;
755
- unsafe ||= mutable.unsafe;
756
- const route = run.routeExecution;
757
- if (route) {
758
- const triageTransport = hasOwn(route, 'triageTransportRetries'), ambiguityTransport = hasOwn(route, 'ambiguityTransportRetries');
759
- if (triageTransport !== ambiguityTransport)
760
- throw new Error('legacy route transport budgets are incomplete');
761
- if (triageTransport) {
762
- legacyBits([route.triageTransportRetries, route.ambiguityTransportRetries], 'route');
763
- delete route.triageTransportRetries;
764
- delete route.ambiguityTransportRetries;
765
- changed = true;
766
- }
767
- if (hasOwn(route, 'previousAttemptId')) {
768
- delete route.previousAttemptId;
769
- changed = true;
770
- }
771
- routeInFlight = ['triage-in-flight', 'review-in-flight', 'repair-in-flight'].includes(route.phase);
772
- if (routeInFlight) {
773
- unsafe = true;
774
- delete route.attemptId;
775
- delete route.startedAt;
776
- }
777
- }
778
- const direct = run.directReview;
779
- if (direct?.review && hasOwn(direct.review, 'transportRetries')) {
780
- legacyBits([direct.review.transportRetries], 'direct review');
781
- delete direct.review.transportRetries;
782
- changed = true;
783
- }
784
- if (direct && hasOwn(direct, 'invocation')) {
785
- delete direct.invocation;
786
- unsafe = changed = true;
787
- }
788
- const spec = run.specDelivery;
789
- if (spec?.budgets?.author && hasOwn(spec.budgets.author, 'transportRetries')) {
790
- legacyBits([spec.budgets.author.transportRetries], 'spec author');
791
- delete spec.budgets.author.transportRetries;
792
- changed = true;
793
- }
794
- if (spec?.budgets?.review && hasOwn(spec.budgets.review, 'transportRetries')) {
795
- legacyBits([spec.budgets.review.transportRetries], 'spec review');
796
- delete spec.budgets.review.transportRetries;
797
- changed = true;
798
- }
799
- if (spec?.review && !hasOwn(spec.review, 'reviewerSessionId')) {
800
- spec.review.reviewerSessionId = spec.review.reviewer?.sessionId ?? (spec.invocation?.purpose === 'review' ? spec.invocation.sessionId : null);
801
- changed = true;
802
- }
803
- if (spec?.invocation?.purpose === 'review') {
804
- delete spec.invocation;
805
- unsafe = changed = true;
806
- }
807
- if (spec?.invocation?.purpose === 'author') {
808
- if (spec.invocation.status === 'launched')
809
- unsafe = true;
810
- if (spec.authorSessionId === null)
811
- spec.authorSessionId = spec.invocation.sessionId;
812
- else if (spec.authorSessionId !== spec.invocation.sessionId)
813
- unsafe = true;
814
- delete spec.invocation;
815
- changed = true;
816
- }
817
- if (run.reportInvocation && !hasOwn(run.reportInvocation, 'promptFactsSha256')) {
818
- delete run.reportInvocation;
819
- unsafe = changed = true;
820
- }
821
- if (run.process && ['route', 'code-review', 'spec-author', 'spec-review', 'proof'].includes(run.process.purpose)) {
822
- delete run.process;
823
- unsafe = changed = true;
824
- }
825
- if (!unsafe)
826
- return changed;
827
- if (routeInFlight)
828
- delete run.routeExecution;
829
- delete run.reportInvocation;
830
- delete run.process;
831
- delete run.intent;
832
- if (direct)
833
- run.directReview = projectTerminalDirectReview(direct, { status: 'blocked', kind: 'safety' }, 'report-lifecycle-migration-identity-unavailable');
834
- run.lifecycle = 'blocked';
835
- run.outcomeEvidenceId = `report-lifecycle-migration:${String(run.runId)}`;
836
- run.terminalOutcome = { status: 'blocked', kind: 'safety', resumable: false, evidencePath: backupPath };
837
- return true;
838
- }
839
- function hasLegacyMutableLifecycle(value) {
840
- if (typeof value !== 'object' || value === null || !Array.isArray(value.runs))
841
- return false;
842
- return (value.runs).some((run) => hasOwn(run, 'transportRetries') || run.process?.purpose === 'implementation'
843
- || hasOwn(run.checkQualification, 'implementationStarted') || hasOwn(run.checkQualification, 'repairInvocation')
844
- || hasOwn(run.checkQualification, 'deniedPathsBaseline') || hasOwn(run.reviewFeedback, 'implementationInvocation'));
845
- }
846
- function canonicalizeLegacyMutableRun(run) {
847
- let changed = false, unsafe = false;
848
- if (hasOwn(run, 'transportRetries')) {
849
- legacyBits([run.transportRetries], 'mutable invocation');
850
- delete run.transportRetries;
851
- changed = true;
852
- }
853
- const qualification = run.checkQualification;
854
- if (qualification) {
855
- if (qualification.repairInvocation?.phase === 'launched')
856
- unsafe = true;
857
- for (const field of ['implementationStarted', 'repairInvocation', 'deniedPathsBaseline'])
858
- if (hasOwn(qualification, field)) {
859
- delete qualification[field];
860
- changed = true;
861
- }
862
- }
863
- const feedback = run.reviewFeedback;
864
- if (feedback && hasOwn(feedback, 'implementationInvocation')) {
865
- if (feedback.implementationInvocation?.phase === 'launched')
866
- unsafe = true;
867
- delete feedback.implementationInvocation;
868
- changed = true;
869
- }
870
- if (run.process?.purpose === 'implementation') {
871
- delete run.process;
872
- changed = unsafe = true;
873
- }
874
- if (!unsafe)
875
- return { changed, unsafe };
876
- delete run.mutableInvocation;
877
- if (feedback?.activeBatch)
878
- run.reviewFeedback = blockReviewFeedback(feedback, 'safety', String(run.updatedAt));
879
- return { changed: true, unsafe: true };
880
- }
881
- function legacyBits(values, owner) {
882
- if (values.some((value) => value !== 0 && value !== 1))
883
- throw new Error(`legacy ${owner} transport budget is invalid`);
618
+ return { schema: 'codex-orchestrator.run-state', generation: 0, runs: [] };
884
619
  }
885
620
  function parseRawState(bytes) {
886
621
  let value;
@@ -892,50 +627,24 @@ function parseRawState(bytes) {
892
627
  }
893
628
  return validateRunStateFile(value);
894
629
  }
895
- async function readOptionalFile(path) {
630
+ async function readOptionalStateFile(path) {
631
+ let handle;
896
632
  try {
897
- return await readFile(path);
633
+ handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
898
634
  }
899
635
  catch (error) {
900
636
  if (isErrorCode(error, 'ENOENT'))
901
637
  return undefined;
902
638
  throw error;
903
639
  }
904
- }
905
- async function readMigrationMetadata(path) {
906
- const bytes = await readOptionalFile(path);
907
- if (!bytes)
908
- return undefined;
909
- let value;
910
640
  try {
911
- value = JSON.parse(bytes.toString('utf8'));
641
+ const stat = await handle.stat();
642
+ if (!stat.isFile())
643
+ throw new Error(`${path} is not a regular file`);
644
+ return await handle.readFile();
912
645
  }
913
- catch {
914
- throw new Error('candidate migration metadata is malformed');
915
- }
916
- assertExactObject(value, [
917
- 'version', 'sourceGeneration', 'sourceBytesSha256', 'publicationEffectPossible', 'createdAt', 'updatedAt',
918
- ], 'candidate migration metadata');
919
- if (value.version !== 1)
920
- throw new Error('candidate migration metadata version is invalid');
921
- if (value.sourceGeneration !== null && (!Number.isSafeInteger(value.sourceGeneration) || value.sourceGeneration <= 0)) {
922
- throw new Error('candidate migration source generation is invalid');
923
- }
924
- if (value.sourceBytesSha256 !== null)
925
- assertSha256(value.sourceBytesSha256, 'candidate migration source hash');
926
- if ((value.sourceGeneration === null) !== (value.sourceBytesSha256 === null))
927
- throw new Error('candidate migration source identity is incomplete');
928
- if (typeof value.publicationEffectPossible !== 'boolean')
929
- throw new Error('candidate migration publication watermark is invalid');
930
- assertTimestamp(value.createdAt, 'candidate migration createdAt');
931
- assertTimestamp(value.updatedAt, 'candidate migration updatedAt');
932
- return value;
933
- }
934
- function assertNoCandidateV3Fields(runs) {
935
- for (const run of runs) {
936
- if (run.changeBindingVersion !== undefined || run.candidateBinding !== undefined || run.executionLease !== undefined) {
937
- throw new Error('V2 run state cannot contain candidate V3 fields');
938
- }
646
+ finally {
647
+ await handle.close();
939
648
  }
940
649
  }
941
650
  function isErrorCode(error, code) {
@@ -949,22 +658,18 @@ function validateReviewFeedbackRunInvariant(run, field) {
949
658
  || batch.priorPublishedHeadSha !== feedback.previousPublishedHeadSha)) {
950
659
  throw new Error(`${field}.reviewFeedback active batch identity binding is invalid`);
951
660
  }
952
- const retainedQuiescentHistory = (feedback.phase === 'idle' || feedback.phase === 'bootstrap-required') && !batch
953
- && run.lifecycle === 'blocked' && run.terminalOutcome?.status === 'blocked';
954
- if ((feedback.phase === 'bootstrap-required' || feedback.phase === 'idle')
955
- && run.lifecycle !== 'review-ready' && !retainedQuiescentHistory) {
956
- throw new Error(`${field}.reviewFeedback quiescent phase requires review-ready lifecycle`);
957
- }
958
- if (['frozen', 'repairing'].includes(feedback.phase) && !['implementing', 'reworking', 'checking', 'proving'].includes(run.lifecycle)) {
959
- throw new Error(`${field}.reviewFeedback active repair phase has invalid lifecycle`);
960
- }
961
- if (feedback.phase === 'verified' && run.lifecycle !== 'publishing') {
962
- throw new Error(`${field}.reviewFeedback verified phase requires publishing lifecycle`);
661
+ const retainedQuiescentHistory = !batch && run.lifecycle === 'blocked' && run.terminalOutcome?.status === 'blocked';
662
+ if (!batch && run.lifecycle !== 'review-ready' && !retainedQuiescentHistory) {
663
+ throw new Error(`${field}.reviewFeedback quiescent data requires review-ready lifecycle`);
963
664
  }
964
- if (feedback.phase === 'publishing' && run.lifecycle !== 'publishing') {
965
- throw new Error(`${field}.reviewFeedback publishing phase requires publishing lifecycle`);
665
+ if (batch && !feedback.verifiedReceipt
666
+ && !['implementing', 'checking', 'proving', 'reviewing', 'safe-halt'].includes(run.lifecycle)) {
667
+ throw new Error(`${field}.reviewFeedback active batch has invalid lifecycle`);
966
668
  }
967
- if (feedback.phase === 'verified' || feedback.phase === 'publishing') {
669
+ if (feedback.verifiedReceipt) {
670
+ if (run.lifecycle !== 'reviewing' && run.lifecycle !== 'publishing') {
671
+ throw new Error(`${field}.reviewFeedback verified batch requires review or publication lifecycle`);
672
+ }
968
673
  const verified = feedback.verifiedReceipt;
969
674
  if (!verified || verified.batchId !== batch?.batchId
970
675
  || verified.checkedChangeSha256 !== run.checkedChangeSha256
@@ -973,16 +678,10 @@ function validateReviewFeedbackRunInvariant(run, field) {
973
678
  throw new Error(`${field}.reviewFeedback verification receipt binding is invalid`);
974
679
  }
975
680
  }
976
- if (feedback.phase === 'blocked-safety' || feedback.phase === 'blocked-exhausted') {
977
- if (run.lifecycle !== 'blocked' || run.terminalOutcome?.status !== 'blocked' || run.terminalOutcome.resumable
978
- || run.terminalOutcome.kind !== (feedback.phase === 'blocked-safety' ? 'safety' : 'exhausted')) {
979
- throw new Error(`${field}.reviewFeedback terminal projection mismatch`);
980
- }
981
- }
982
681
  }
983
682
  function isLifecycle(value) {
984
683
  return typeof value === 'string' && [
985
- 'claimed', 'triaging', 'routed', 'waiting-human', 'spec-authoring', 'implementing', 'reworking', 'checking', 'proving', 'publishing',
684
+ 'claimed', 'implementing', 'checking', 'proving', 'reviewing', 'publishing', 'safe-halt',
986
685
  'review-ready', 'blocked', 'transport-failed', 'cancelled', 'internal-error',
987
686
  ].includes(value);
988
687
  }
@@ -1001,6 +700,10 @@ function assertNonEmptyString(value, field) {
1001
700
  if (typeof value !== 'string' || value.length === 0 || value.length > 16 * 1024)
1002
701
  throw new Error(`${field} is invalid`);
1003
702
  }
703
+ function assertAbsolutePath(value, field) {
704
+ if (typeof value !== 'string' || !value.startsWith('/') || posix.normalize(value) !== value)
705
+ throw new Error(`${field} is invalid`);
706
+ }
1004
707
  function assertPositiveInteger(value, field) {
1005
708
  if (!Number.isSafeInteger(value) || value <= 0)
1006
709
  throw new Error(`${field} is invalid`);