codex-orchestrator 2.0.10 → 2.0.12

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 (279) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +29 -30
  3. package/dist/src/index.d.ts +4 -9
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js +2 -5
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/v2/acceptance-proof.d.ts +53 -25
  8. package/dist/src/v2/acceptance-proof.d.ts.map +1 -1
  9. package/dist/src/v2/acceptance-proof.js +189 -197
  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 +7 -0
  16. package/dist/src/v2/adapters/command.d.ts.map +1 -1
  17. package/dist/src/v2/adapters/command.js +44 -3
  18. package/dist/src/v2/adapters/command.js.map +1 -1
  19. package/dist/src/v2/adapters/gh-pull-request-adapter.js +1 -0
  20. package/dist/src/v2/adapters/gh-pull-request-adapter.js.map +1 -1
  21. package/dist/src/v2/adapters/pull-requests.d.ts +1 -0
  22. package/dist/src/v2/adapters/pull-requests.d.ts.map +1 -1
  23. package/dist/src/v2/adapters/pull-requests.js +1 -0
  24. package/dist/src/v2/adapters/pull-requests.js.map +1 -1
  25. package/dist/src/v2/adapters/worktree.js +3 -3
  26. package/dist/src/v2/adapters/worktree.js.map +1 -1
  27. package/dist/src/v2/atomic-store.d.ts +15 -0
  28. package/dist/src/v2/atomic-store.d.ts.map +1 -1
  29. package/dist/src/v2/atomic-store.js +55 -8
  30. package/dist/src/v2/atomic-store.js.map +1 -1
  31. package/dist/src/v2/candidate.d.ts +136 -0
  32. package/dist/src/v2/candidate.d.ts.map +1 -0
  33. package/dist/src/v2/candidate.js +107 -0
  34. package/dist/src/v2/candidate.js.map +1 -0
  35. package/dist/src/v2/checked-change.d.ts +39 -7
  36. package/dist/src/v2/checked-change.d.ts.map +1 -1
  37. package/dist/src/v2/checked-change.js +73 -1
  38. package/dist/src/v2/checked-change.js.map +1 -1
  39. package/dist/src/v2/cli-contract.d.ts +1 -1
  40. package/dist/src/v2/cli-contract.d.ts.map +1 -1
  41. package/dist/src/v2/cli-contract.js +4 -6
  42. package/dist/src/v2/cli-contract.js.map +1 -1
  43. package/dist/src/v2/cli.d.ts +14 -0
  44. package/dist/src/v2/cli.d.ts.map +1 -1
  45. package/dist/src/v2/cli.js +70 -21
  46. package/dist/src/v2/cli.js.map +1 -1
  47. package/dist/src/v2/code-review-report.d.ts +10 -18
  48. package/dist/src/v2/code-review-report.d.ts.map +1 -1
  49. package/dist/src/v2/code-review-report.js +63 -60
  50. package/dist/src/v2/code-review-report.js.map +1 -1
  51. package/dist/src/v2/codex-process.d.ts +6 -1
  52. package/dist/src/v2/codex-process.d.ts.map +1 -1
  53. package/dist/src/v2/codex-process.js +36 -9
  54. package/dist/src/v2/codex-process.js.map +1 -1
  55. package/dist/src/v2/config.d.ts +0 -2
  56. package/dist/src/v2/config.d.ts.map +1 -1
  57. package/dist/src/v2/config.js +3 -6
  58. package/dist/src/v2/config.js.map +1 -1
  59. package/dist/src/v2/contained-report-operation.d.ts +17 -6
  60. package/dist/src/v2/contained-report-operation.d.ts.map +1 -1
  61. package/dist/src/v2/contained-report-operation.js +11 -24
  62. package/dist/src/v2/contained-report-operation.js.map +1 -1
  63. package/dist/src/v2/containment.d.ts +1 -0
  64. package/dist/src/v2/containment.d.ts.map +1 -1
  65. package/dist/src/v2/containment.js +12 -2
  66. package/dist/src/v2/containment.js.map +1 -1
  67. package/dist/src/v2/delivery-authority.d.ts +26 -0
  68. package/dist/src/v2/delivery-authority.d.ts.map +1 -0
  69. package/dist/src/v2/delivery-authority.js +44 -0
  70. package/dist/src/v2/delivery-authority.js.map +1 -0
  71. package/dist/src/v2/direct-delivery.d.ts +19 -57
  72. package/dist/src/v2/direct-delivery.d.ts.map +1 -1
  73. package/dist/src/v2/direct-delivery.js +154 -211
  74. package/dist/src/v2/direct-delivery.js.map +1 -1
  75. package/dist/src/v2/immutable-workflow-publisher.d.ts.map +1 -1
  76. package/dist/src/v2/immutable-workflow-publisher.js +3 -1
  77. package/dist/src/v2/immutable-workflow-publisher.js.map +1 -1
  78. package/dist/src/v2/implementation-report.d.ts +3 -1
  79. package/dist/src/v2/implementation-report.d.ts.map +1 -1
  80. package/dist/src/v2/implementation-report.js +17 -4
  81. package/dist/src/v2/implementation-report.js.map +1 -1
  82. package/dist/src/v2/implementation-reviewer.d.ts +22 -13
  83. package/dist/src/v2/implementation-reviewer.d.ts.map +1 -1
  84. package/dist/src/v2/implementation-reviewer.js +96 -30
  85. package/dist/src/v2/implementation-reviewer.js.map +1 -1
  86. package/dist/src/v2/owner-control-lock.d.ts +13 -1
  87. package/dist/src/v2/owner-control-lock.d.ts.map +1 -1
  88. package/dist/src/v2/owner-control-lock.js +66 -7
  89. package/dist/src/v2/owner-control-lock.js.map +1 -1
  90. package/dist/src/v2/pending-effect-settlement.d.ts +44 -0
  91. package/dist/src/v2/pending-effect-settlement.d.ts.map +1 -0
  92. package/dist/src/v2/pending-effect-settlement.js +69 -0
  93. package/dist/src/v2/pending-effect-settlement.js.map +1 -0
  94. package/dist/src/v2/process-identity.d.ts +45 -0
  95. package/dist/src/v2/process-identity.d.ts.map +1 -0
  96. package/dist/src/v2/process-identity.js +118 -0
  97. package/dist/src/v2/process-identity.js.map +1 -0
  98. package/dist/src/v2/proof-report.d.ts +4 -2
  99. package/dist/src/v2/proof-report.d.ts.map +1 -1
  100. package/dist/src/v2/proof-report.js +16 -44
  101. package/dist/src/v2/proof-report.js.map +1 -1
  102. package/dist/src/v2/review-feedback-coordinator.d.ts +8 -2
  103. package/dist/src/v2/review-feedback-coordinator.d.ts.map +1 -1
  104. package/dist/src/v2/review-feedback-coordinator.js +98 -71
  105. package/dist/src/v2/review-feedback-coordinator.js.map +1 -1
  106. package/dist/src/v2/review-feedback.d.ts +14 -38
  107. package/dist/src/v2/review-feedback.d.ts.map +1 -1
  108. package/dist/src/v2/review-feedback.js +48 -115
  109. package/dist/src/v2/review-feedback.js.map +1 -1
  110. package/dist/src/v2/run-issue.d.ts +139 -60
  111. package/dist/src/v2/run-issue.d.ts.map +1 -1
  112. package/dist/src/v2/run-issue.js +2236 -1974
  113. package/dist/src/v2/run-issue.js.map +1 -1
  114. package/dist/src/v2/run-state-projections.d.ts +84 -0
  115. package/dist/src/v2/run-state-projections.d.ts.map +1 -0
  116. package/dist/src/v2/run-state-projections.js +142 -0
  117. package/dist/src/v2/run-state-projections.js.map +1 -0
  118. package/dist/src/v2/run-store.d.ts +114 -82
  119. package/dist/src/v2/run-store.d.ts.map +1 -1
  120. package/dist/src/v2/run-store.js +308 -224
  121. package/dist/src/v2/run-store.js.map +1 -1
  122. package/dist/src/v2/runtime-assets.d.ts +3 -0
  123. package/dist/src/v2/runtime-assets.d.ts.map +1 -1
  124. package/dist/src/v2/runtime-assets.js +104 -0
  125. package/dist/src/v2/runtime-assets.js.map +1 -1
  126. package/dist/src/v2/runtime.d.ts +52 -15
  127. package/dist/src/v2/runtime.d.ts.map +1 -1
  128. package/dist/src/v2/runtime.js +818 -348
  129. package/dist/src/v2/runtime.js.map +1 -1
  130. package/dist/src/v2/setup.js +0 -2
  131. package/dist/src/v2/setup.js.map +1 -1
  132. package/dist/src/v2/validation-progression.d.ts +70 -0
  133. package/dist/src/v2/validation-progression.d.ts.map +1 -0
  134. package/dist/src/v2/validation-progression.js +247 -0
  135. package/dist/src/v2/validation-progression.js.map +1 -0
  136. package/dist/src/v2/workflow-assets.d.ts +9 -3
  137. package/dist/src/v2/workflow-assets.d.ts.map +1 -1
  138. package/dist/src/v2/workflow-assets.js +256 -43
  139. package/dist/src/v2/workflow-assets.js.map +1 -1
  140. package/docs/deep-dive.md +52 -14
  141. package/internal-workflow/docs/agents/bug-workflow-routing.md +9 -7
  142. package/internal-workflow/docs/agents/coding-skill-routing.md +170 -120
  143. package/internal-workflow/docs/agents/tool-usage.md +23 -12
  144. package/internal-workflow/manifest.json +1 -1
  145. package/internal-workflow/operations/code-review/SKILL.md +34 -15
  146. package/internal-workflow/operations/implementation/SKILL.md +21 -16
  147. package/internal-workflow/profiles/implementer.toml +9 -0
  148. package/internal-workflow/profiles/review_coordinator.toml +9 -0
  149. package/internal-workflow/profiles/spec_reviewer.toml +9 -0
  150. package/internal-workflow/profiles/standards_reviewer.toml +9 -0
  151. package/internal-workflow/schemas/code-review-v1.json +1 -1
  152. package/internal-workflow/schemas/implementation-report-v1.json +1 -1
  153. package/internal-workflow/schemas/proof-report-v1.json +1 -1
  154. package/internal-workflow/skills/bug-root-cause-explainer/SKILL.md +114 -0
  155. package/internal-workflow/skills/bug-root-cause-explainer/agents/openai.yaml +7 -0
  156. package/internal-workflow/skills/bug-root-cause-explainer/evals/evals.json +18 -0
  157. package/internal-workflow/skills/code-review/SKILL.md +84 -288
  158. package/internal-workflow/skills/code-review/agents/openai.yaml +5 -3
  159. package/internal-workflow/skills/code-review/evals/evals.json +83 -0
  160. package/internal-workflow/skills/code-review/references/standards-smells.md +41 -0
  161. package/internal-workflow/skills/diagnosing-bugs/SKILL.md +69 -32
  162. package/internal-workflow/skills/diagnosing-bugs/agents/openai.yaml +2 -2
  163. package/internal-workflow/skills/diagnosing-bugs/evals/evals.json +63 -0
  164. package/internal-workflow/skills/grilling/SKILL.md +51 -0
  165. package/internal-workflow/skills/grilling/agents/openai.yaml +6 -0
  166. package/internal-workflow/skills/grilling/evals/evals.json +47 -0
  167. package/internal-workflow/skills/implement/SKILL.md +135 -0
  168. package/internal-workflow/skills/implement/agents/openai.yaml +6 -0
  169. package/internal-workflow/skills/implement/evals/evals.json +150 -0
  170. package/internal-workflow/skills/plan/SKILL.md +59 -0
  171. package/internal-workflow/skills/plan/agents/openai.yaml +6 -0
  172. package/internal-workflow/skills/plan/evals/evals.json +36 -0
  173. package/internal-workflow/skills/prototype/LOGIC.md +130 -0
  174. package/internal-workflow/skills/prototype/SKILL.md +69 -0
  175. package/internal-workflow/skills/prototype/UI.md +157 -0
  176. package/internal-workflow/skills/prototype/agents/openai.yaml +6 -0
  177. package/internal-workflow/skills/prototype/evals/evals.json +67 -0
  178. package/internal-workflow/skills/research/SKILL.md +110 -0
  179. package/internal-workflow/skills/research/agents/openai.yaml +6 -0
  180. package/internal-workflow/skills/research/evals/evals.json +49 -0
  181. package/internal-workflow/skills/tdd/SKILL.md +72 -67
  182. package/internal-workflow/skills/tdd/agents/openai.yaml +2 -2
  183. package/internal-workflow/skills/tdd/evals/evals.json +12 -0
  184. package/internal-workflow/skills/tdd/mocking.md +48 -1
  185. package/internal-workflow/skills/tdd/refactoring.md +3 -3
  186. package/internal-workflow/skills/tickets-orchestrator/SKILL.md +199 -0
  187. package/internal-workflow/skills/tickets-orchestrator/agents/openai.yaml +6 -0
  188. package/internal-workflow/skills/tickets-orchestrator/evals/evals.json +126 -0
  189. package/internal-workflow/skills/tickets-orchestrator/references/delegate-integrate.md +83 -0
  190. package/internal-workflow/skills/tickets-orchestrator/references/finish-delivery.md +69 -0
  191. package/internal-workflow/skills/tickets-orchestrator/references/stop-completion.md +63 -0
  192. package/internal-workflow/skills/to-spec/SKILL.md +133 -0
  193. package/internal-workflow/skills/to-spec/agents/openai.yaml +6 -0
  194. package/internal-workflow/skills/to-spec/evals/evals.json +24 -0
  195. package/internal-workflow/skills/to-tickets/SKILL.md +189 -0
  196. package/internal-workflow/skills/to-tickets/agents/openai.yaml +6 -0
  197. package/internal-workflow/skills/to-tickets/evals/evals.json +79 -0
  198. package/internal-workflow/skills/to-tickets/references/publishing-details.md +117 -0
  199. package/package.json +1 -1
  200. package/dist/src/v2/proof-store.d.ts +0 -42
  201. package/dist/src/v2/proof-store.d.ts.map +0 -1
  202. package/dist/src/v2/proof-store.js +0 -180
  203. package/dist/src/v2/proof-store.js.map +0 -1
  204. package/dist/src/v2/route-continuations.d.ts +0 -32
  205. package/dist/src/v2/route-continuations.d.ts.map +0 -1
  206. package/dist/src/v2/route-continuations.js +0 -2
  207. package/dist/src/v2/route-continuations.js.map +0 -1
  208. package/dist/src/v2/route-coordinator.d.ts +0 -77
  209. package/dist/src/v2/route-coordinator.d.ts.map +0 -1
  210. package/dist/src/v2/route-coordinator.js +0 -370
  211. package/dist/src/v2/route-coordinator.js.map +0 -1
  212. package/dist/src/v2/route-decision.d.ts +0 -129
  213. package/dist/src/v2/route-decision.d.ts.map +0 -1
  214. package/dist/src/v2/route-decision.js +0 -400
  215. package/dist/src/v2/route-decision.js.map +0 -1
  216. package/dist/src/v2/spec-coordinator.d.ts +0 -85
  217. package/dist/src/v2/spec-coordinator.d.ts.map +0 -1
  218. package/dist/src/v2/spec-coordinator.js +0 -88
  219. package/dist/src/v2/spec-coordinator.js.map +0 -1
  220. package/dist/src/v2/spec-delivery.d.ts +0 -143
  221. package/dist/src/v2/spec-delivery.d.ts.map +0 -1
  222. package/dist/src/v2/spec-delivery.js +0 -401
  223. package/dist/src/v2/spec-delivery.js.map +0 -1
  224. package/dist/src/v2/triage-route.d.ts +0 -68
  225. package/dist/src/v2/triage-route.d.ts.map +0 -1
  226. package/dist/src/v2/triage-route.js +0 -223
  227. package/dist/src/v2/triage-route.js.map +0 -1
  228. package/dist/src/v2/waiting-human-coordinator.d.ts +0 -49
  229. package/dist/src/v2/waiting-human-coordinator.d.ts.map +0 -1
  230. package/dist/src/v2/waiting-human-coordinator.js +0 -509
  231. package/dist/src/v2/waiting-human-coordinator.js.map +0 -1
  232. package/dist/src/v2/waiting-human.d.ts +0 -143
  233. package/dist/src/v2/waiting-human.d.ts.map +0 -1
  234. package/dist/src/v2/waiting-human.js +0 -408
  235. package/dist/src/v2/waiting-human.js.map +0 -1
  236. package/internal-workflow/docs/agents/contract-test-ledger.md +0 -71
  237. package/internal-workflow/docs/agents/review-gates.md +0 -42
  238. package/internal-workflow/docs/agents/review-protocol.md +0 -98
  239. package/internal-workflow/evals/coding-skill-evals.json +0 -332
  240. package/internal-workflow/operations/ambiguity-review/SKILL.md +0 -5
  241. package/internal-workflow/operations/qualification-repair/SKILL.md +0 -17
  242. package/internal-workflow/operations/spec-author/SKILL.md +0 -12
  243. package/internal-workflow/operations/spec-review/SKILL.md +0 -12
  244. package/internal-workflow/operations/triage/SKILL.md +0 -12
  245. package/internal-workflow/profiles/analyst_deep.toml +0 -9
  246. package/internal-workflow/profiles/implementer_standard.toml +0 -9
  247. package/internal-workflow/profiles/proof_agent.toml +0 -8
  248. package/internal-workflow/profiles/reviewer_deep.toml +0 -9
  249. package/internal-workflow/profiles/reviewer_standard.toml +0 -9
  250. package/internal-workflow/schemas/ambiguity-review-v1.json +0 -1
  251. package/internal-workflow/schemas/spec-author-v1.json +0 -1
  252. package/internal-workflow/schemas/spec-review-v1.json +0 -30
  253. package/internal-workflow/schemas/triage-route-v1.json +0 -1
  254. package/internal-workflow/skills/agent-auto/SKILL.md +0 -19
  255. package/internal-workflow/skills/agent-auto/agents/openai.yaml +0 -6
  256. package/internal-workflow/skills/code-debugger/SKILL.md +0 -122
  257. package/internal-workflow/skills/code-debugger/agents/openai.yaml +0 -7
  258. package/internal-workflow/skills/code-review/references/bug-classes.md +0 -56
  259. package/internal-workflow/skills/code-review/references/cleanup-lens.md +0 -52
  260. package/internal-workflow/skills/code-review/references/framework-lenses.md +0 -34
  261. package/internal-workflow/skills/code-review/references/targeted-recipes.md +0 -49
  262. package/internal-workflow/skills/implementation-spec-maker/SKILL.md +0 -107
  263. package/internal-workflow/skills/implementation-spec-maker/agents/openai.yaml +0 -6
  264. package/internal-workflow/skills/implementation-spec-maker/references/source-modes.md +0 -32
  265. package/internal-workflow/skills/implementation-spec-maker/references/spec-template.md +0 -146
  266. package/internal-workflow/skills/implementation-spec-review/SKILL.md +0 -131
  267. package/internal-workflow/skills/implementation-spec-review/agents/openai.yaml +0 -6
  268. package/internal-workflow/skills/implementation-spec-review/evals/evals.json +0 -78
  269. package/internal-workflow/skills/implementation-spec-review/references/review-loop.md +0 -121
  270. package/internal-workflow/skills/small-task-implementer/SKILL.md +0 -112
  271. package/internal-workflow/skills/small-task-implementer/agents/openai.yaml +0 -6
  272. package/internal-workflow/skills/spec-implementer/SKILL.md +0 -126
  273. package/internal-workflow/skills/spec-implementer/agents/openai.yaml +0 -6
  274. package/internal-workflow/skills/spec-implementer/evals/evals.json +0 -30
  275. package/internal-workflow/skills/spec-implementer/references/review-loop.md +0 -100
  276. package/internal-workflow/skills/triage/AGENT-BRIEF.md +0 -192
  277. package/internal-workflow/skills/triage/OUT-OF-SCOPE.md +0 -101
  278. package/internal-workflow/skills/triage/SKILL.md +0 -134
  279. package/internal-workflow/skills/triage/agents/openai.yaml +0 -6
@@ -1,78 +1,103 @@
1
+ import { validateActiveAttempt } from './active-attempt.js';
1
2
  import { validateDirectReview } from './direct-delivery.js';
2
- import { validateSpecDelivery } from './spec-delivery.js';
3
- import { validateRouteExecution, validateRouteReceipt, validateRouteStateInvariant, } from './route-decision.js';
4
- import { validateWaitingHumanExecution } from './waiting-human.js';
3
+ import { validateDeliveryAuthority } from './delivery-authority.js';
4
+ import { constants } from 'node:fs';
5
5
  import { posix } from 'node:path';
6
+ import { open } from 'node:fs/promises';
6
7
  import { AtomicStateFile } from './atomic-store.js';
7
- import { createReviewFeedbackBootstrap, validateImplementationInvocation, validateReviewFeedbackExecution, } from './review-feedback.js';
8
+ import { canonicalJson, sha256 } from './containment.js';
9
+ import { validateCandidateBinding, validateCandidateMaterialization } from './candidate.js';
10
+ import { validateReviewFeedbackRunData, } from './review-feedback.js';
8
11
  const SHA256_PATTERN = /^[0-9a-f]{64}$/;
9
12
  const GIT_SHA_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
10
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
+ }
11
17
  export class WorkflowGenerationUnrecoverableError extends Error {
12
18
  constructor() {
13
19
  super('workflow-generation-unrecoverable');
14
20
  this.name = 'WorkflowGenerationUnrecoverableError';
15
21
  }
16
22
  }
17
- export class RouteInitializationUnrecoverableError extends Error {
23
+ export class IssueInitializationUnrecoverableError extends Error {
18
24
  constructor() {
19
- super('route-initialization-unrecoverable');
20
- this.name = 'RouteInitializationUnrecoverableError';
25
+ super('issue-initialization-unrecoverable');
26
+ this.name = 'IssueInitializationUnrecoverableError';
21
27
  }
22
28
  }
23
29
  export class FileRunRecordWriter {
24
30
  file;
31
+ maxBytes;
25
32
  constructor(path, options = {}) {
26
33
  this.file = new AtomicStateFile(path, validateRunStateFile, options);
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 };
43
+ try {
44
+ return { status: 'supported', rawSha256, state: parseRawState(bytes) };
45
+ }
46
+ catch {
47
+ return { status: 'unsupported', rawSha256 };
48
+ }
27
49
  }
28
50
  async read() {
29
- return await this.file.read() ?? emptyRunState();
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');
30
57
  }
31
58
  async compareAndSwap(expectedGeneration, next) {
32
59
  validateRunStateBody(next);
33
- return this.file.compareAndSwap(expectedGeneration, { ...structuredClone(next), generation: expectedGeneration + 1 });
60
+ const candidate = { ...structuredClone(next), generation: expectedGeneration + 1 };
61
+ return this.file.compareAndSwap(expectedGeneration, candidate);
34
62
  }
35
63
  }
36
64
  export class InMemoryRunRecordWriter {
37
- 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
+ }
38
76
  async read() {
39
- return structuredClone(this.state);
77
+ return structuredClone(this.state ?? emptyRunState());
40
78
  }
41
79
  async compareAndSwap(expectedGeneration, next) {
42
- if (this.state.generation !== expectedGeneration)
80
+ if ((this.state?.generation ?? 0) !== expectedGeneration)
43
81
  throw new Error('run state generation conflict');
82
+ validateRunStateBody(next);
44
83
  const value = validateRunStateFile({ ...structuredClone(next), generation: expectedGeneration + 1 });
45
84
  this.state = value;
46
85
  return structuredClone(value);
47
86
  }
48
87
  }
49
88
  export function validateRunStateFile(value) {
50
- assertExactObject(value, ['schema', 'version', 'generation', 'runs'], 'run state');
51
- if (value.schema !== 'codex-orchestrator.agent-auto-state' || (value.version !== 1 && value.version !== 2))
52
- 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');
53
92
  if (!Number.isSafeInteger(value.generation) || value.generation <= 0)
54
93
  throw new Error('run state generation is invalid');
55
94
  validateRuns(value.runs);
56
- if (value.version === 1) {
57
- for (const run of value.runs) {
58
- if (hasOwn(run, 'reviewFeedback'))
59
- throw new Error('V1 run state cannot contain review feedback');
60
- }
61
- return {
62
- schema: 'codex-orchestrator.agent-auto-state',
63
- version: 2,
64
- generation: value.generation,
65
- runs: value.runs.map((run) => run.lifecycle === 'review-ready'
66
- ? { ...structuredClone(run), reviewFeedback: createReviewFeedbackBootstrap() }
67
- : structuredClone(run)),
68
- };
69
- }
70
95
  return value;
71
96
  }
72
97
  function validateRunStateBody(value) {
73
- assertExactObject(value, ['schema', 'version', 'runs'], 'run state body');
74
- if (value.schema !== 'codex-orchestrator.agent-auto-state' || value.version !== 2)
75
- 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');
76
101
  validateRuns(value.runs);
77
102
  }
78
103
  function validateRuns(value) {
@@ -88,21 +113,20 @@ function validateRuns(value) {
88
113
  }
89
114
  function validateRunRecord(value, field) {
90
115
  const optional = [
91
- 'process',
116
+ 'activeAttempt',
92
117
  'checkedChangeSha256',
93
118
  'proofId',
119
+ 'proofExecution',
94
120
  'proofReceipt',
95
- 'intent',
121
+ 'pendingEffect',
96
122
  'outcomeEvidenceId',
97
123
  'terminalOutcome',
98
- 'routeExecution',
99
- 'routeReceipt',
100
- 'waitingHuman',
124
+ 'deliveryAuthority',
101
125
  'directReview',
102
- 'specDelivery',
103
126
  'reviewFeedback',
104
- 'checkQualification',
105
- 'baselineChecks',
127
+ 'changeBindingVersion',
128
+ 'candidateBinding',
129
+ 'candidateMaterialization',
106
130
  ].filter((key) => hasOwn(value, key));
107
131
  assertExactObject(value, [
108
132
  'runId', 'issueNumber', 'canonicalRepository', 'baseSha', 'branchName', 'worktreePath', 'lifecycle', 'cycle',
@@ -122,11 +146,11 @@ function validateRunRecord(value, field) {
122
146
  }
123
147
  if (!isLifecycle(value.lifecycle))
124
148
  throw new Error(`${field}.lifecycle is invalid`);
125
- if (!Number.isSafeInteger(value.cycle) || value.cycle < 1 || value.cycle > 5)
149
+ if (!Number.isSafeInteger(value.cycle) || value.cycle < 1)
126
150
  throw new Error(`${field}.cycle is invalid`);
127
- if (value.reportRepairs !== 0 && value.reportRepairs !== 1)
151
+ if (!Number.isSafeInteger(value.reportRepairs) || value.reportRepairs < 0)
128
152
  throw new Error(`${field}.reportRepairs is invalid`);
129
- if (value.transportRetries !== 0 && value.transportRetries !== 1)
153
+ if (!Number.isSafeInteger(value.transportRetries) || value.transportRetries < 0)
130
154
  throw new Error(`${field}.transportRetries is invalid`);
131
155
  validateIssueSnapshot(value.issueSnapshot, `${field}.issueSnapshot`);
132
156
  validateFrozenCriteria(value.frozenCriteria, `${field}.frozenCriteria`);
@@ -137,80 +161,91 @@ function validateRunRecord(value, field) {
137
161
  if (workflowGeneration.packageVersion !== value.packageVersion) {
138
162
  throw new Error(`${field}.workflowGeneration package version mismatch`);
139
163
  }
140
- const routeGenerationHash = workflowGeneration.generationHash;
141
- if (hasOwn(value, 'routeExecution'))
142
- validateRouteExecution(value.routeExecution, routeGenerationHash);
143
- if (hasOwn(value, 'routeReceipt'))
144
- 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
+ }
145
177
  validateStringShaRecord(value.skillHashes, `${field}.skillHashes`);
146
- if (hasOwn(value, 'baselineChecks'))
147
- validateChecks(value.baselineChecks, `${field}.baselineChecks`, false);
148
178
  validateChecks(value.checks, `${field}.checks`);
149
- if (hasOwn(value, 'process'))
150
- validateProcess(value.process, `${field}.process`);
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`);
183
+ }
151
184
  if (hasOwn(value, 'checkedChangeSha256'))
152
185
  assertSha256(value.checkedChangeSha256, `${field}.checkedChangeSha256`);
153
186
  if (hasOwn(value, 'proofId'))
154
187
  assertNonEmptyString(value.proofId, `${field}.proofId`);
188
+ if (hasOwn(value, 'proofExecution'))
189
+ validateProofExecution(value.proofExecution, `${field}.proofExecution`);
155
190
  if (hasOwn(value, 'proofReceipt'))
156
191
  validateReceipt(value.proofReceipt, `${field}.proofReceipt`);
157
- if (hasOwn(value, 'intent'))
158
- validateIntent(value.intent, `${field}.intent`);
192
+ if (hasOwn(value, 'pendingEffect'))
193
+ validatePendingEffect(value.pendingEffect, `${field}.pendingEffect`);
159
194
  if (hasOwn(value, 'outcomeEvidenceId'))
160
195
  assertNonEmptyString(value.outcomeEvidenceId, `${field}.outcomeEvidenceId`);
161
196
  if (hasOwn(value, 'terminalOutcome'))
162
197
  validateTerminalOutcome(value.terminalOutcome, `${field}.terminalOutcome`);
163
- if (hasOwn(value, 'waitingHuman')) {
164
- if (!routeGenerationHash)
165
- throw new WorkflowGenerationUnrecoverableError();
166
- validateWaitingHumanExecution(value.waitingHuman, {
167
- runId: value.runId,
168
- lifecycle: value.lifecycle,
169
- workflowGenerationHash: routeGenerationHash,
170
- routeReceipt: hasOwn(value, 'routeReceipt') ? value.routeReceipt : undefined,
171
- terminalOutcome: hasOwn(value, 'terminalOutcome') ? value.terminalOutcome : undefined,
172
- });
173
- }
174
198
  if (hasOwn(value, 'directReview')) {
175
- if (!hasOwn(value, 'routeReceipt') || value.routeReceipt.route !== 'direct') {
176
- throw new Error(`${field}.directReview requires a direct route`);
177
- }
178
- const rawProcess = hasOwn(value, 'process') && hasOwn(value.process, 'purpose')
179
- ? value.process
180
- : undefined;
181
- const process = rawProcess && !['spec-author', 'spec-review'].includes(rawProcess.purpose)
182
- ? {
183
- purpose: rawProcess.purpose,
184
- resumeLifecycle: rawProcess.resumeLifecycle,
185
- resumeReviewStage: rawProcess.resumeReviewStage,
186
- }
187
- : undefined;
199
+ if (!hasOwn(value, 'deliveryAuthority'))
200
+ throw new Error(`${field}.directReview requires delivery authority`);
188
201
  validateDirectReview(value.directReview, {
189
202
  lifecycle: value.lifecycle,
190
203
  ...(hasOwn(value, 'terminalOutcome') ? { terminalOutcome: directTerminalOutcome(value.terminalOutcome) } : {}),
191
- ...(process ? { process: {
192
- purpose: process.purpose,
193
- resumeLifecycle: process.resumeLifecycle,
194
- resumeReviewStage: process.resumeReviewStage,
195
- } } : {}),
204
+ ...(hasOwn(value, 'terminalOutcome') && value.terminalOutcome.status === 'internal-error'
205
+ ? { terminalCode: value.terminalOutcome.code }
206
+ : {}),
196
207
  });
197
208
  }
198
- if (hasOwn(value, 'specDelivery')) {
199
- if (!hasOwn(value, 'routeReceipt') || value.routeReceipt.route !== 'spec-required') {
200
- throw new Error(`${field}.specDelivery requires a spec-required route`);
209
+ if (hasOwn(value, 'reviewFeedback')) {
210
+ validateReviewFeedbackRunData(value.reviewFeedback);
211
+ validateReviewFeedbackRunInvariant(value, field);
212
+ }
213
+ if (hasOwn(value, 'changeBindingVersion') && value.changeBindingVersion !== 2)
214
+ throw new Error(`${field}.changeBindingVersion is invalid`);
215
+ if (hasOwn(value, 'candidateBinding'))
216
+ validateCandidateBinding(value.candidateBinding, `${field}.candidateBinding`, value.runId);
217
+ if (hasOwn(value, 'candidateMaterialization'))
218
+ validateCandidateMaterialization(value.candidateMaterialization, `${field}.candidateMaterialization`);
219
+ if (hasOwn(value, 'candidateBinding') !== hasOwn(value, 'changeBindingVersion'))
220
+ throw new Error(`${field} candidate binding version is incomplete`);
221
+ if (hasOwn(value, 'candidateMaterialization')) {
222
+ if (!hasOwn(value, 'candidateBinding'))
223
+ throw new Error(`${field}.candidateMaterialization requires candidate binding`);
224
+ const binding = value.candidateBinding;
225
+ const materialization = value.candidateMaterialization;
226
+ if (materialization.bindingId !== binding.bindingId || materialization.candidateCommitSha !== binding.candidateCommitSha) {
227
+ throw new Error(`${field}.candidateMaterialization binding is invalid`);
201
228
  }
202
- const spec = validateSpecDelivery(value.specDelivery);
203
- if (spec.issueNumber !== value.issueNumber || spec.runId !== value.runId
204
- || spec.workflowGenerationSha256 !== routeGenerationHash) {
205
- throw new Error(`${field}.specDelivery identity binding is invalid`);
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`);
206
237
  }
207
238
  }
208
- if (hasOwn(value, 'reviewFeedback')) {
209
- validateReviewFeedbackExecution(value.reviewFeedback);
210
- validateReviewFeedbackRunInvariant(value, field);
239
+ if (value.pendingEffect?.kind === 'candidate-pin-release') {
240
+ if (!hasOwn(value, 'candidateBinding'))
241
+ throw new Error(`${field}.candidate pin cleanup requires candidate binding`);
242
+ const binding = value.candidateBinding;
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`);
247
+ }
211
248
  }
212
- if (hasOwn(value, 'checkQualification'))
213
- validateCheckQualification(value.checkQualification, `${field}.checkQualification`);
214
249
  assertTimestamp(value.createdAt, `${field}.createdAt`);
215
250
  assertTimestamp(value.updatedAt, `${field}.updatedAt`);
216
251
  const terminal = ['review-ready', 'blocked', 'transport-failed', 'cancelled', 'internal-error'].includes(value.lifecycle);
@@ -222,29 +257,32 @@ function validateRunRecord(value, field) {
222
257
  || value.checks.some((check) => check.status === 'failed'))) {
223
258
  throw new Error(`${field} proving requires passed checks and checked change proof identity`);
224
259
  }
225
- if (value.lifecycle === 'publishing' && !hasOwn(value, 'proofReceipt'))
226
- throw new Error(`${field} publishing requires proofReceipt`);
227
- if (value.lifecycle === 'safe-halt' && !hasOwn(value, 'process'))
228
- throw new Error(`${field} safe-halt requires retained process evidence`);
229
- if (value.lifecycle === 'review-ready' && (!hasOwn(value, 'proofReceipt') || hasOwn(value, 'intent'))) {
230
- throw new Error(`${field} review-ready requires proofReceipt and no intent`);
231
- }
232
- if (terminal && hasOwn(value, 'process'))
233
- throw new Error(`${field} terminal lifecycle cannot retain process ownership`);
234
- if (terminal && hasOwn(value, 'intent') && value.lifecycle !== 'transport-failed')
235
- throw new Error(`${field} terminal lifecycle cannot retain intent`);
236
- if (value.lifecycle === 'transport-failed' && hasOwn(value, 'intent')
260
+ if ((value.lifecycle === 'reviewing' || value.lifecycle === 'publishing') && !hasOwn(value, 'proofReceipt')) {
261
+ throw new Error(`${field} review and publication require proofReceipt`);
262
+ }
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'
272
+ && value.terminalOutcome?.status === 'blocked'
273
+ && value.terminalOutcome.kind === 'safety'
274
+ && !value.terminalOutcome.resumable
275
+ && hasOwn(value, 'candidateBinding')
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
237
283
  && value.terminalOutcome.resumable) {
238
- throw new Error(`${field} resumable transport failure cannot retain intent`);
284
+ throw new Error(`${field} resumable transport failure cannot retain pending effect`);
239
285
  }
240
- if (value.lifecycle === 'waiting-human' && !hasOwn(value, 'waitingHuman'))
241
- throw new Error(`${field} waiting-human lifecycle requires waitingHuman execution`);
242
- validateRouteStateInvariant({
243
- lifecycle: value.lifecycle,
244
- routeExecution: value.routeExecution,
245
- routeReceipt: value.routeReceipt,
246
- generationHash: routeGenerationHash,
247
- });
248
286
  }
249
287
  function directTerminalOutcome(outcome) {
250
288
  if (outcome.status === 'review-ready')
@@ -265,83 +303,31 @@ function validateWorkflowGeneration(value, field) {
265
303
  }
266
304
  assertSha256(value.contentSha256, `${field}.contentSha256`);
267
305
  }
268
- function validateProcess(value, field) {
269
- assertExactObject(value, [
270
- 'pid', 'processGroupId', 'startedAt', 'baseline', 'purpose', 'resumeLifecycle', 'resumeReviewStage',
271
- ], field);
272
- assertPositiveInteger(value.pid, `${field}.pid`);
273
- assertPositiveInteger(value.processGroupId, `${field}.processGroupId`);
274
- assertTimestamp(value.startedAt, `${field}.startedAt`);
275
- assertExactObject(value.baseline, [
276
- 'headSha', 'indexTreeSha', 'trackedContentSha256', 'untrackedContentSha256', 'worktreeIdentity',
277
- ], `${field}.baseline`);
278
- assertGitSha(value.baseline.headSha, `${field}.baseline.headSha`);
279
- assertGitSha(value.baseline.indexTreeSha, `${field}.baseline.indexTreeSha`);
280
- assertSha256(value.baseline.trackedContentSha256, `${field}.baseline.trackedContentSha256`);
281
- assertSha256(value.baseline.untrackedContentSha256, `${field}.baseline.untrackedContentSha256`);
282
- assertNonEmptyString(value.baseline.worktreeIdentity, `${field}.baseline.worktreeIdentity`);
283
- if (!['route', 'implementation', 'code-review', 'proof', 'spec-author', 'spec-review'].includes(value.purpose)) {
284
- throw new Error(`${field}.purpose is invalid`);
285
- }
286
- if (!isLifecycle(value.resumeLifecycle))
287
- throw new Error(`${field}.resumeLifecycle is invalid`);
288
- if (value.resumeReviewStage !== null && ![
289
- 'review-full', 'review-repair', 'review-closure',
290
- ].includes(value.resumeReviewStage))
291
- throw new Error(`${field}.resumeReviewStage is invalid`);
292
- }
293
- function validateChecks(value, field, allowUnchangedFailure = true) {
306
+ function validateChecks(value, field) {
294
307
  if (!Array.isArray(value) || value.length > 256)
295
308
  throw new Error(`${field} is invalid`);
296
309
  const ids = new Set();
297
310
  for (const [index, check] of value.entries()) {
298
- assertExactObject(check, ['id', 'command', 'status', 'outputSha256'], `${field}[${index}]`);
311
+ const candidate = hasOwn(check, 'bindingId') || hasOwn(check, 'candidateTreeSha') || hasOwn(check, 'checkPolicySha256');
312
+ assertExactObject(check, [
313
+ 'id', 'command', 'status', 'outputSha256',
314
+ ...(candidate ? ['bindingId', 'candidateTreeSha', 'checkPolicySha256'] : []),
315
+ ], `${field}[${index}]`);
299
316
  assertNonEmptyString(check.id, `${field}[${index}].id`);
300
317
  assertNonEmptyString(check.command, `${field}[${index}].command`);
301
- if (check.status !== 'passed' && check.status !== 'failed'
302
- && (check.status !== 'unchanged-failure' || !allowUnchangedFailure))
318
+ if (check.status !== 'passed' && check.status !== 'failed')
303
319
  throw new Error(`${field}[${index}].status is invalid`);
304
320
  assertSha256(check.outputSha256, `${field}[${index}].outputSha256`);
321
+ if (candidate) {
322
+ assertSha256(check.bindingId, `${field}[${index}].bindingId`);
323
+ assertGitSha(check.candidateTreeSha, `${field}[${index}].candidateTreeSha`);
324
+ assertSha256(check.checkPolicySha256, `${field}[${index}].checkPolicySha256`);
325
+ }
305
326
  if (ids.has(check.id))
306
327
  throw new Error(`${field} IDs must be unique`);
307
328
  ids.add(check.id);
308
329
  }
309
330
  }
310
- function validateCheckQualification(value, field) {
311
- const optional = [
312
- ...(hasOwn(value, 'implementationStarted') ? ['implementationStarted'] : []),
313
- ...(hasOwn(value, 'repairInvocation') ? ['repairInvocation'] : []),
314
- ...(hasOwn(value, 'deniedPathsBaseline') ? ['deniedPathsBaseline'] : []),
315
- ];
316
- assertExactObject(value, ['version', 'checkPolicySha256', 'repairAttempts', 'checks', ...optional], field);
317
- if (value.version !== 1)
318
- throw new Error(`${field}.version is invalid`);
319
- assertSha256(value.checkPolicySha256, `${field}.checkPolicySha256`);
320
- if (!Number.isSafeInteger(value.repairAttempts) || value.repairAttempts < 0 || value.repairAttempts > 5) {
321
- throw new Error(`${field}.repairAttempts is invalid`);
322
- }
323
- validateChecks(value.checks, `${field}.checks`, false);
324
- if (hasOwn(value, 'implementationStarted') && typeof value.implementationStarted !== 'boolean') {
325
- throw new Error(`${field}.implementationStarted is invalid`);
326
- }
327
- if (hasOwn(value, 'repairInvocation') && value.repairInvocation !== null) {
328
- validateImplementationInvocation(value.repairInvocation);
329
- }
330
- if (hasOwn(value, 'deniedPathsBaseline'))
331
- assertSha256(value.deniedPathsBaseline, `${field}.deniedPathsBaseline`);
332
- if (hasOwn(value, 'deniedPathsBaseline') !== (hasOwn(value, 'repairInvocation') && value.repairInvocation !== null)) {
333
- throw new Error(`${field}.deniedPathsBaseline requires a repair invocation`);
334
- }
335
- const repairInvocation = hasOwn(value, 'repairInvocation')
336
- ? value.repairInvocation
337
- : undefined;
338
- if (value.implementationStarted === true && repairInvocation !== null && repairInvocation !== undefined) {
339
- throw new Error(`${field} cannot own qualification repair and implementation launches together`);
340
- }
341
- if (repairInvocation?.phase === 'launched' && value.repairAttempts === 0) {
342
- throw new Error(`${field}.repairAttempts must reserve a launched repair`);
343
- }
344
- }
345
331
  function validateIssueSnapshot(value, field) {
346
332
  const optional = hasOwn(value, 'comments') ? ['comments'] : [];
347
333
  assertExactObject(value, ['number', 'title', 'body', 'url', 'state', 'labels', ...optional], field);
@@ -398,53 +384,58 @@ function validateStringList(value, field) {
398
384
  for (const item of value)
399
385
  assertNonEmptyString(item, field);
400
386
  }
401
- function validateIntent(value, field) {
387
+ function validatePendingEffect(value, field) {
402
388
  if (typeof value !== 'object' || value === null || Array.isArray(value))
403
389
  throw new Error(`${field} is invalid`);
404
390
  const kind = value.kind;
405
- if (kind === 'claim-labels' || kind === 'labels') {
406
- 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);
407
394
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
408
395
  validateStringArray(value.expected, `${field}.expected`);
409
396
  }
410
- else if (kind === 'commit') {
411
- assertExactObject(value, ['kind', 'parentSha', 'treeSha', 'message'], field);
397
+ else if (kind === 'initial-commit') {
398
+ assertExactObject(value, [...identity, 'parentSha', 'treeSha', 'message', ...(hasOwn(value, 'candidateRef') ? ['candidateRef'] : [])], field);
412
399
  assertGitSha(value.parentSha, `${field}.parentSha`);
413
400
  assertGitSha(value.treeSha, `${field}.treeSha`);
414
401
  assertNonEmptyString(value.message, `${field}.message`);
402
+ if (hasOwn(value, 'candidateRef'))
403
+ assertCandidateRef(value.candidateRef, `${field}.candidateRef`);
415
404
  }
416
- else if (kind === 'push') {
417
- assertExactObject(value, ['kind', 'branch', 'sha'], field);
405
+ else if (kind === 'initial-push') {
406
+ assertExactObject(value, [...identity, 'branch', 'sha'], field);
418
407
  assertNonEmptyString(value.branch, `${field}.branch`);
419
408
  assertGitSha(value.sha, `${field}.sha`);
420
409
  }
421
- else if (kind === 'pr') {
422
- 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);
423
412
  for (const key of ['owner', 'repo', 'head', 'base', 'marker'])
424
413
  assertNonEmptyString(value[key], `${field}.${key}`);
425
414
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
426
415
  }
427
- else if (kind === 'comment') {
428
- assertExactObject(value, ['kind', 'issueNumber', 'marker', 'bodySha256'], field);
416
+ else if (kind === 'claim-comment' || kind === 'handoff-comment') {
417
+ assertExactObject(value, [...identity, 'issueNumber', 'marker', 'bodySha256'], field);
429
418
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
430
419
  assertNonEmptyString(value.marker, `${field}.marker`);
431
420
  assertSha256(value.bodySha256, `${field}.bodySha256`);
432
421
  }
433
422
  else if (kind === 'review-activation-labels') {
434
- assertExactObject(value, ['kind', 'issueNumber', 'batchId', 'expected'], field);
423
+ assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'expected'], field);
435
424
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
436
425
  assertSha256(value.batchId, `${field}.batchId`);
437
426
  validateStringArray(value.expected, `${field}.expected`);
438
427
  }
439
428
  else if (kind === 'review-update-commit') {
440
- assertExactObject(value, ['kind', 'batchId', 'parentSha', 'treeSha', 'message'], field);
429
+ assertExactObject(value, [...identity, 'batchId', 'parentSha', 'treeSha', 'message', ...(hasOwn(value, 'candidateRef') ? ['candidateRef'] : [])], field);
441
430
  assertSha256(value.batchId, `${field}.batchId`);
442
431
  assertGitSha(value.parentSha, `${field}.parentSha`);
443
432
  assertGitSha(value.treeSha, `${field}.treeSha`);
444
433
  assertNonEmptyString(value.message, `${field}.message`);
434
+ if (hasOwn(value, 'candidateRef'))
435
+ assertCandidateRef(value.candidateRef, `${field}.candidateRef`);
445
436
  }
446
437
  else if (kind === 'review-update-push') {
447
- assertExactObject(value, ['kind', 'batchId', 'branch', 'priorRemoteSha', 'sha', 'treeSha'], field);
438
+ assertExactObject(value, [...identity, 'batchId', 'branch', 'priorRemoteSha', 'sha', 'treeSha'], field);
448
439
  assertSha256(value.batchId, `${field}.batchId`);
449
440
  assertNonEmptyString(value.branch, `${field}.branch`);
450
441
  assertGitSha(value.priorRemoteSha, `${field}.priorRemoteSha`);
@@ -452,7 +443,7 @@ function validateIntent(value, field) {
452
443
  assertGitSha(value.treeSha, `${field}.treeSha`);
453
444
  }
454
445
  else if (kind === 'review-summary') {
455
- assertExactObject(value, ['kind', 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'marker', 'bodySha256', 'epochHeadSha'], field);
446
+ assertExactObject(value, [...identity, 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'marker', 'bodySha256', 'epochHeadSha'], field);
456
447
  assertSha256(value.batchId, `${field}.batchId`);
457
448
  assertPositiveInteger(value.pullRequestNumber, `${field}.pullRequestNumber`);
458
449
  assertNonEmptyString(value.pullRequestNodeId, `${field}.pullRequestNodeId`);
@@ -461,7 +452,7 @@ function validateIntent(value, field) {
461
452
  assertGitSha(value.epochHeadSha, `${field}.epochHeadSha`);
462
453
  }
463
454
  else if (kind === 'review-final-labels') {
464
- assertExactObject(value, ['kind', 'issueNumber', 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'epochHeadSha', 'expected'], field);
455
+ assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'pullRequestNumber', 'pullRequestNodeId', 'epochHeadSha', 'expected'], field);
465
456
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
466
457
  assertSha256(value.batchId, `${field}.batchId`);
467
458
  assertPositiveInteger(value.pullRequestNumber, `${field}.pullRequestNumber`);
@@ -470,27 +461,61 @@ function validateIntent(value, field) {
470
461
  validateStringArray(value.expected, `${field}.expected`);
471
462
  }
472
463
  else if (kind === 'review-blocked-labels') {
473
- assertExactObject(value, ['kind', 'issueNumber', 'batchId', 'expected', 'blockKind', 'evidenceCode'], field);
464
+ assertExactObject(value, [...identity, 'issueNumber', 'batchId', 'expected', 'blockKind', 'evidenceCode'], field);
474
465
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
475
466
  assertSha256(value.batchId, `${field}.batchId`);
476
467
  validateStringArray(value.expected, `${field}.expected`);
477
- if (value.blockKind !== 'safety' && value.blockKind !== 'exhausted')
468
+ if (!['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.blockKind)) {
478
469
  throw new Error(`${field}.blockKind is invalid`);
470
+ }
479
471
  assertNonEmptyString(value.evidenceCode, `${field}.evidenceCode`);
480
472
  }
481
473
  else if (kind === 'blocked-labels') {
482
- assertExactObject(value, ['kind', 'issueNumber', 'expected', 'blockKind', 'resumable', 'evidenceCode'], field);
474
+ assertExactObject(value, [...identity, 'issueNumber', 'expected', 'blockKind', 'resumable', 'evidenceCode'], field);
483
475
  assertPositiveInteger(value.issueNumber, `${field}.issueNumber`);
484
476
  validateStringArray(value.expected, `${field}.expected`);
485
- if (!['external', 'safety', 'exhausted'].includes(value.blockKind))
477
+ if (!['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.blockKind))
486
478
  throw new Error(`${field}.blockKind is invalid`);
487
479
  if (typeof value.resumable !== 'boolean')
488
480
  throw new Error(`${field}.resumable is invalid`);
489
481
  assertNonEmptyString(value.evidenceCode, `${field}.evidenceCode`);
490
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
+ }
491
512
  else {
492
513
  throw new Error(`${field}.kind is invalid`);
493
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`);
494
519
  }
495
520
  function validateTerminalOutcome(value, field) {
496
521
  if (typeof value !== 'object' || value === null || Array.isArray(value))
@@ -503,11 +528,13 @@ function validateTerminalOutcome(value, field) {
503
528
  assertGitSha(value.continuationEpoch, `${field}.continuationEpoch`);
504
529
  }
505
530
  else if (status === 'blocked') {
506
- assertExactObject(value, ['status', 'kind', 'resumable', 'evidencePath'], field);
507
- 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))
508
533
  throw new Error(`${field}.kind is invalid`);
509
534
  if (typeof value.resumable !== 'boolean')
510
535
  throw new Error(`${field}.resumable is invalid`);
536
+ if (hasOwn(value, 'blocker'))
537
+ validateBlockerDetail(value.blocker, `${field}.blocker`);
511
538
  }
512
539
  else if (status === 'transport-failed') {
513
540
  assertExactObject(value, ['status', 'resumable', 'evidencePath'], field);
@@ -526,6 +553,19 @@ function validateTerminalOutcome(value, field) {
526
553
  }
527
554
  assertNonEmptyString(value.evidencePath, `${field}.evidencePath`);
528
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
+ }
529
569
  function validateReceipt(value, field) {
530
570
  assertExactObject(value, ['proofId', 'bindingSha256', 'summary', 'publishableEvidence', 'localEvidenceId'], field);
531
571
  assertNonEmptyString(value.proofId, `${field}.proofId`);
@@ -543,6 +583,18 @@ function validateReceipt(value, field) {
543
583
  assertNonEmptyString(evidence.description, `${field}.publishableEvidence.description`);
544
584
  }
545
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
+ }
546
598
  function validateStringShaRecord(value, field) {
547
599
  if (typeof value !== 'object' || value === null || Array.isArray(value))
548
600
  throw new Error(`${field} is invalid`);
@@ -563,7 +615,40 @@ function validateStringArray(value, field) {
563
615
  throw new Error(`${field} must be sorted and unique`);
564
616
  }
565
617
  function emptyRunState() {
566
- return { schema: 'codex-orchestrator.agent-auto-state', version: 2, generation: 0, runs: [] };
618
+ return { schema: 'codex-orchestrator.run-state', generation: 0, runs: [] };
619
+ }
620
+ function parseRawState(bytes) {
621
+ let value;
622
+ try {
623
+ value = JSON.parse(bytes.toString('utf8'));
624
+ }
625
+ catch {
626
+ throw new Error('run state JSON is malformed');
627
+ }
628
+ return validateRunStateFile(value);
629
+ }
630
+ async function readOptionalStateFile(path) {
631
+ let handle;
632
+ try {
633
+ handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
634
+ }
635
+ catch (error) {
636
+ if (isErrorCode(error, 'ENOENT'))
637
+ return undefined;
638
+ throw error;
639
+ }
640
+ try {
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();
645
+ }
646
+ finally {
647
+ await handle.close();
648
+ }
649
+ }
650
+ function isErrorCode(error, code) {
651
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
567
652
  }
568
653
  function validateReviewFeedbackRunInvariant(run, field) {
569
654
  const feedback = run.reviewFeedback;
@@ -573,22 +658,18 @@ function validateReviewFeedbackRunInvariant(run, field) {
573
658
  || batch.priorPublishedHeadSha !== feedback.previousPublishedHeadSha)) {
574
659
  throw new Error(`${field}.reviewFeedback active batch identity binding is invalid`);
575
660
  }
576
- const retainedQuiescentHistory = (feedback.phase === 'idle' || feedback.phase === 'bootstrap-required') && !batch
577
- && run.lifecycle === 'blocked' && run.terminalOutcome?.status === 'blocked';
578
- if ((feedback.phase === 'bootstrap-required' || feedback.phase === 'idle')
579
- && run.lifecycle !== 'review-ready' && !retainedQuiescentHistory) {
580
- throw new Error(`${field}.reviewFeedback quiescent phase requires review-ready lifecycle`);
581
- }
582
- if (['frozen', 'repairing'].includes(feedback.phase) && !['implementing', 'reworking', 'checking', 'proving', 'safe-halt'].includes(run.lifecycle)) {
583
- throw new Error(`${field}.reviewFeedback active repair phase has invalid 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`);
584
664
  }
585
- if (feedback.phase === 'verified' && run.lifecycle !== 'publishing') {
586
- throw new Error(`${field}.reviewFeedback verified 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`);
587
668
  }
588
- if (feedback.phase === 'publishing' && run.lifecycle !== 'publishing') {
589
- throw new Error(`${field}.reviewFeedback publishing phase requires publishing lifecycle`);
590
- }
591
- 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
+ }
592
673
  const verified = feedback.verifiedReceipt;
593
674
  if (!verified || verified.batchId !== batch?.batchId
594
675
  || verified.checkedChangeSha256 !== run.checkedChangeSha256
@@ -597,16 +678,10 @@ function validateReviewFeedbackRunInvariant(run, field) {
597
678
  throw new Error(`${field}.reviewFeedback verification receipt binding is invalid`);
598
679
  }
599
680
  }
600
- if (feedback.phase === 'blocked-safety' || feedback.phase === 'blocked-exhausted') {
601
- if (run.lifecycle !== 'blocked' || run.terminalOutcome?.status !== 'blocked' || run.terminalOutcome.resumable
602
- || run.terminalOutcome.kind !== (feedback.phase === 'blocked-safety' ? 'safety' : 'exhausted')) {
603
- throw new Error(`${field}.reviewFeedback terminal projection mismatch`);
604
- }
605
- }
606
681
  }
607
682
  function isLifecycle(value) {
608
683
  return typeof value === 'string' && [
609
- 'claimed', 'triaging', 'routed', 'waiting-human', 'spec-authoring', 'implementing', 'reworking', 'checking', 'proving', 'publishing', 'safe-halt',
684
+ 'claimed', 'implementing', 'checking', 'proving', 'reviewing', 'publishing', 'safe-halt',
610
685
  'review-ready', 'blocked', 'transport-failed', 'cancelled', 'internal-error',
611
686
  ].includes(value);
612
687
  }
@@ -625,6 +700,10 @@ function assertNonEmptyString(value, field) {
625
700
  if (typeof value !== 'string' || value.length === 0 || value.length > 16 * 1024)
626
701
  throw new Error(`${field} is invalid`);
627
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
+ }
628
707
  function assertPositiveInteger(value, field) {
629
708
  if (!Number.isSafeInteger(value) || value <= 0)
630
709
  throw new Error(`${field} is invalid`);
@@ -637,6 +716,11 @@ function assertGitSha(value, field) {
637
716
  if (typeof value !== 'string' || !GIT_SHA_PATTERN.test(value))
638
717
  throw new Error(`${field} must be a Git object ID`);
639
718
  }
719
+ function assertCandidateRef(value, field) {
720
+ if (typeof value !== 'string' || !/^refs\/codex-orchestrator\/candidates\/[0-9a-f-]{36}\/[0-9a-f]{64}$/u.test(value)) {
721
+ throw new Error(`${field} is invalid`);
722
+ }
723
+ }
640
724
  function assertTimestamp(value, field) {
641
725
  if (typeof value !== 'string' || Number.isNaN(Date.parse(value)) || new Date(value).toISOString() !== value)
642
726
  throw new Error(`${field} is invalid`);