codex-orchestrator 2.0.11 → 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 (262) hide show
  1. package/CHANGELOG.md +15 -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 +17 -4
  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,35 +1,29 @@
1
1
  import { constants } from 'node:fs';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
- import { execFile, spawn } from 'node:child_process';
3
+ import { spawn } from 'node:child_process';
4
4
  import { access, link, lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, realpath, rm, unlink } from 'node:fs/promises';
5
- import { homedir, hostname, platform } from 'node:os';
5
+ import { homedir, hostname } from 'node:os';
6
6
  import { basename, dirname, isAbsolute, join, posix, relative, resolve } from 'node:path';
7
7
  import { writeDurableAtomicFile } from './adapters/durable-atomic-file.js';
8
8
  import { GitWorktreeManager } from './adapters/worktree.js';
9
- import { ReviewFeedbackCoordinator } from './review-feedback-coordinator.js';
9
+ import { ReviewFeedbackObserver } from './review-feedback-coordinator.js';
10
10
  import { defaultProcessExecutor } from './adapters/command.js';
11
11
  import { RunnerAndroidProofController } from './android-proof-runner.js';
12
12
  import { AcceptanceProof, CandidateProofInspectionError } from './acceptance-proof.js';
13
13
  import { createCheckedChangeCapabilities } from './checked-change.js';
14
- import { InjectedContainedMutableOperation, InjectedContainedReportOperation, } from './contained-report-operation.js';
14
+ import { InjectedContainedReportOperation } from './contained-report-operation.js';
15
15
  import { ContainedImplementationReviewer } from './implementation-reviewer.js';
16
16
  import { parseAgentAutoConfig } from './config.js';
17
- import { WaitingHumanCoordinator } from './waiting-human-coordinator.js';
18
17
  import { canonicalJson, parseJsonWithoutDuplicateKeys, sha256, } from './containment.js';
19
- import { FileProofRecordWriter } from './proof-store.js';
20
18
  import { CheckProcessQuiescenceError, parseIssueCheckInvocation, resolveIssueCheckPolicy } from './issue-check-policy.js';
21
19
  import { acquireOwnerControlLock, OwnerControlLockBlockedError } from './owner-control-lock.js';
22
20
  import { decodeAgentReportForValidation } from './report-envelope.js';
23
21
  import { CodexProcess, ProcessQuiescenceError } from './codex-process.js';
24
22
  import { FileAndroidLeaseVerifier, FileIosLeaseVerifier } from './mobile-lease.js';
25
23
  import { publishRuntimeAssetSnapshot } from './runtime-assets.js';
26
- import { RouteCoordinator } from './route-coordinator.js';
27
- import { SpecCoordinator } from './spec-coordinator.js';
28
- import { createSpecRevision } from './spec-delivery.js';
29
- import { validateCodeReviewDefects } from './code-review-report.js';
30
- import { hashRouteDecision, validateRouteReceipt } from './route-decision.js';
31
- import { OwnerLockContentionError, OwnerLockSafetyError, RunIssue } from './run-issue.js';
24
+ import { OwnerLockContentionError, OwnerLockSafetyError, RunIssue, } from './run-issue.js';
32
25
  import { FileRunRecordWriter } from './run-store.js';
26
+ import { captureProcessStartIdentity, observeProcessGroup, observeProcessIdentity } from './process-identity.js';
33
27
  import { parseWorkflowExecutionProfile, verifyWorkflowGeneration, } from './workflow-assets.js';
34
28
  import { candidateBindingId, candidateBindingSeed, candidateRef, } from './candidate.js';
35
29
  export class LocalGitRunIssueAdapter {
@@ -51,12 +45,9 @@ export class LocalGitRunIssueAdapter {
51
45
  throw new Error('candidate expected HEAD diverged');
52
46
  await this.git(['-C', input.worktreePath, 'read-tree', input.expectedHeadSha]);
53
47
  }),
54
- prepareExecution: (input) => this.prepareCandidateExecution(input),
55
- markExecutionLaunched: ({ lease, pid, processGroupId, launchedAt }) => ({
56
- ...lease, phase: 'launched', pid, processGroupId, launchedAt,
57
- }),
58
- inspectExecution: (input) => this.inspectCandidateExecution(input),
59
- removeExecution: (input) => this.removeCandidateExecution(input.lease),
48
+ prepareMaterialization: (input) => this.prepareCandidateMaterialization(input),
49
+ inspectMaterialization: (input) => this.inspectCandidateMaterialization(input),
50
+ removeMaterialization: (input) => this.removeCandidateMaterialization(input.materialization),
60
51
  copyProofArtifacts: (input) => this.copyCandidateProofArtifacts(input),
61
52
  createOrObserveCommit: (input) => this.createOrObserveCandidateCommit(input),
62
53
  releasePin: (input) => this.releaseCandidatePin(input.binding, input.expectedPinnedCommitSha),
@@ -98,23 +89,23 @@ export class LocalGitRunIssueAdapter {
98
89
  throw new Error('orphan candidate ref cleanup failed');
99
90
  }
100
91
  }
101
- const executionRoot = canonicalFilesystemPath(resolve(input.workspaceRoot, '.candidate-executions'));
102
- const activeExecutions = new Map(input.activeExecutions.map((execution) => [
103
- canonicalFilesystemPath(execution.path), execution.candidateCommitSha,
92
+ const materializationRoot = canonicalFilesystemPath(resolve(input.workspaceRoot, '.candidate-materializations'));
93
+ const activeMaterializations = new Map(input.activeMaterializations.map((materialization) => [
94
+ canonicalFilesystemPath(materialization.path), materialization.candidateCommitSha,
104
95
  ]));
105
96
  for (const worktree of await this.worktrees.listWorktrees(root)) {
106
97
  const path = canonicalFilesystemPath(worktree.path);
107
- const relativePath = relative(executionRoot, path);
98
+ const relativePath = relative(materializationRoot, path);
108
99
  if (relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath))) {
109
- const expectedCommit = activeExecutions.get(path);
100
+ const expectedCommit = activeMaterializations.get(path);
110
101
  if (expectedCommit) {
111
102
  if (worktree.branch !== undefined || await this.getHead(path).catch(() => '') !== expectedCommit) {
112
- throw new Error('persisted candidate execution owner diverged');
103
+ throw new Error('persisted candidate materialization diverged');
113
104
  }
114
105
  }
115
106
  else {
116
107
  if (worktree.branch !== undefined)
117
- throw new Error('orphan candidate execution unexpectedly owns a branch');
108
+ throw new Error('orphan candidate materialization unexpectedly owns a branch');
118
109
  await this.git(['-C', root, 'worktree', 'remove', '--force', path]);
119
110
  }
120
111
  }
@@ -215,11 +206,11 @@ export class LocalGitRunIssueAdapter {
215
206
  && await this.commitMatches(root, observed, binding.expectedHeadSha, binding.candidateTreeSha) ? 'matching' : 'diverged';
216
207
  });
217
208
  }
218
- async prepareCandidateExecution(input) {
209
+ async prepareCandidateMaterialization(input) {
219
210
  return this.candidateOperation('candidate-materialization-io-failed', async () => {
220
211
  const root = await this.repositoryFromWorkspaceRoot(input.workspaceRoot);
221
212
  this.candidateRepositories.set(input.binding.candidateRef, root);
222
- const path = join(input.workspaceRoot, '.candidate-executions', input.runId, input.binding.bindingId, `${input.operation}-${input.attemptId}`);
213
+ const path = join(input.workspaceRoot, '.candidate-materializations', input.runId, input.binding.bindingId, input.materializationId);
223
214
  const existing = (await this.worktrees.listWorktrees(root)).find((worktree) => sameFilesystemPath(worktree.path, path));
224
215
  if (existing) {
225
216
  const clean = await this.worktrees.isWorktreeClean(path);
@@ -231,17 +222,16 @@ export class LocalGitRunIssueAdapter {
231
222
  await mkdir(dirname(path), { recursive: true });
232
223
  await this.git(['-C', root, 'worktree', 'add', '--detach', path, input.binding.candidateCommitSha]);
233
224
  }
234
- return { kind: 'prepared', lease: {
225
+ return { kind: 'prepared', materialization: {
235
226
  version: 2, bindingId: input.binding.bindingId, candidateCommitSha: input.binding.candidateCommitSha,
236
- path, operation: input.operation, attemptId: input.attemptId, phase: 'prepared', pid: null,
237
- processGroupId: null, preparedAt: new Date().toISOString(), launchedAt: null,
227
+ path,
238
228
  } };
239
229
  });
240
230
  }
241
- async inspectCandidateExecution(input) {
231
+ async inspectCandidateMaterialization(input) {
242
232
  return this.candidateOperation('candidate-materialization-io-failed', async () => {
243
233
  try {
244
- const stat = await lstat(input.lease.path);
234
+ const stat = await lstat(input.materialization.path);
245
235
  if (stat.isSymbolicLink() || !stat.isDirectory())
246
236
  return 'mutated';
247
237
  }
@@ -251,43 +241,41 @@ export class LocalGitRunIssueAdapter {
251
241
  throw error;
252
242
  }
253
243
  const [head, tree, changed] = await Promise.all([
254
- this.getHead(input.lease.path),
255
- this.git(['-C', input.lease.path, 'rev-parse', 'HEAD^{tree}']).then((value) => value.trim()),
256
- this.worktrees.listChangedFilesIgnoringUntrackedRoot(input.lease.path, input.artifactDir),
244
+ this.getHead(input.materialization.path),
245
+ this.git(['-C', input.materialization.path, 'rev-parse', 'HEAD^{tree}']).then((value) => value.trim()),
246
+ this.worktrees.listChangedFilesIgnoringUntrackedRoot(input.materialization.path, input.artifactDir),
257
247
  ]);
258
248
  return head === input.binding.candidateCommitSha && tree === input.binding.candidateTreeSha && changed.length === 0 ? 'matching' : 'mutated';
259
249
  });
260
250
  }
261
- async removeCandidateExecution(lease) {
251
+ async removeCandidateMaterialization(materialization) {
262
252
  return this.candidateOperation('candidate-materialization-io-failed', async () => {
263
- if (lease.phase === 'launched' && lease.processGroupId && processGroupIsAlive(lease.processGroupId))
264
- throw new Error('candidate execution process is still active');
265
- const root = await this.repositoryFromWorkspaceRoot(lease.path);
266
- const registered = (await this.worktrees.listWorktrees(root)).find((worktree) => sameFilesystemPath(worktree.path, lease.path));
253
+ const root = await this.repositoryFromWorkspaceRoot(materialization.path);
254
+ const registered = (await this.worktrees.listWorktrees(root)).find((worktree) => sameFilesystemPath(worktree.path, materialization.path));
267
255
  if (!registered)
268
256
  return;
269
- if (registered.branch !== undefined || await this.getHead(lease.path) !== lease.candidateCommitSha)
270
- throw new Error('candidate execution owner diverged');
271
- await this.git(['-C', root, 'worktree', 'remove', '--force', lease.path]);
257
+ if (registered.branch !== undefined || await this.getHead(materialization.path) !== materialization.candidateCommitSha)
258
+ throw new Error('candidate materialization diverged');
259
+ await this.git(['-C', root, 'worktree', 'remove', '--force', materialization.path]);
272
260
  });
273
261
  }
274
262
  async copyCandidateProofArtifacts(input) {
275
263
  return this.candidateOperation('candidate-materialization-io-failed', async () => {
276
264
  const proofRoot = `${input.artifactDir}/${input.proofId}`;
277
- const repositoryRoot = await this.repositoryFromWorkspaceRoot(input.lease.path);
265
+ const repositoryRoot = await this.repositoryFromWorkspaceRoot(input.materialization.path);
278
266
  const registered = (await this.worktrees.listWorktrees(repositoryRoot))
279
- .find((worktree) => sameFilesystemPath(worktree.path, input.lease.path));
267
+ .find((worktree) => sameFilesystemPath(worktree.path, input.materialization.path));
280
268
  if (!registered || registered.branch !== undefined
281
- || await this.getHead(input.lease.path).catch(() => '') !== input.lease.candidateCommitSha) {
269
+ || await this.getHead(input.materialization.path).catch(() => '') !== input.materialization.candidateCommitSha) {
282
270
  return { kind: 'artifact-conflict', relativePath: proofRoot };
283
271
  }
284
- const sourceRoot = await inspectSafeArtifactRoot(input.lease.path);
272
+ const sourceRoot = await inspectSafeArtifactRoot(input.materialization.path);
285
273
  const destinationRoot = await inspectSafeArtifactRoot(input.issueWorktreePath);
286
274
  for (const artifact of input.artifacts) {
287
275
  if (artifact.relativePath !== proofRoot && !artifact.relativePath.startsWith(`${proofRoot}/`))
288
276
  return { kind: 'artifact-conflict', relativePath: artifact.relativePath };
289
277
  try {
290
- const source = join(input.lease.path, artifact.relativePath);
278
+ const source = join(input.materialization.path, artifact.relativePath);
291
279
  const destination = join(input.issueWorktreePath, artifact.relativePath);
292
280
  const sourceBytes = await readRegularFileWithoutSymlinkAncestors(sourceRoot, source);
293
281
  if (sha256(sourceBytes) !== artifact.sha256)
@@ -518,6 +506,13 @@ export class LocalGitRunIssueAdapter {
518
506
  listChangedFiles(worktreePath) {
519
507
  return this.worktrees.listChangedFiles(worktreePath);
520
508
  }
509
+ async diffTrees(worktreePath, previousTreeSha, candidateTreeSha) {
510
+ const [names, patch] = await Promise.all([
511
+ this.git(['-C', worktreePath, 'diff-tree', '--no-commit-id', '--name-only', '-r', '-z', previousTreeSha, candidateTreeSha], { maxOutputBytes: 1024 * 1024 }),
512
+ this.git(['-C', worktreePath, 'diff', '--binary', '--full-index', '--no-ext-diff', previousTreeSha, candidateTreeSha, '--'], { maxOutputBytes: 1024 * 1024 }),
513
+ ]);
514
+ return { changedFiles: names.split('\0').filter(Boolean).sort(), patch };
515
+ }
521
516
  listChangedFilesIgnoringUntrackedRoot(worktreePath, ignoredRoot) {
522
517
  return this.worktrees.listChangedFilesIgnoringUntrackedRoot(worktreePath, ignoredRoot);
523
518
  }
@@ -784,106 +779,84 @@ export class ContainedImplementationAgent {
784
779
  async run(input) {
785
780
  const config = this.dependencies.config();
786
781
  const canonicalRepository = `${config.github.owner.toLowerCase()}/${config.github.repo.toLowerCase()}`;
787
- const promptFacts = implementationPromptFacts(input);
788
- const operation = this.operation(config, canonicalRepository, promptFacts);
789
- const result = await operation.run({
790
- operation: input.operation, runId: input.runId, worktreePath: input.worktreePath,
791
- workflowGeneration: input.workflowGeneration, promptFacts, signal: input.signal,
792
- context: { repairOnly: input.repairOnly, reworkFindings: [...input.reworkFindings] },
793
- invocationState: input.invocationState, beforeLaunch: input.beforeLaunch,
782
+ const attemptId = input.attemptId;
783
+ const attempt = await prepareContainedAttempt({
784
+ orchestratorHome: this.dependencies.orchestratorHome,
785
+ canonicalRepository,
786
+ runId: input.runId,
787
+ attemptId,
788
+ operationId: input.operation,
789
+ workflowGeneration: input.workflowGeneration,
790
+ bootId: this.dependencies.bootId,
794
791
  });
795
- if (result.status === 'completed')
796
- return { kind: 'completed', attemptId: result.attemptId,
797
- report: decodeAgentReportForValidation(result.reportBytes) };
798
- if (result.status === 'cancelled')
799
- return { kind: 'cancelled' };
800
- if (result.status === 'retryable')
801
- return { kind: 'transport-failed', resumable: true, code: result.code };
802
- if (result.status === 'safe-halt')
803
- return { kind: 'safe-halt', code: result.code };
804
- return result.kind === 'external'
805
- ? { kind: 'transport-failed', resumable: true, code: result.code }
806
- : { kind: 'safe-halt', code: result.code };
807
- }
808
- async settle(input) {
809
- const config = this.dependencies.config();
810
- const canonicalRepository = `${config.github.owner.toLowerCase()}/${config.github.repo.toLowerCase()}`;
811
- const promptFacts = implementationPromptFacts(input);
812
- const result = await this.operation(config, canonicalRepository, promptFacts).settle({
813
- operation: input.operation, runId: input.runId, worktreePath: input.worktreePath,
814
- workflowGeneration: input.workflowGeneration, promptFacts, signal: input.signal,
815
- context: { repairOnly: input.repairOnly, reworkFindings: [...input.reworkFindings] },
816
- invocationState: input.invocationState,
792
+ const baseline = await this.dependencies.git.snapshot(input.worktreePath);
793
+ await input.onPrepared?.({
794
+ attemptId,
795
+ reportPath: attempt.reportPath,
796
+ preparedAt: (this.dependencies.now ?? (() => new Date().toISOString()))(),
797
+ baseline,
817
798
  });
818
- return result.status === 'settled' ? { kind: 'settled' } : { kind: 'safe-halt', code: result.code };
819
- }
820
- operation(config, canonicalRepository, promptFacts) {
821
- return new InjectedContainedMutableOperation({
822
- host: hostname(), bootId: this.dependencies.bootId,
823
- now: this.dependencies.now ?? (() => new Date().toISOString()),
824
- createAttemptId: this.dependencies.createAttemptId ?? randomUUID,
825
- prepare: async ({ operation: worker, attemptId, runId, workflowGeneration }) => ({
826
- operation: worker, generationHash: workflowGeneration.generationHash,
827
- ...await prepareContainedAttempt({
828
- orchestratorHome: this.dependencies.orchestratorHome, canonicalRepository, runId, attemptId,
829
- operationId: worker, workflowGeneration, bootId: this.dependencies.bootId,
799
+ try {
800
+ try {
801
+ const recovered = await readRegularFile(attempt.reportPath);
802
+ return { kind: 'completed', attemptId, report: decodeAgentReportForValidation(recovered) };
803
+ }
804
+ catch (error) {
805
+ if (!isErrorCode(error, 'ENOENT'))
806
+ return { kind: 'internal-error' };
807
+ }
808
+ const result = await (this.dependencies.process ?? new CodexProcess()).run({
809
+ codexPath: config.codex.command,
810
+ cwd: input.worktreePath,
811
+ schemaPath: attempt.schemaPath,
812
+ reportPath: attempt.reportPath,
813
+ toolHome: attempt.toolHome,
814
+ tmpDir: attempt.tmpDir,
815
+ safePath: this.dependencies.safePath,
816
+ parentCodexHome: this.dependencies.parentCodexHome,
817
+ parentEnv: process.env,
818
+ prompt: [
819
+ `Package profile instructions: ${attempt.profile.developerInstructions}`,
820
+ `Follow the exact operation at ${attempt.operationPath}.`,
821
+ `The operation's immutable workflow root is ${attempt.workflowRoot}.`,
822
+ `Implement issue #${input.issue.number}: ${input.issue.title}`,
823
+ `Implementation cycle: ${input.cycle}.`,
824
+ ...(input.reviewFeedbackRound ? [`Pull-request feedback repair round: ${input.reviewFeedbackRound}.`] : []),
825
+ ...(input.reviewFeedback?.length ? [`Frozen trusted pull-request feedback: ${canonicalJson(input.reviewFeedback)}`] : []),
826
+ `Exact delivery authority: ${canonicalJson(input.deliveryAuthority)}`,
827
+ `Frozen acceptance criteria: ${canonicalJson(input.frozenCriteria)}`,
828
+ ...(input.reworkFindings.length > 0 ? [`Repair these verified findings: ${canonicalJson(input.reworkFindings)}`] : []),
829
+ ...(input.repairOnly ? ['Report repair only: do not modify any worktree file; emit a schema-valid implementation report for the existing change.'] : []),
830
+ 'Do not commit, push, publish, or print credentials or local auth paths.',
831
+ ].join('\n'),
832
+ timeoutMs: config.codex.timeoutMs,
833
+ idleTimeoutMs: config.codex.idleTimeoutMs,
834
+ operationPolicy: attempt.policy,
835
+ executionProfile: attempt.profile,
836
+ onSpawned: async ({ pid, processGroupId }) => input.onLaunched?.({
837
+ attemptId, pid, processGroupId, launchedAt: (this.dependencies.now ?? (() => new Date().toISOString()))(),
830
838
  }),
831
- }),
832
- snapshot: (worktreePath) => this.dependencies.git.snapshot(worktreePath),
833
- readReport: async (path) => readRegularFile(path).then((bytes) => ({ status: 'available', bytes }))
834
- .catch((error) => isErrorCode(error, 'ENOENT') ? { status: 'absent' } : { status: 'unknown' }),
835
- processStartIdentity: readProcessStartIdentity,
836
- inspectProcess: inspectReportProcess,
837
- launch: async ({ attempt, worktreePath, signal, onSpawned }) => {
838
- try {
839
- const result = await (this.dependencies.process ?? new CodexProcess()).run({
840
- codexPath: config.codex.command, cwd: worktreePath, schemaPath: attempt.schemaPath,
841
- reportPath: attempt.reportPath, toolHome: attempt.toolHome, tmpDir: attempt.tmpDir,
842
- safePath: this.dependencies.safePath, parentCodexHome: this.dependencies.parentCodexHome,
843
- parentEnv: process.env,
844
- prompt: [
845
- `Package profile instructions: ${attempt.profile.developerInstructions}`,
846
- `Follow the exact operation at ${attempt.operationPath}.`,
847
- `The operation's immutable workflow root is ${attempt.workflowRoot}.`,
848
- ...promptFacts,
849
- ].join('\n'),
850
- timeoutMs: config.codex.timeoutMs, idleTimeoutMs: config.codex.idleTimeoutMs,
851
- operationPolicy: attempt.policy, executionProfile: attempt.profile, onSpawned,
852
- }, signal);
853
- if (result.kind === 'cancelled')
854
- return { status: 'cancelled' };
855
- if (['spawn-failed', 'transport-failed', 'timeout', 'idle-timeout'].includes(result.kind))
856
- return { status: 'retryable', code: `mutable-operation-${result.kind}` };
857
- if (result.kind !== 'completed' || result.report.kind !== 'available')
858
- return { status: 'retryable', code: 'mutable-operation-report-unavailable' };
859
- return { status: 'completed', reportBytes: result.report.bytes };
860
- }
861
- catch (error) {
862
- return error instanceof ProcessQuiescenceError
863
- ? { status: 'safe-halt' }
864
- : { status: 'retryable', code: 'mutable-operation-launch-failed' };
865
- }
866
- },
867
- });
839
+ }, input.signal);
840
+ if (result.kind === 'cancelled')
841
+ return { kind: 'cancelled' };
842
+ if (['spawn-failed', 'transport-failed', 'timeout', 'idle-timeout', 'launch-gate-failed'].includes(result.kind)) {
843
+ return { kind: 'transport-failed', resumable: true };
844
+ }
845
+ if (result.kind !== 'completed' || result.report.kind !== 'available')
846
+ return { kind: 'internal-error' };
847
+ return {
848
+ kind: 'completed',
849
+ attemptId,
850
+ report: decodeAgentReportForValidation(result.report.bytes),
851
+ };
852
+ }
853
+ catch (error) {
854
+ if (!(error instanceof ProcessQuiescenceError))
855
+ return { kind: 'internal-error' };
856
+ return { kind: 'safe-halt' };
857
+ }
868
858
  }
869
859
  }
870
- function implementationPromptFacts(input) {
871
- const task = input.operation === 'qualification-repair' ? [
872
- `Pre-implementation qualification repair for issue #${input.issue.number}: ${input.issue.title}`,
873
- 'Do not implement the issue acceptance criteria yet. Repair only the reported scoped-check failures so the existing worktree qualifies for implementation.',
874
- 'You may modify the product files required by those failures. Return changedFiles as the complete cumulative worktree change set.',
875
- ] : [`Implement issue #${input.issue.number}: ${input.issue.title}`];
876
- return [...task,
877
- `Implementation cycle: ${input.cycle}.`,
878
- ...(input.reviewFeedbackRound ? [`Pull-request feedback repair round: ${input.reviewFeedbackRound}.`] : []),
879
- ...(input.reviewFeedback?.length ? [`Frozen trusted pull-request feedback: ${canonicalJson(input.reviewFeedback)}`] : []),
880
- ...(input.operation !== 'qualification-repair' ? [`Frozen acceptance criteria: ${canonicalJson(input.frozenCriteria)}`] : []),
881
- ...(input.reworkFindings.length > 0 ? [`Repair these verified findings: ${canonicalJson(input.reworkFindings)}`] : []),
882
- ...(input.phaseFacts?.length ? [`Phase-owned invocation correlation: ${canonicalJson(input.phaseFacts)}`] : []),
883
- ...(input.repairOnly ? ['Report repair only: do not modify any worktree file; emit a schema-valid implementation report for the existing change.'] : []),
884
- 'Do not commit, push, publish, or print credentials or local auth paths.',
885
- ];
886
- }
887
860
  export class ContainedProofAgent {
888
861
  dependencies;
889
862
  constructor(dependencies) {
@@ -892,54 +865,150 @@ export class ContainedProofAgent {
892
865
  async run(input) {
893
866
  if (!input.workflowGeneration)
894
867
  throw new Error('proof workflow generation is required');
895
- if (!input.iosProofInputs)
896
- throw new Error('runner-owned iOS proof inputs are required');
897
868
  const config = this.dependencies.config();
869
+ const canonicalRepository = `${config.github.owner.toLowerCase()}/${config.github.repo.toLowerCase()}`;
898
870
  const issueWorktreePath = resolve(this.dependencies.targetRoot, config.runner.workspaceRoot, `issue-${input.issue.number}`);
899
871
  const worktreePath = input.worktreePath ? resolve(input.worktreePath) : issueWorktreePath;
872
+ const attempt = await prepareContainedAttempt({
873
+ orchestratorHome: this.dependencies.orchestratorHome,
874
+ canonicalRepository,
875
+ runId: input.runId,
876
+ attemptId: input.attemptId ?? (this.dependencies.createAttemptId ?? randomUUID)(),
877
+ operationId: 'acceptance-proof',
878
+ workflowGeneration: input.workflowGeneration,
879
+ bootId: this.dependencies.bootId,
880
+ });
900
881
  const artifactRoot = resolve(worktreePath, config.proof.artifactDir);
882
+ const snapshotRoot = dirname(attempt.sourceSkillPath ?? attempt.operationPath);
883
+ const iosLeaseRoot = join(resolve(this.dependencies.orchestratorHome), 'v2', sha256(canonicalRepository), 'leases');
884
+ const iosLeaseArtifact = join(artifactRoot, input.proofId, 'ios-lease.json');
885
+ const iosTooling = await discoverIosTooling(this.dependencies.processExecutor, this.dependencies.iosXcrunPath);
901
886
  const before = await artifactInventory(artifactRoot, config.proof.artifactDir);
902
- if (!input.invocationState)
903
- return { kind: 'internal-error' };
904
- if (input.repairOnly && await input.invocationState.read() === undefined
905
- && !artifactInventoryMatches(before, input.repairArtifactSha256)) {
906
- return { kind: 'internal-error', code: 'proof-report-repair-artifact-drift' };
907
- }
908
- const result = await this.dependencies.operation.run({
909
- operation: 'acceptance-proof', runId: input.runId, worktreePath,
910
- workflowGeneration: input.workflowGeneration, signal: input.signal, invocationState: input.invocationState,
911
- beforeLaunch: input.beforeLaunch,
912
- promptFacts: [
913
- `Proof ID: ${input.proofId}. Issue: ${canonicalJson(input.issue)}.`,
914
- `Frozen acceptance criteria: ${canonicalJson(input.frozenCriteria)}.`,
915
- `Checked change: ${input.checkedChangeSha256}; files: ${canonicalJson(input.changedFiles)}; checks: ${canonicalJson(input.checks)}.`,
916
- `Runner artifacts: ${canonicalJson(input.runnerPreparedArtifactPaths)}; hashes: ${canonicalJson(input.runnerPreparedArtifactSha256)}; warnings: ${canonicalJson(input.runnerPreparationWarnings)}.`,
917
- `Runner-owned immutable iOS proof inputs: ${canonicalJson(input.iosProofInputs)}.`,
918
- ...(input.iosProofInputs.runtimeId === null || input.iosProofInputs.deviceTypeId === null
919
- ? ['iOS Simulator tooling discovery is unavailable; return a typed tool blocker for an iOS surface.'] : []),
920
- `Report repair: ${canonicalJson({ repairOnly: input.repairOnly, findings: input.repairFindings })}.`,
921
- ...(input.repairOnly ? [`Immutable pre-repair artifact inventory: ${canonicalJson(input.repairArtifactSha256)}.`] : []),
922
- ],
923
- });
924
- if (result.status === 'cancelled')
925
- return { kind: 'cancelled' };
926
- if (result.status !== 'completed') {
927
- if (input.repairOnly && await input.invocationState.read() === undefined
928
- && !artifactInventoryMatches(await artifactInventory(artifactRoot, config.proof.artifactDir), input.repairArtifactSha256)) {
929
- return { kind: 'internal-error', code: 'proof-report-repair-artifact-drift' };
887
+ try {
888
+ try {
889
+ const recoveredReport = await readRegularFile(attempt.reportPath);
890
+ const recoveredInventory = await artifactInventory(artifactRoot, config.proof.artifactDir);
891
+ const runnerPrepared = new Set(input.runnerPreparedArtifactPaths);
892
+ return {
893
+ kind: 'report',
894
+ report: decodeAgentReportForValidation(recoveredReport, ['visualEvidence', 'blocker']),
895
+ proofPhaseChangedFiles: [...recoveredInventory.keys()].filter((path) => !runnerPrepared.has(path)).sort(),
896
+ };
897
+ }
898
+ catch (error) {
899
+ if (!isErrorCode(error, 'ENOENT'))
900
+ throw error;
901
+ }
902
+ if (input.recoverOnly)
903
+ return { kind: 'internal-error' };
904
+ const result = await (this.dependencies.process ?? new CodexProcess()).run({
905
+ codexPath: config.codex.command,
906
+ cwd: worktreePath,
907
+ schemaPath: attempt.schemaPath,
908
+ reportPath: attempt.reportPath,
909
+ toolHome: attempt.toolHome,
910
+ tmpDir: attempt.tmpDir,
911
+ safePath: this.dependencies.safePath,
912
+ parentCodexHome: this.dependencies.parentCodexHome,
913
+ parentEnv: process.env,
914
+ prompt: [
915
+ `Package profile instructions: ${attempt.profile.developerInstructions}`,
916
+ `Follow the exact operation at ${attempt.operationPath}.`,
917
+ `The operation's immutable workflow root is ${attempt.workflowRoot}.`,
918
+ `Independently prove issue #${input.issue.number}.`,
919
+ `Frozen acceptance criteria: ${canonicalJson(input.frozenCriteria)}`,
920
+ `Checked change digest: ${input.checkedChangeSha256}.`,
921
+ `Checked changed files: ${canonicalJson(input.changedFiles)}.`,
922
+ `Configured check receipts: ${canonicalJson(input.checks)}.`,
923
+ `Write evidence only below ${config.proof.artifactDir}.`,
924
+ 'When a frozen criterion has a browser surface, follow references/browser.md from the exact acceptance-proof skill snapshot.',
925
+ 'When a frozen criterion has an Android surface, follow references/android.md from the exact acceptance-proof skill snapshot.',
926
+ ...(config.proof.android ? [
927
+ `Runner-owned Android artifact paths: ${canonicalJson(input.runnerPreparedArtifactPaths)}.`,
928
+ `Runner-owned Android preparation warnings: ${canonicalJson(input.runnerPreparationWarnings)}.`,
929
+ 'The trusted Runner owns emulator, build, adb, lease, capture, and cleanup actions. Do not invoke adb, emulator, Flutter run, or an Android lease helper.',
930
+ 'Inspect Runner artifacts when present. If preparation warnings are present, continue with all available non-visual evidence and preserve the warning as a residual risk; Android infrastructure failure alone must not block delivery.',
931
+ ] : [
932
+ 'Runner-owned Android proof is not configured for this repository. Do not invoke adb, emulator, Flutter run, or an Android lease helper; return a typed tool blocker for Android criteria.',
933
+ ]),
934
+ 'When a frozen criterion has an iOS surface, follow references/ios.md from the exact acceptance-proof skill snapshot.',
935
+ `iOS lease helper: ${join(snapshotRoot, 'tools', 'ios-lease.mjs')}.`,
936
+ `iOS lease root: ${iosLeaseRoot}.`,
937
+ `iOS lease artifact: ${iosLeaseArtifact}.`,
938
+ `iOS lease proof ID: ${input.proofId}.`,
939
+ `iOS lease owner PID: ${process.pid}.`,
940
+ `iOS xcrun path: ${this.dependencies.iosXcrunPath}.`,
941
+ ...(iosTooling ? [
942
+ `iOS runtime ID: ${iosTooling.runtimeId}.`,
943
+ `iOS device type ID: ${iosTooling.deviceTypeId}.`,
944
+ ] : ['iOS Simulator tooling discovery is unavailable; return a typed tool blocker for an iOS surface.']),
945
+ ...(input.repairOnly ? [`Proof Report repair only: ${canonicalJson(input.repairFindings)} Do not modify product or evidence files.`] : []),
946
+ 'Do not modify product files, commit, push, publish, or print credentials or local auth paths.',
947
+ ].join('\n'),
948
+ timeoutMs: config.codex.timeoutMs,
949
+ idleTimeoutMs: config.codex.idleTimeoutMs,
950
+ operationPolicy: attempt.policy,
951
+ executionProfile: attempt.profile,
952
+ onSpawned: async ({ pid, processGroupId }) => input.onLaunched?.({
953
+ pid, processGroupId, launchedAt: new Date().toISOString(),
954
+ }),
955
+ }, input.signal);
956
+ if (result.kind === 'cancelled')
957
+ return { kind: 'cancelled' };
958
+ if (['spawn-failed', 'transport-failed', 'timeout', 'idle-timeout', 'launch-gate-failed'].includes(result.kind)) {
959
+ return { kind: 'transport-failed', resumable: true };
930
960
  }
931
- return { kind: 'deferred', code: result.code };
961
+ if (result.kind !== 'completed' || result.report.kind !== 'available')
962
+ return { kind: 'internal-error' };
963
+ const after = await artifactInventory(artifactRoot, config.proof.artifactDir);
964
+ return {
965
+ kind: 'report',
966
+ report: decodeAgentReportForValidation(result.report.bytes, ['visualEvidence', 'blocker']),
967
+ proofPhaseChangedFiles: changedArtifactPaths(before, after),
968
+ };
932
969
  }
933
- const after = await artifactInventory(artifactRoot, config.proof.artifactDir);
934
- const runnerPrepared = new Set(input.runnerPreparedArtifactPaths);
935
- const changed = before.size === 0
936
- ? [...after.keys()].filter((path) => !runnerPrepared.has(path)).sort()
937
- : changedArtifactPaths(before, after);
938
- return {
939
- kind: 'report', report: decodeAgentReportForValidation(result.reportBytes, ['visualEvidence', 'blocker']),
940
- proofPhaseChangedFiles: changed, proofPhaseArtifactSha256: Object.fromEntries([...after.entries()].sort()),
941
- };
970
+ catch (error) {
971
+ return error instanceof ProcessQuiescenceError ? { kind: 'safe-halt' } : { kind: 'internal-error' };
972
+ }
973
+ }
974
+ }
975
+ export async function observeAttemptReadViewCleanup(input) {
976
+ const runtimeRoot = join(resolve(input.orchestratorHome), 'v2', sha256(input.canonicalRepository));
977
+ const attemptRoot = join(runtimeRoot, 'runs', input.identity.runId, 'attempts', input.identity.attemptId);
978
+ const expectedResultPath = join(attemptRoot, 'report.json');
979
+ if (input.identity.resultPath !== expectedResultPath)
980
+ return 'pending';
981
+ let canonicalRuntimeRoot;
982
+ let canonicalAttemptRoot;
983
+ try {
984
+ canonicalRuntimeRoot = await realpath(runtimeRoot);
985
+ }
986
+ catch (error) {
987
+ return isErrorCode(error, 'ENOENT') ? 'confirmed' : 'pending';
988
+ }
989
+ try {
990
+ canonicalAttemptRoot = await realpath(attemptRoot);
942
991
  }
992
+ catch (error) {
993
+ return isErrorCode(error, 'ENOENT') ? 'confirmed' : 'pending';
994
+ }
995
+ const expectedRelative = relative(runtimeRoot, attemptRoot);
996
+ if (!isPathWithin(canonicalRuntimeRoot, canonicalAttemptRoot)
997
+ || relative(canonicalRuntimeRoot, canonicalAttemptRoot) !== expectedRelative)
998
+ return 'pending';
999
+ await rm(join(attemptRoot, 'read-view'), { recursive: true, force: true });
1000
+ return 'confirmed';
1001
+ }
1002
+ export function selectedConfiguredCheckPolicySha256(selectedChecks, configuredAuthority) {
1003
+ const selected = {};
1004
+ for (const check of selectedChecks) {
1005
+ if (Object.prototype.hasOwnProperty.call(selected, check.id)
1006
+ || configuredAuthority[check.id] !== check.command) {
1007
+ throw new CandidateProofInspectionError('selected check no longer matches current configured authority');
1008
+ }
1009
+ selected[check.id] = check.command;
1010
+ }
1011
+ return sha256(canonicalJson(selected));
943
1012
  }
944
1013
  export async function resolveCodexExecutable(command, safePath) {
945
1014
  if (command.length === 0 || safePath.length === 0)
@@ -1006,6 +1075,13 @@ export function createV2Runtime(input) {
1006
1075
  git,
1007
1076
  now,
1008
1077
  });
1078
+ const proofAgent = input.proofAgent ?? new ContainedProofAgent({
1079
+ ...containedDependencies(),
1080
+ targetRoot,
1081
+ androidAdbPath,
1082
+ iosXcrunPath,
1083
+ processExecutor: commandExecutor,
1084
+ });
1009
1085
  const androidProofController = new RunnerAndroidProofController({
1010
1086
  adbPath: androidAdbPath,
1011
1087
  emulatorPath: androidEmulatorPath,
@@ -1013,10 +1089,6 @@ export function createV2Runtime(input) {
1013
1089
  now: () => new Date(now()),
1014
1090
  });
1015
1091
  const reportOperation = new InjectedContainedReportOperation({
1016
- host: hostname(),
1017
- bootId: input.bootId,
1018
- now,
1019
- createAttemptId: input.createAttemptId ?? randomUUID,
1020
1092
  prepare: async ({ operation, attemptId, runId, workflowGeneration }) => ({
1021
1093
  operation,
1022
1094
  generationHash: workflowGeneration.generationHash,
@@ -1030,44 +1102,38 @@ export function createV2Runtime(input) {
1030
1102
  bootId: input.bootId,
1031
1103
  }),
1032
1104
  }),
1033
- snapshot: (worktreePath, operation) => operation === 'acceptance-proof' && proofFreshnessGit.snapshotIgnoringUntrackedRoot
1034
- ? proofFreshnessGit.snapshotIgnoringUntrackedRoot(worktreePath, requireConfig(currentConfig).proof.artifactDir)
1035
- : git.snapshot(worktreePath),
1036
- readReport: async (path) => readRegularFile(path).then((bytes) => ({ status: 'available', bytes }))
1037
- .catch((error) => isErrorCode(error, 'ENOENT') ? { status: 'absent' } : { status: 'unknown' }),
1038
- settleAttempt: async (attempt) => attempt.tmpDir
1039
- ? rm(join(dirname(attempt.tmpDir), 'read-view'), { recursive: true, force: true })
1040
- : Promise.reject(new Error('report operation attempt temporary directory is missing')),
1041
- processStartIdentity: readProcessStartIdentity,
1042
- inspectProcess: inspectReportProcess,
1043
- launch: async ({ operation, attempt, worktreePath, promptFacts, signal, onSpawned }) => {
1105
+ snapshot: (worktreePath) => git.snapshot(worktreePath),
1106
+ launch: async ({ attempt, worktreePath, promptFacts, signal, onLaunched }) => {
1044
1107
  const config = requireConfig(currentConfig);
1045
1108
  if (!attempt.schemaPath || !attempt.reportPath || !attempt.toolHome || !attempt.tmpDir
1046
1109
  || !attempt.profile || !attempt.operationPath || !attempt.workflowRoot) {
1047
1110
  return { status: 'blocked', kind: 'safety', code: 'report-operation-attempt-incomplete' };
1048
1111
  }
1049
- let readView;
1050
- if (operation !== 'spec-author' && operation !== 'acceptance-proof') {
1051
- try {
1052
- readView = await materializeReportReadView({
1053
- worktreePath,
1054
- destination: join(dirname(attempt.tmpDir), 'read-view'),
1055
- deniedPaths: config.deny.readPaths,
1056
- });
1057
- }
1058
- catch {
1059
- return { status: 'blocked', kind: 'safety', code: 'report-operation-read-view-failed' };
1112
+ try {
1113
+ return { status: 'completed', reportBytes: await readRegularFile(attempt.reportPath) };
1114
+ }
1115
+ catch (error) {
1116
+ if (!isErrorCode(error, 'ENOENT')) {
1117
+ return { status: 'blocked', kind: 'safety', code: 'report-operation-result-read-failed' };
1060
1118
  }
1061
1119
  }
1062
- const revisionPath = operation === 'spec-author'
1063
- ? join(dirname(attempt.reportPath), `revision-${promptFacts[0]}.md`)
1064
- : undefined;
1120
+ let readView;
1121
+ try {
1122
+ readView = await materializeReportReadView({
1123
+ worktreePath,
1124
+ destination: join(dirname(attempt.tmpDir), 'read-view'),
1125
+ deniedPaths: config.deny.readPaths,
1126
+ });
1127
+ }
1128
+ catch {
1129
+ return { status: 'blocked', kind: 'safety', code: 'report-operation-read-view-failed' };
1130
+ }
1065
1131
  let result;
1066
1132
  let cleanupAfterSafeHalt = false;
1067
1133
  try {
1068
1134
  result = await containedProcess.run({
1069
1135
  codexPath: config.codex.command,
1070
- cwd: operation === 'spec-author' ? dirname(attempt.reportPath) : operation === 'acceptance-proof' ? worktreePath : readView,
1136
+ cwd: readView,
1071
1137
  schemaPath: attempt.schemaPath,
1072
1138
  reportPath: attempt.reportPath,
1073
1139
  toolHome: attempt.toolHome,
@@ -1075,25 +1141,12 @@ export function createV2Runtime(input) {
1075
1141
  safePath: requireRuntimeString(input.safePath, 'safePath'),
1076
1142
  parentCodexHome: requireRuntimeString(input.parentCodexHome, 'parentCodexHome'),
1077
1143
  parentEnv: process.env,
1078
- prompt: operation === 'spec-author' ? [
1079
- `Package profile instructions: ${attempt.profile.developerInstructions}`,
1080
- `Follow the exact operation at ${attempt.operationPath}.`,
1081
- `The operation's immutable workflow root is ${attempt.workflowRoot}.`,
1082
- `Runner-provided facts: ${canonicalJson(promptFacts)}`,
1083
- `Write the complete new immutable revision only to ${revisionPath}. Return that exact absolute path and its SHA-256 in the report.`,
1084
- 'Do not modify the product worktree, prior revisions, external state, or any .env file.',
1085
- ].join('\n') : operation === 'acceptance-proof' ? [
1086
- `Package profile instructions: ${attempt.profile.developerInstructions}`,
1087
- `Follow the exact operation at ${attempt.operationPath}.`,
1088
- `The operation's immutable workflow root is ${attempt.workflowRoot}.`,
1089
- `Runner-provided proof facts:\n${promptFacts.join('\n')}`,
1090
- `Write evidence only below ${config.proof.artifactDir}. CheckedChange is the sole authority for completed checks.`,
1091
- 'Do not modify product files, commit, push, publish, use network or MCP tools, or print credentials or local auth paths.',
1092
- ].join('\n') : [
1144
+ prompt: [
1093
1145
  `Package profile instructions: ${attempt.profile.developerInstructions}`,
1094
1146
  `Follow the exact operation at ${attempt.operationPath}.`,
1095
1147
  `The operation's immutable workflow root is ${attempt.workflowRoot}.`,
1096
1148
  `Runner-provided facts: ${canonicalJson(promptFacts)}`,
1149
+ `Available reviewer roles: ${canonicalJson(attempt.reviewers ?? [])}`,
1097
1150
  'This is a read-only, report-only operation. Do not edit files or external state, use network or MCP tools, or request additional authority.',
1098
1151
  'Do not read .env or any .env* file. The runner has removed repository credential paths from this read view.',
1099
1152
  ].join('\n'),
@@ -1101,24 +1154,30 @@ export function createV2Runtime(input) {
1101
1154
  idleTimeoutMs: config.codex.idleTimeoutMs,
1102
1155
  operationPolicy: attempt.policy,
1103
1156
  executionProfile: attempt.profile,
1104
- onSpawned,
1157
+ agentProfilePaths: attempt.reviewerProfiles,
1158
+ onSpawned: async ({ pid, processGroupId }) => onLaunched?.({ pid, processGroupId }),
1105
1159
  }, signal);
1106
1160
  }
1107
1161
  catch (error) {
1108
1162
  if (error instanceof ProcessQuiescenceError) {
1109
1163
  cleanupAfterSafeHalt = true;
1110
- return { status: 'safe-halt' };
1164
+ return {
1165
+ status: 'safe-halt',
1166
+ pid: error.pid,
1167
+ processGroupId: error.processGroupId,
1168
+ startedAt: now(),
1169
+ };
1111
1170
  }
1112
1171
  throw error;
1113
1172
  }
1114
1173
  finally {
1115
- if (readView && !cleanupAfterSafeHalt)
1174
+ if (!cleanupAfterSafeHalt)
1116
1175
  await rm(readView, { recursive: true, force: true });
1117
1176
  }
1118
1177
  if (result.kind === 'cancelled')
1119
1178
  return { status: 'cancelled' };
1120
1179
  if (result.kind === 'launch-gate-failed') {
1121
- return { status: 'blocked', kind: 'safety', code: 'review-operation-launch-persistence-failed' };
1180
+ return { status: 'retryable', code: 'review-operation-launch-gate-failed' };
1122
1181
  }
1123
1182
  if (['spawn-failed', 'transport-failed', 'timeout', 'idle-timeout'].includes(result.kind)) {
1124
1183
  return { status: 'retryable', code: `report-operation-${result.kind}` };
@@ -1129,104 +1188,9 @@ export function createV2Runtime(input) {
1129
1188
  return { status: 'completed', reportBytes: result.report.bytes };
1130
1189
  },
1131
1190
  });
1132
- const proofAgent = input.proofAgent ?? new ContainedProofAgent({
1133
- config: () => requireConfig(currentConfig), targetRoot, operation: reportOperation,
1134
- });
1135
1191
  const implementationReviewer = new ContainedImplementationReviewer({
1136
1192
  operation: reportOperation,
1137
1193
  });
1138
- const specOperation = {
1139
- author: async ({ context, state, mode, authorSessionId: sessionId, signal, invocationState }) => {
1140
- let completedAttemptId;
1141
- try {
1142
- const result = await reportOperation.run({
1143
- operation: 'spec-author', runId: context.runId, worktreePath: context.worktreePath,
1144
- workflowGeneration: context.workflowGeneration, signal, invocationState,
1145
- promptFacts: [
1146
- String(state.revisions.length + 1),
1147
- `Author session ID: ${sessionId}. Author mode: ${mode}.`,
1148
- `Issue authority and frozen criteria: ${canonicalJson({ issue: context.issue, frozenCriteria: context.frozenCriteria })}.`,
1149
- `Prior revisions and review state: ${canonicalJson({ revisions: state.revisions, review: state.review })}.`,
1150
- ],
1151
- });
1152
- if (result.status === 'cancelled')
1153
- return { status: 'cancelled' };
1154
- if (result.status === 'retryable' || result.status === 'safe-halt')
1155
- return { status: 'retryable', code: result.code };
1156
- if (result.status === 'blocked')
1157
- return { status: 'blocked', kind: result.kind, code: result.code };
1158
- completedAttemptId = result.attemptId;
1159
- const invocation = await invocationState.read();
1160
- if (!invocation || invocation.operation !== 'spec-author' || invocation.attemptId !== result.attemptId) {
1161
- return { status: 'blocked', kind: 'safety', code: 'spec-author-invocation-correlation-missing' };
1162
- }
1163
- const revisionPath = join(dirname(invocation.reportPath), `revision-${state.revisions.length + 1}.md`);
1164
- const report = decodeAgentReportForValidation(result.reportBytes);
1165
- if (report.status !== 'ready' || report.specPath !== revisionPath || report.specSha256 === null) {
1166
- return { status: 'invalid', code: 'spec-author-report-invalid', attemptId: result.attemptId };
1167
- }
1168
- const content = await readRegularFile(revisionPath);
1169
- if (report.specSha256 !== sha256(content))
1170
- return { status: 'invalid', code: 'spec-author-report-invalid', attemptId: result.attemptId };
1171
- const previous = state.revisions.at(-1) ?? null;
1172
- return { status: 'completed', value: createSpecRevision({
1173
- revision: state.revisions.length + 1, path: revisionPath, content: content.toString('utf8'),
1174
- evidence: [{ path: context.issue.url, sha256: sha256(canonicalJson(context.issue)), description: 'Frozen issue authority' }],
1175
- author: { attemptId: result.attemptId, sessionId }, previousRevision: previous,
1176
- }) };
1177
- }
1178
- catch (error) {
1179
- return completedAttemptId
1180
- ? { status: 'invalid', code: 'spec-author-report-invalid', attemptId: completedAttemptId }
1181
- : { status: 'retryable', code: 'spec-author-report-invalid' };
1182
- }
1183
- },
1184
- review: async ({ context, state, mode, reviewerSessionId: sessionId, signal, invocationState }) => {
1185
- let completedAttemptId;
1186
- try {
1187
- const result = await reportOperation.run({
1188
- operation: 'spec-review', runId: context.runId, worktreePath: context.worktreePath,
1189
- workflowGeneration: context.workflowGeneration, signal, invocationState,
1190
- forbiddenAttemptIds: state.revisions.map((revision) => revision.author.attemptId),
1191
- promptFacts: [
1192
- `Reviewer session ID: ${sessionId}. Review mode: ${mode}.`,
1193
- `Issue authority and frozen criteria: ${canonicalJson({ issue: context.issue, frozenCriteria: context.frozenCriteria })}.`,
1194
- `Immutable spec delivery state: ${canonicalJson(state)}.`,
1195
- ],
1196
- });
1197
- if (result.status === 'cancelled')
1198
- return { status: 'cancelled' };
1199
- if (result.status === 'retryable' || result.status === 'safe-halt')
1200
- return { status: 'retryable', code: result.code };
1201
- if (result.status === 'blocked')
1202
- return { status: 'blocked', kind: result.kind, code: result.code };
1203
- completedAttemptId = result.attemptId;
1204
- const raw = decodeAgentReportForValidation(result.reportBytes);
1205
- if (raw.mode !== mode || raw.reviewerSessionId !== sessionId || !Array.isArray(raw.coverage) || !Array.isArray(raw.defects)
1206
- || !Array.isArray(raw.affectedDefectIds) || !Array.isArray(raw.affectedContracts) || !Array.isArray(raw.acceptedRisks)
1207
- || typeof raw.coverageInvalidated !== 'boolean'
1208
- || !['approved', 'needs-work', 'rejected'].includes(raw.verdict))
1209
- return { status: 'invalid', code: 'spec-review-report-invalid', attemptId: result.attemptId };
1210
- const target = state.revisions.at(-1);
1211
- const defects = validateCodeReviewDefects(raw.defects, target.revision);
1212
- const report = {
1213
- version: 1, targetRevision: target.revision, targetSha256: target.revisionSha256, mode,
1214
- verdict: raw.verdict, reviewer: { attemptId: result.attemptId, sessionId },
1215
- coverage: raw.coverage, defects,
1216
- affectedDefectIds: raw.affectedDefectIds,
1217
- affectedContracts: raw.affectedContracts,
1218
- closureRequestSha256: mode === 'closure' ? state.review.closureRequestSha256 : null,
1219
- acceptedRisks: [], coverageInvalidated: raw.coverageInvalidated,
1220
- };
1221
- return { status: 'completed', value: report, reportSha256: result.reportSha256 };
1222
- }
1223
- catch {
1224
- return completedAttemptId
1225
- ? { status: 'invalid', code: 'spec-review-report-invalid', attemptId: completedAttemptId }
1226
- : { status: 'retryable', code: 'spec-review-report-invalid' };
1227
- }
1228
- },
1229
- };
1230
1194
  const readConfig = async (requestedRoot) => {
1231
1195
  if (resolve(requestedRoot) !== targetRoot)
1232
1196
  throw new Error('runtime target root mismatch');
@@ -1237,31 +1201,17 @@ export function createV2Runtime(input) {
1237
1201
  codex: { ...parsed.codex, command: await resolveRuntimeCodex(parsed.codex.command) },
1238
1202
  };
1239
1203
  const path = join(targetRoot, config.runner.stateDir, 'v2', 'run-state.json');
1240
- if (!runRecords) {
1241
- const processStartIdentity = await readProcessStartIdentity(process.pid);
1242
- if (!processStartIdentity)
1243
- throw new Error('run-store owner process identity is unavailable');
1244
- runRecords = new FileRunRecordWriter(path, {
1245
- host: hostname(), bootId: input.bootId, pid: process.pid, processStartIdentity,
1246
- inspectProcessIdentity: async (pid) => {
1247
- const identity = await readProcessStartIdentity(pid);
1248
- if (identity)
1249
- return { processStartIdentity: identity };
1250
- try {
1251
- process.kill(pid, 0);
1252
- return 'unknown';
1253
- }
1254
- catch (error) {
1255
- return isErrorCode(error, 'ESRCH') ? 'absent' : 'unknown';
1256
- }
1257
- },
1258
- exclusiveRepositoryOwnership: true,
1259
- });
1260
- }
1204
+ if (!runRecords)
1205
+ runRecords = new FileRunRecordWriter(path);
1261
1206
  currentConfig = config;
1262
1207
  return { bytes, config };
1263
1208
  };
1264
1209
  const records = {
1210
+ inspect: async () => {
1211
+ if (!runRecords)
1212
+ throw new Error('run store used before config');
1213
+ return runRecords.inspect();
1214
+ },
1265
1215
  read: async () => {
1266
1216
  if (!runRecords)
1267
1217
  throw new Error('run store used before config');
@@ -1272,11 +1222,6 @@ export function createV2Runtime(input) {
1272
1222
  throw new Error('run store used before config');
1273
1223
  return runRecords.compareAndSwap(generation, next);
1274
1224
  },
1275
- markPublicationEffectPossible: async () => {
1276
- if (!runRecords?.markPublicationEffectPossible)
1277
- return;
1278
- await runRecords.markPublicationEffectPossible();
1279
- },
1280
1225
  };
1281
1226
  const capabilities = createCheckedChangeCapabilities();
1282
1227
  const proof = {
@@ -1284,27 +1229,8 @@ export function createV2Runtime(input) {
1284
1229
  const config = requireConfig(currentConfig);
1285
1230
  const checked = capabilities.verifyAndRead(proofInput.checkedChange);
1286
1231
  const repoKey = sha256(checked.payload.canonicalRepository);
1287
- const ownerProcessIdentity = await readProcessStartIdentity(process.pid);
1288
- if (!ownerProcessIdentity)
1289
- throw new Error('proof-store owner process identity is unavailable');
1290
- const proofRecords = new FileProofRecordWriter(join(orchestratorHome, 'v2', repoKey, 'proofs'), {
1291
- host: hostname(), bootId: input.bootId, pid: process.pid, processStartIdentity: ownerProcessIdentity,
1292
- inspectProcessIdentity: async (pid) => {
1293
- const identity = await readProcessStartIdentity(pid);
1294
- if (identity)
1295
- return { processStartIdentity: identity };
1296
- try {
1297
- process.kill(pid, 0);
1298
- return 'unknown';
1299
- }
1300
- catch (error) {
1301
- return isErrorCode(error, 'ESRCH') ? 'absent' : 'unknown';
1302
- }
1303
- },
1304
- exclusiveRepositoryOwnership: true,
1305
- });
1306
1232
  const issueWorktreePath = resolve(targetRoot, config.runner.workspaceRoot, `issue-${checked.payload.issueNumber}`);
1307
- const worktreePath = proofInput.executionLease?.path ?? issueWorktreePath;
1233
+ const worktreePath = proofInput.materialization?.path ?? issueWorktreePath;
1308
1234
  const androidLease = new FileAndroidLeaseVerifier({
1309
1235
  leaseRoot: join(orchestratorHome, 'v2', repoKey, 'leases'),
1310
1236
  worktreeRoot: worktreePath,
@@ -1323,10 +1249,9 @@ export function createV2Runtime(input) {
1323
1249
  });
1324
1250
  const acceptanceProof = new AcceptanceProof({
1325
1251
  checkedChangeReader: capabilities,
1326
- proofRecords,
1327
1252
  proofAgent,
1328
- inspectFreshness: async (payload, executionLease) => {
1329
- const checkPolicySha256 = sha256(canonicalJson(resolveIssueCheckPolicy(proofInput.issue.body, config.checks).checks));
1253
+ inspectFreshness: async (payload, materialization) => {
1254
+ const checkPolicySha256 = selectedConfiguredCheckPolicySha256(payload.checks, resolveIssueCheckPolicy(proofInput.issue.body, config.checks).checks);
1330
1255
  if (payload.version === 1)
1331
1256
  return {
1332
1257
  ...await (proofFreshnessGit.snapshotIgnoringUntrackedRoot
@@ -1334,11 +1259,11 @@ export function createV2Runtime(input) {
1334
1259
  : git.snapshot(worktreePath)),
1335
1260
  checkPolicySha256,
1336
1261
  };
1337
- if (!executionLease || !git.candidateV2)
1262
+ if (!materialization || !git.candidateV2)
1338
1263
  throw new CandidateProofInspectionError('candidate-git-v2-required');
1339
- const inspection = await git.candidateV2.inspectExecution({
1264
+ const inspection = await git.candidateV2.inspectMaterialization({
1340
1265
  binding: payload.binding,
1341
- lease: executionLease,
1266
+ materialization,
1342
1267
  artifactDir: config.proof.artifactDir,
1343
1268
  });
1344
1269
  if (inspection.kind === 'failed')
@@ -1352,7 +1277,6 @@ export function createV2Runtime(input) {
1352
1277
  androidLease,
1353
1278
  iosLease,
1354
1279
  proofArtifactDir: config.proof.artifactDir,
1355
- now,
1356
1280
  signal: controller.signal,
1357
1281
  });
1358
1282
  const androidRelevant = !!config.proof.android && isAndroidProofRelevant(proofInput.issue, proofInput.frozenCriteria, checked.payload.changedFiles);
@@ -1360,22 +1284,11 @@ export function createV2Runtime(input) {
1360
1284
  const runnerPreparedArtifactSha256 = {};
1361
1285
  const runnerPreparationWarnings = [];
1362
1286
  let preparationAttempted = false;
1363
- if (!proofInput.workflowGeneration)
1364
- throw new Error('proof workflow generation is required');
1365
- const iosTooling = await discoverIosTooling(commandExecutor, iosXcrunPath);
1366
- const iosProofInputs = {
1367
- helperPath: join(proofInput.workflowGeneration.generationRoot, 'skills', 'acceptance-proof', 'tools', 'ios-lease.mjs'),
1368
- leaseRoot: join(orchestratorHome, 'v2', repoKey, 'leases'),
1369
- leaseArtifactPath: resolve(worktreePath, config.proof.artifactDir, proofInput.proofId, 'ios-lease.json'),
1370
- proofId: proofInput.proofId, ownerPid: process.pid, xcrunPath: iosXcrunPath,
1371
- runtimeId: iosTooling?.runtimeId ?? null, deviceTypeId: iosTooling?.deviceTypeId ?? null,
1372
- };
1373
1287
  const result = await acceptanceProof.proveChange({
1374
1288
  ...proofInput,
1375
1289
  runnerPreparedArtifactPaths,
1376
1290
  runnerPreparedArtifactSha256,
1377
1291
  runnerPreparationWarnings,
1378
- iosProofInputs,
1379
1292
  beforeAgentLaunch: async () => {
1380
1293
  await proofInput.beforeAgentLaunch?.();
1381
1294
  if (!androidRelevant || preparationAttempted)
@@ -1401,10 +1314,10 @@ export function createV2Runtime(input) {
1401
1314
  }
1402
1315
  },
1403
1316
  });
1404
- if (checked.payload.version === 2 && proofInput.executionLease && git.candidateV2 && result.status === 'passed') {
1317
+ if (checked.payload.version === 2 && proofInput.materialization && git.candidateV2 && result.status === 'passed') {
1405
1318
  const artifacts = result.receipt.publishableEvidence.map((artifact) => ({ relativePath: artifact.ref, sha256: artifact.sha256 }));
1406
1319
  const copied = await git.candidateV2.copyProofArtifacts({
1407
- lease: proofInput.executionLease,
1320
+ materialization: proofInput.materialization,
1408
1321
  issueWorktreePath,
1409
1322
  artifactDir: config.proof.artifactDir,
1410
1323
  proofId: proofInput.proofId,
@@ -1449,6 +1362,8 @@ export function createV2Runtime(input) {
1449
1362
  id: comment.id,
1450
1363
  body: comment.body,
1451
1364
  authorAssociation: comment.authorAssociation,
1365
+ author: comment.author.login,
1366
+ authorId: comment.author.id,
1452
1367
  createdAt: comment.createdAt,
1453
1368
  updatedAt: comment.updatedAt,
1454
1369
  })),
@@ -1461,9 +1376,10 @@ export function createV2Runtime(input) {
1461
1376
  removeLabels: current.filter((label) => !labels.includes(label)),
1462
1377
  });
1463
1378
  },
1379
+ getRepositoryPermission: (login, expectedUserId) => input.issues.getRepositoryPermission(login, expectedUserId),
1464
1380
  transitionToBlocked: async (issueNumber, labels) => {
1465
1381
  const current = await input.issues.getLabels(issueNumber);
1466
- if (current.includes(labels.review) || current.includes(labels.waitingHuman))
1382
+ if (current.includes(labels.review))
1467
1383
  return;
1468
1384
  const hasRunnerStatus = current.some((label) => (label === labels.auto || label === labels.running || label === labels.blocked));
1469
1385
  if (!hasRunnerStatus)
@@ -1480,16 +1396,17 @@ export function createV2Runtime(input) {
1480
1396
  const matches = (await input.pullRequests.listAllByHeadBranch(headBranch))
1481
1397
  .filter((pullRequest) => pullRequest.state === 'OPEN' && pullRequest.baseRefName === baseBranch);
1482
1398
  if (matches.length > 1)
1483
- throw new Error('multiple open pull requests match publication intent');
1399
+ throw new Error('multiple open pull requests match publication pendingEffect');
1484
1400
  const match = matches[0];
1485
1401
  if (!match)
1486
1402
  return undefined;
1403
+ const reviewTarget = await input.pullRequests.getReviewTarget(match.number);
1487
1404
  return {
1488
1405
  url: match.url,
1489
1406
  body: match.body,
1490
1407
  number: match.number,
1491
1408
  nodeId: match.nodeId,
1492
- ...(match.headSha ? { headSha: match.headSha } : {}),
1409
+ ...(reviewTarget ? { headSha: reviewTarget.headRefOid } : {}),
1493
1410
  };
1494
1411
  },
1495
1412
  createDraft: async ({ title, body, headBranch, baseBranch }) => input.pullRequests.createDraftPullRequest({ title, body, headBranch, baseBranch }),
@@ -1500,54 +1417,10 @@ export function createV2Runtime(input) {
1500
1417
  return { id: comment.id, body: comment.body };
1501
1418
  },
1502
1419
  },
1503
- reviewFeedback: new ReviewFeedbackCoordinator({ pullRequests: input.pullRequests, issues: input.issues, now }),
1420
+ reviewFeedback: new ReviewFeedbackObserver({ pullRequests: input.pullRequests, issues: input.issues, now }),
1504
1421
  git,
1505
- routeCoordinator: {
1506
- run: ({ state, ...routeInput }) => new RouteCoordinator({
1507
- state,
1508
- operation: reportOperation,
1509
- now,
1510
- createReceipt: ({ artifact, triage, review, decidedAt }) => {
1511
- if (artifact.status === 'blocked')
1512
- throw new Error('blocked triage cannot create a route receipt');
1513
- const receipt = {
1514
- version: 1,
1515
- route: artifact.status,
1516
- triage,
1517
- review,
1518
- artifact,
1519
- decisionSha256: '',
1520
- decidedAt,
1521
- assumptions: [...artifact.assumptions],
1522
- };
1523
- receipt.decisionSha256 = hashRouteDecision(receipt);
1524
- return validateRouteReceipt(receipt, triage.generationHash);
1525
- },
1526
- }).run(routeInput),
1527
- },
1528
- routeContinuations: {
1529
- direct: async () => ({ status: 'completed' }),
1530
- specRequired: (context, state, signal) => new SpecCoordinator({
1531
- state, operation: specOperation, createAuthorSessionId: randomUUID, createReviewerSessionId: randomUUID,
1532
- }).run(context, signal),
1533
- awaitingUser: async (context, state, signal) => {
1534
- const config = requireConfig(currentConfig);
1535
- return new WaitingHumanCoordinator({
1536
- issues: input.issues,
1537
- labels: {
1538
- auto: config.github.labels.auto.name,
1539
- running: config.github.labels.running.name,
1540
- blocked: config.github.labels.blocked.name,
1541
- review: config.github.labels.review.name,
1542
- waitingHuman: config.github.labels.waitingHuman.name,
1543
- },
1544
- now,
1545
- }).run(context, state, signal);
1546
- },
1547
- },
1548
1422
  implementationAgent,
1549
1423
  implementationReviewer,
1550
- waitForReviewProcessAbsence: waitForProcessGroupAbsent,
1551
1424
  checks: {
1552
1425
  supportsLaunchOwnership: true,
1553
1426
  run: async ({ source, command, cwd, signal, onLaunched }) => {
@@ -1566,12 +1439,56 @@ export function createV2Runtime(input) {
1566
1439
  await writeDurableAtomicFile(resolve(targetRoot, relativePath), `${canonicalJson({ version: 1, runId, code, summary, recordedAt: now() })}\n`);
1567
1440
  return { id: `evidence:${runId}:${code}`, path: relativePath };
1568
1441
  },
1442
+ outcomeEvidencePath: (runId, code, summarySha256) => `${requireConfig(currentConfig).runner.stateDir}/v2/evidence/${runId}/${sha256(code)}-${summarySha256}.json`,
1443
+ inspectOutcomeEvidence: async (path) => {
1444
+ try {
1445
+ return { sha256: sha256(await readRegularFile(resolve(targetRoot, path))) };
1446
+ }
1447
+ catch (error) {
1448
+ if (isErrorCode(error, 'ENOENT'))
1449
+ return undefined;
1450
+ throw error;
1451
+ }
1452
+ },
1453
+ writeOutcomeEvidence: ({ path, bytes, sha256: expectedSha256 }) => writeExactDurableBytes(resolve(targetRoot, path), bytes, expectedSha256),
1569
1454
  packageVersion: input.packageVersion,
1570
1455
  createWorkflowGeneration: input.createWorkflowGeneration,
1571
1456
  verifyWorkflowGeneration,
1572
1457
  createRunId: input.createRunId ?? randomUUID,
1573
1458
  createProofId: input.createProofId ?? randomUUID,
1574
1459
  createReviewSessionId: input.createAttemptId ?? randomUUID,
1460
+ processIdentity: {
1461
+ host: hostname(),
1462
+ bootId: input.bootId,
1463
+ capture: async (pid, processGroupId) => {
1464
+ const captured = await captureProcessStartIdentity({ platform: process.platform, pid, processGroupId });
1465
+ return captured.status === 'available' ? captured.identity : undefined;
1466
+ },
1467
+ observe: async (identity) => ({
1468
+ leader: await observeProcessIdentity({ platform: process.platform, ...identity }),
1469
+ group: observeProcessGroup(identity.processGroupId),
1470
+ }),
1471
+ },
1472
+ inspectAttemptResult: async (path) => {
1473
+ try {
1474
+ const bytes = await readRegularFile(path);
1475
+ return { bytes, sha256: sha256(bytes) };
1476
+ }
1477
+ catch (error) {
1478
+ if (isErrorCode(error, 'ENOENT'))
1479
+ return undefined;
1480
+ throw error;
1481
+ }
1482
+ },
1483
+ observeAttemptCleanup: async (attempt) => {
1484
+ return observeAttemptReadViewCleanup({
1485
+ orchestratorHome,
1486
+ canonicalRepository: requireCanonicalRepository(currentConfig),
1487
+ identity: attempt,
1488
+ });
1489
+ },
1490
+ writeAttemptResult: ({ path, bytes, sha256: expectedSha256 }) => writeExactDurableBytes(path, bytes, expectedSha256),
1491
+ attemptResultPath: ({ canonicalRepository, runId, attemptId }) => join(resolve(orchestratorHome), 'v2', sha256(canonicalRepository), 'runs', runId, 'attempts', attemptId, 'report.json'),
1575
1492
  now,
1576
1493
  signal: controller.signal,
1577
1494
  });
@@ -1624,6 +1541,24 @@ async function readRegularFile(path) {
1624
1541
  await handle.close();
1625
1542
  }
1626
1543
  }
1544
+ async function writeExactDurableBytes(path, bytes, expectedSha256) {
1545
+ if (sha256(bytes) !== expectedSha256)
1546
+ throw new Error('durable result bytes do not match expected SHA-256');
1547
+ try {
1548
+ const existing = await readRegularFile(path);
1549
+ if (sha256(existing) !== expectedSha256)
1550
+ throw new Error('durable result path already contains different bytes');
1551
+ return;
1552
+ }
1553
+ catch (error) {
1554
+ if (!isErrorCode(error, 'ENOENT'))
1555
+ throw error;
1556
+ }
1557
+ await writeDurableAtomicFile(path, bytes, 0o600);
1558
+ const observed = await readRegularFile(path);
1559
+ if (sha256(observed) !== expectedSha256)
1560
+ throw new Error('durable result write postcondition failed');
1561
+ }
1627
1562
  async function inspectRegularFile(path) {
1628
1563
  const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
1629
1564
  try {
@@ -1735,6 +1670,10 @@ async function runSpawnCheck(file, args, cwd, signal, timeoutMs, onLaunched) {
1735
1670
  status: code === 0 ? 'passed' : 'failed',
1736
1671
  output: Buffer.concat(chunks),
1737
1672
  outputSha256: outputHash.digest('hex'),
1673
+ observation: {
1674
+ leader: 'absent',
1675
+ group: child.pid ? observeProcessGroup(child.pid) : 'unknown',
1676
+ },
1738
1677
  });
1739
1678
  })().catch(rejectCheck);
1740
1679
  });
@@ -1845,8 +1784,7 @@ async function prepareContainedAttempt(input) {
1845
1784
  operation: input.operationId,
1846
1785
  bootId: input.bootId,
1847
1786
  });
1848
- const reportOnly = input.operationId === 'triage' || input.operationId === 'ambiguity-review'
1849
- || input.operationId === 'code-review' || input.operationId === 'spec-review';
1787
+ const reportOnly = input.operationId === 'code-review';
1850
1788
  if ((reportOnly
1851
1789
  ? snapshot.policy.sandboxMode !== 'read-only'
1852
1790
  || snapshot.policy.worktreeAccess !== 'read-only'
@@ -1882,6 +1820,8 @@ async function prepareContainedAttempt(input) {
1882
1820
  tmpDir,
1883
1821
  policy: structuredClone(snapshot.policy),
1884
1822
  profile,
1823
+ reviewers: Object.keys(snapshot.reviewerProfiles).sort(),
1824
+ reviewerProfiles: structuredClone(snapshot.reviewerProfiles),
1885
1825
  };
1886
1826
  }
1887
1827
  async function artifactInventory(root, logicalRoot) {
@@ -1921,9 +1861,6 @@ function changedArtifactPaths(before, after) {
1921
1861
  .filter((path) => before.get(path) !== after.get(path))
1922
1862
  .sort();
1923
1863
  }
1924
- function artifactInventoryMatches(actual, expected) {
1925
- return canonicalJson(Object.fromEntries([...actual.entries()].sort())) === canonicalJson(expected ?? null);
1926
- }
1927
1864
  async function fingerprintRepositoryPath(root, relativePath) {
1928
1865
  const segments = relativePath.split('/');
1929
1866
  let current = root;
@@ -1960,63 +1897,6 @@ async function fingerprintDeniedEntry(path, existingInfo) {
1960
1897
  }
1961
1898
  return { kind: 'directory', entries };
1962
1899
  }
1963
- async function waitForProcessGroupAbsent(processGroupId) {
1964
- while (true) {
1965
- try {
1966
- process.kill(-processGroupId, 0);
1967
- }
1968
- catch (error) {
1969
- if (isErrorCode(error, 'ESRCH'))
1970
- return;
1971
- if (!isErrorCode(error, 'EPERM'))
1972
- throw error;
1973
- }
1974
- await new Promise((resolveWait) => setTimeout(resolveWait, 1_000));
1975
- }
1976
- }
1977
- async function readProcessStartIdentity(pid) {
1978
- if (!Number.isSafeInteger(pid) || pid <= 0)
1979
- return undefined;
1980
- if (platform() === 'linux') {
1981
- try {
1982
- const text = await readFile(`/proc/${pid}/stat`, 'utf8');
1983
- const close = text.lastIndexOf(') ');
1984
- const fields = close < 0 ? [] : text.slice(close + 2).trim().split(/\s+/u);
1985
- return fields[19] ? `linux:${fields[19]}` : undefined;
1986
- }
1987
- catch {
1988
- return undefined;
1989
- }
1990
- }
1991
- return await new Promise((resolveIdentity) => {
1992
- execFile('/bin/ps', ['-o', 'lstart=', '-p', String(pid)], { encoding: 'utf8' }, (error, stdout) => {
1993
- const value = error ? '' : stdout.trim().replace(/\s+/gu, ' ');
1994
- resolveIdentity(value ? `${platform()}:${value}` : undefined);
1995
- });
1996
- });
1997
- }
1998
- async function inspectReportProcess(invocation) {
1999
- const processStartIdentity = await readProcessStartIdentity(invocation.pid);
2000
- let processGroupAlive;
2001
- try {
2002
- process.kill(-invocation.processGroupId, 0);
2003
- processGroupAlive = true;
2004
- }
2005
- catch (error) {
2006
- if (isErrorCode(error, 'ESRCH'))
2007
- processGroupAlive = false;
2008
- else if (isErrorCode(error, 'EPERM'))
2009
- processGroupAlive = true;
2010
- else
2011
- processGroupAlive = 'unknown';
2012
- }
2013
- if (!processStartIdentity) {
2014
- return processGroupAlive === false
2015
- ? { status: 'absent', processGroupAlive: false }
2016
- : { status: 'unknown' };
2017
- }
2018
- return { status: 'present', processStartIdentity, processGroupAlive };
2019
- }
2020
1900
  export async function materializeReportReadView(input) {
2021
1901
  await assertDisjointReadViewPaths(input.worktreePath, input.destination);
2022
1902
  const deniedPaths = normalizeReadViewDeniedPaths(input.deniedPaths);