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,53 +1,33 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { validateCandidateBinding } from './candidate.js';
2
3
  import { canonicalJson } from './containment.js';
3
- import { hashClosureRequest, validateCodeReviewDefects } from './code-review-report.js';
4
+ import { validateCodeReviewDefects } from './code-review-report.js';
4
5
  const SHA256 = /^[0-9a-f]{64}$/u;
5
- const MAX_ITEMS = 256;
6
- export const MAX_DIRECT_REVIEW_REPORT_REPAIRS = 4;
7
- export function directReviewTargetFingerprint(input) {
8
- const changedFiles = sortedUniqueStrings(input.changedFiles, 'direct review changed files');
9
- assertSha256(input.routeDecisionSha256, 'direct review route decision hash');
6
+ export function directReviewCandidateTargetFingerprint(input) {
7
+ const binding = validateCandidateBinding(input.binding, 'direct review candidate binding');
8
+ assertSha256(input.deliveryAuthoritySha256, 'direct review delivery authority hash');
10
9
  assertSha256(input.workflowGenerationHash, 'direct review workflow generation hash');
11
10
  if (!Number.isSafeInteger(input.cycle) || input.cycle < 1)
12
11
  throw new Error('direct review cycle is invalid');
13
- return createHash('sha256').update(`codex-orchestrator-direct-review-target-v1\0${canonicalJson({
14
- snapshot: input.snapshot,
15
- changedFiles,
16
- routeDecisionSha256: input.routeDecisionSha256,
12
+ return createHash('sha256').update(`codex-orchestrator-direct-review-target-v2\0${canonicalJson({
13
+ bindingId: binding.bindingId,
14
+ expectedHeadSha: binding.expectedHeadSha,
15
+ candidateCommitSha: binding.candidateCommitSha,
16
+ candidateTreeSha: binding.candidateTreeSha,
17
+ canonicalChangedFiles: binding.canonicalChangedFiles,
18
+ sourceWorktreeIdentity: binding.sourceWorktreeIdentity,
19
+ deliveryAuthoritySha256: input.deliveryAuthoritySha256,
17
20
  workflowGenerationHash: input.workflowGenerationHash,
18
21
  cycle: input.cycle,
19
22
  frozenCriteria: input.frozenCriteria,
20
23
  })}`).digest('hex');
21
24
  }
22
- export function prepareDirectReviewInvocation(state, invocation) {
23
- if (state.status !== 'active' || state.invocation || state.stage === null || state.stage.endsWith('-repair')) {
24
- throw new Error('direct review is not ready to prepare an invocation');
25
- }
26
- if (invocation.targetRevision !== state.targetRevision || invocation.targetFingerprint !== state.targetFingerprint) {
27
- throw new Error('direct review invocation target mismatch');
28
- }
29
- return {
30
- ...structuredClone(state),
31
- invocation: { ...structuredClone(invocation), status: 'prepared', pid: null, processGroupId: null },
32
- };
33
- }
34
- export function launchDirectReviewInvocation(state, input) {
35
- if (!state.invocation || state.invocation.status !== 'prepared' || state.invocation.attemptId !== input.attemptId
36
- || !Number.isSafeInteger(input.pid) || input.pid < 1 || !Number.isSafeInteger(input.processGroupId) || input.processGroupId < 1) {
37
- throw new Error('direct review prepared invocation mismatch');
38
- }
39
- return {
40
- ...structuredClone(state),
41
- invocation: { ...structuredClone(state.invocation), status: 'launched', pid: input.pid, processGroupId: input.processGroupId },
42
- };
43
- }
44
- export function acceptApprovedDirectReview(state, report, artifactSha256) {
25
+ export function acceptApprovedDirectReview(state, report, artifactSha256, candidateTreeSha, mode) {
45
26
  assertSha256(artifactSha256, 'direct review accepted report hash');
46
- if (state.status !== 'active' || !state.invocation || state.stage === null || report.verdict !== 'approved'
27
+ assertGitSha(candidateTreeSha, 'direct review approved candidate tree');
28
+ if (state.status !== 'active' || state.stage === null || report.verdict !== 'approved'
47
29
  || report.targetRevision !== state.targetRevision || report.targetFingerprint !== state.targetFingerprint
48
- || report.operation !== state.invocation.operation || report.mode !== state.invocation.mode
49
- || report.reviewerSessionId !== state.invocation.reviewerSessionId
50
- || report.closureRequestSha256 !== state.invocation.closureRequestSha256) {
30
+ || report.operation !== 'code-review' || report.reviewerSessionId !== state.review.reviewerSessionId) {
51
31
  throw new Error('accepted direct review report correlation mismatch');
52
32
  }
53
33
  const defects = mergeDefectLedger(state, report);
@@ -55,49 +35,74 @@ export function acceptApprovedDirectReview(state, report, artifactSha256) {
55
35
  && defect.status !== 'verified' && defect.status !== 'superseded')) {
56
36
  throw new Error('approved direct review merge has unresolved defects');
57
37
  }
38
+ const impactedCoverage = new Set([
39
+ ...state.review.defects
40
+ .filter((defect) => defect.status === 'fixed' && defect.statusTargetRevision === state.targetRevision)
41
+ .flatMap((defect) => defect.affectedTargets),
42
+ ...state.repairFindings
43
+ .filter((finding) => finding.status === 'fixed')
44
+ .flatMap((finding) => finding.affectedContracts),
45
+ ].filter((target) => target.startsWith('contract:')).map((target) => target.slice('contract:'.length)));
46
+ const mergedCoverage = [...new Set([
47
+ ...state.review.coverage.filter((coverage) => !impactedCoverage.has(coverage)),
48
+ ...report.coverage,
49
+ ])].sort();
58
50
  const clearTrack = {
59
51
  ...structuredClone(state.review),
60
52
  disposition: 'clear',
61
- coverage: [...report.coverage],
62
- defects,
63
- affectedDefectIds: [],
53
+ coverage: mergedCoverage,
54
+ defects: defects.filter((defect) => defect.status !== 'verified' && defect.status !== 'superseded'),
64
55
  acceptedReportSha256: artifactSha256,
65
56
  };
66
- const { invocation: _invocation, ...withoutInvocation } = structuredClone(state);
67
57
  const outcomes = new Map(report.repairFindingOutcomes.map((outcome) => [outcome.id, outcome.status]));
68
58
  return {
69
- ...withoutInvocation,
59
+ ...structuredClone(state),
70
60
  status: 'clear',
61
+ previousTarget: mode === 'targeted' ? structuredClone(state.previousTarget) : null,
71
62
  review: clearTrack,
72
63
  repairFindings: state.repairFindings.map((finding) => {
73
64
  const outcome = outcomes.get(finding.id);
74
65
  return outcome ? { ...structuredClone(finding), status: outcome } : structuredClone(finding);
75
- }),
66
+ }).filter((finding) => finding.status !== 'verified'),
76
67
  };
77
68
  }
78
- export function acceptNeedsWorkDirectReview(state, report, artifactSha256) {
69
+ export function acceptNeedsWorkDirectReview(state, report, artifactSha256, candidateTreeSha) {
79
70
  assertSha256(artifactSha256, 'direct review accepted report hash');
71
+ assertGitSha(candidateTreeSha, 'direct review candidate tree');
80
72
  const reopenedFinding = report.repairFindingOutcomes.some((outcome) => outcome.status === 'reopened');
81
- if (state.status !== 'active' || !state.invocation || state.stage === null || report.verdict !== 'needs-work'
73
+ if (state.status !== 'active' || state.stage === null || report.verdict !== 'needs-work'
82
74
  || report.targetRevision !== state.targetRevision || report.targetFingerprint !== state.targetFingerprint
83
- || report.operation !== state.invocation.operation || report.mode !== state.invocation.mode
84
- || report.reviewerSessionId !== state.invocation.reviewerSessionId
85
- || report.closureRequestSha256 !== state.invocation.closureRequestSha256
75
+ || report.operation !== 'code-review' || report.reviewerSessionId !== state.review.reviewerSessionId
86
76
  || (!report.defects.some((defect) => defect.status === 'open' || defect.status === 'reopened') && !reopenedFinding)) {
87
77
  throw new Error('needs-work direct review report correlation mismatch');
88
78
  }
89
- const { invocation: _invocation, ...withoutInvocation } = structuredClone(state);
90
79
  const outcomes = new Map(report.repairFindingOutcomes.map((outcome) => [outcome.id, outcome.status]));
80
+ const blockedContracts = new Set([
81
+ ...report.defects
82
+ .filter((defect) => defect.status === 'open' || defect.status === 'reopened')
83
+ .flatMap((defect) => defect.affectedTargets),
84
+ ...state.repairFindings
85
+ .filter((finding) => outcomes.get(finding.id) === 'reopened')
86
+ .flatMap((finding) => finding.affectedContracts),
87
+ ].filter((target) => target.startsWith('contract:')).map((target) => target.slice('contract:'.length)));
88
+ const mergedCoverage = [...new Set([
89
+ ...state.review.coverage,
90
+ ...report.coverage,
91
+ ])].filter((coverage) => !blockedContracts.has(coverage)).sort();
91
92
  return {
92
- ...withoutInvocation,
93
+ ...structuredClone(state),
93
94
  status: 'active',
94
95
  stage: 'review-repair',
96
+ previousTarget: {
97
+ targetRevision: state.targetRevision,
98
+ targetFingerprint: state.targetFingerprint,
99
+ candidateTreeSha,
100
+ },
95
101
  review: {
96
102
  ...structuredClone(state.review),
97
103
  disposition: 'active',
98
- coverage: [...report.coverage],
104
+ coverage: mergedCoverage,
99
105
  defects: mergeDefectLedger(state, report),
100
- affectedDefectIds: [],
101
106
  acceptedReportSha256: artifactSha256,
102
107
  },
103
108
  repairFindings: state.repairFindings.map((finding) => {
@@ -108,26 +113,20 @@ export function acceptNeedsWorkDirectReview(state, report, artifactSha256) {
108
113
  }
109
114
  function mergeDefectLedger(state, report) {
110
115
  const current = state.review.defects;
111
- if (report.mode === 'full') {
112
- if (current.length !== 0)
113
- throw new Error('Full review cannot replace an existing defect ledger');
116
+ if (current.length === 0)
114
117
  return structuredClone(report.defects);
115
- }
116
118
  const reported = new Map(report.defects.map((defect) => [defect.id, defect]));
117
- const affected = new Set(state.review.affectedDefectIds);
118
119
  const merged = current.map((existing) => {
119
120
  const next = reported.get(existing.id);
120
121
  if (!next) {
121
- if (!affected.has(existing.id))
122
- return structuredClone(existing);
123
- return { ...structuredClone(existing), status: 'reopened', statusTargetRevision: state.targetRevision, supersededBy: null };
122
+ if (existing.status === 'fixed')
123
+ throw new Error('review omitted an impacted defect ID');
124
+ return structuredClone(existing);
124
125
  }
125
126
  reported.delete(existing.id);
126
- if (!affected.has(existing.id))
127
- return structuredClone(existing);
128
- if (affected.has(existing.id) && existing.status === 'fixed'
127
+ if (existing.status === 'fixed'
129
128
  && !['verified', 'reopened', 'superseded'].includes(next.status)) {
130
- throw new Error('Closure defect transition is invalid');
129
+ throw new Error('full review defect transition is invalid');
131
130
  }
132
131
  return {
133
132
  ...structuredClone(next),
@@ -141,19 +140,30 @@ function mergeDefectLedger(state, report) {
141
140
  for (const added of reported.values()) {
142
141
  if (added.status !== 'open' || added.introducedTargetRevision !== state.targetRevision
143
142
  || added.statusTargetRevision !== state.targetRevision)
144
- throw new Error('Closure introduced defect is invalid');
143
+ throw new Error('full review introduced defect is invalid');
145
144
  merged.push(structuredClone(added));
146
145
  }
147
146
  return merged.sort((left, right) => left.id.localeCompare(right.id));
148
147
  }
149
- export function beginDirectReviewRepair(state, findings) {
150
- if (state.status !== 'clear' || state.review.disposition !== 'clear' || state.invocation || findings.length === 0) {
148
+ export function beginDirectReviewRepair(state, findings, candidateTreeSha) {
149
+ if (findings.length === 0 || (state.status !== 'clear'
150
+ && !(state.status === 'active' && state.stage === 'review-repair'))) {
151
151
  throw new Error('direct review is not clear for repair');
152
152
  }
153
+ if (state.status === 'active') {
154
+ return { ...structuredClone(state), repairFindings: mergeRepairFindings(state.repairFindings, findings) };
155
+ }
156
+ if (state.review.disposition !== 'clear')
157
+ throw new Error('direct review is not clear for repair');
153
158
  return {
154
159
  ...structuredClone(state),
155
160
  status: 'active',
156
161
  stage: 'review-repair',
162
+ previousTarget: candidateTreeSha ? {
163
+ targetRevision: state.targetRevision,
164
+ targetFingerprint: state.targetFingerprint,
165
+ candidateTreeSha,
166
+ } : structuredClone(state.previousTarget),
157
167
  review: { ...structuredClone(state.review), disposition: 'active' },
158
168
  repairFindings: mergeRepairFindings(state.repairFindings, findings),
159
169
  };
@@ -174,10 +184,11 @@ function mergeRepairFindings(current, incoming) {
174
184
  }
175
185
  return [...merged.values()].sort((left, right) => left.id.localeCompare(right.id));
176
186
  }
177
- export function prepareDirectReviewClosure(state, targetFingerprint) {
178
- assertSha256(targetFingerprint, 'direct review Closure target fingerprint');
179
- if (state.status !== 'active' || state.stage !== 'review-repair' || state.invocation) {
180
- throw new Error('direct review is not ready for Closure');
187
+ export function prepareDirectReview(state, targetFingerprint, reviewerSessionId) {
188
+ assertSha256(targetFingerprint, 'direct review target fingerprint');
189
+ assertText(reviewerSessionId, 'direct review reviewer session ID');
190
+ if (state.status !== 'active' || state.stage !== 'review-repair') {
191
+ throw new Error('direct review is not ready for another review');
181
192
  }
182
193
  const targetRevision = state.targetRevision + 1;
183
194
  const defects = state.review.defects.map((defect) => defect.status === 'open' || defect.status === 'reopened'
@@ -186,53 +197,32 @@ export function prepareDirectReviewClosure(state, targetFingerprint) {
186
197
  const repairFindings = state.repairFindings.map((finding) => finding.status === 'open' || finding.status === 'reopened'
187
198
  ? { ...structuredClone(finding), status: 'fixed' }
188
199
  : structuredClone(finding));
189
- const affectedIds = [
190
- ...defects.filter((defect) => defect.status === 'fixed').map((defect) => defect.id),
191
- ...repairFindings.filter((finding) => finding.status === 'fixed').map((finding) => finding.id),
192
- ].sort();
193
- if (affectedIds.length === 0)
194
- throw new Error('direct review Closure has no fixed targets');
200
+ if (![...defects, ...repairFindings].some((item) => item.status === 'fixed'))
201
+ throw new Error('direct review has no fixed targets');
195
202
  const nextState = {
196
203
  ...structuredClone(state),
197
- stage: 'review-closure',
204
+ stage: 'review',
198
205
  targetRevision,
199
206
  targetFingerprint,
207
+ previousTarget: structuredClone(state.previousTarget),
200
208
  review: {
201
209
  ...structuredClone(state.review),
202
- mode: 'closure',
210
+ reviewerSessionId,
203
211
  reportRepairs: 0,
204
212
  transportRetries: 0,
205
213
  defects,
206
- affectedDefectIds: affectedIds,
214
+ acceptedReportSha256: null,
207
215
  },
208
216
  repairFindings,
209
217
  };
210
- const closureRequestSha256 = directReviewClosureRequestSha256(nextState);
211
- return {
212
- state: nextState,
213
- closureRequestSha256,
214
- };
215
- }
216
- export function directReviewClosureRequestSha256(state) {
217
- if (state.status !== 'active' || state.stage !== 'review-closure' || state.review.mode !== 'closure') {
218
- throw new Error('direct review state is not a code-review Closure');
219
- }
220
- return hashClosureRequest({
221
- operation: 'code-review',
222
- targetRevision: state.targetRevision,
223
- targetFingerprint: state.targetFingerprint,
224
- affectedDefectIds: state.review.affectedDefectIds,
225
- fixedRepairFindings: state.repairFindings.filter((finding) => finding.status === 'fixed')
226
- .map((finding) => ({ id: finding.id, affectedContracts: finding.affectedContracts })),
227
- reviewFocus: ['acceptance-criteria', 'correctness', 'test-quality'],
228
- });
218
+ return nextState;
229
219
  }
230
220
  export function projectTerminalDirectReview(state, terminalOutcome, terminalCode) {
231
221
  if (state.status === 'terminal')
232
222
  throw new Error('direct review terminal projection is immutable');
233
223
  if (terminalCode !== undefined)
234
224
  assertText(terminalCode, 'direct review terminal code');
235
- const { invocation: _invocation, terminalCode: _priorCode, terminalOutcome: _priorOutcome, ...preserved } = structuredClone(state);
225
+ const { terminalCode: _priorCode, terminalOutcome: _priorOutcome, ...preserved } = structuredClone(state);
236
226
  return {
237
227
  ...preserved,
238
228
  status: 'terminal',
@@ -240,46 +230,27 @@ export function projectTerminalDirectReview(state, terminalOutcome, terminalCode
240
230
  terminalOutcome: structuredClone(terminalOutcome),
241
231
  };
242
232
  }
243
- export function canRecoverTerminalDirectReviewReport(state, options = {}) {
244
- return state.status === 'terminal' && state.terminalOutcome?.status === 'internal-error'
245
- && !state.invocation && state.stage !== null && state.stage !== 'review-repair'
246
- && state.review.disposition === 'active' && state.review.reportRepairs > 0
247
- && state.review.reportRepairs < MAX_DIRECT_REVIEW_REPORT_REPAIRS
248
- && (state.terminalCode === 'direct-review-report-malformed'
249
- || (state.terminalCode === undefined && options.allowLegacyMalformed === true));
250
- }
251
- export function recoverTerminalDirectReviewReport(state, options = {}) {
252
- if (!canRecoverTerminalDirectReviewReport(state, options)) {
253
- throw new Error('direct review terminal report is not recoverable');
254
- }
255
- const { terminalCode: _terminalCode, terminalOutcome: _terminalOutcome, ...withoutTerminal } = structuredClone(state);
256
- return {
257
- ...withoutTerminal,
258
- status: 'active',
259
- review: { ...withoutTerminal.review, transportRetries: 0 },
260
- };
261
- }
262
233
  export function createInitialDirectReview(input) {
263
234
  assertSha256(input.targetFingerprint, 'direct review target fingerprint');
264
235
  assertText(input.codeReviewerSessionId, 'code reviewer session ID');
265
236
  return {
266
237
  version: 1,
267
238
  status: 'active',
268
- stage: 'review-full',
239
+ stage: 'review',
269
240
  targetRevision: 1,
270
241
  targetFingerprint: input.targetFingerprint,
271
- review: activeTrack(input.codeReviewerSessionId, 'full'),
242
+ previousTarget: null,
243
+ review: activeTrack(input.codeReviewerSessionId),
272
244
  repairFindings: [],
273
245
  };
274
246
  }
275
247
  export function validateDirectReview(value, context) {
276
248
  const optional = [
277
- ...(hasOwn(value, 'invocation') ? ['invocation'] : []),
278
249
  ...(hasOwn(value, 'terminalCode') ? ['terminalCode'] : []),
279
250
  ...(hasOwn(value, 'terminalOutcome') ? ['terminalOutcome'] : []),
280
251
  ];
281
252
  assertExactObject(value, [
282
- 'version', 'status', 'stage', 'targetRevision', 'targetFingerprint', 'review', 'repairFindings', ...optional,
253
+ 'version', 'status', 'stage', 'targetRevision', 'targetFingerprint', 'previousTarget', 'review', 'repairFindings', ...optional,
283
254
  ], 'direct review');
284
255
  if (value.version !== 1 || !['active', 'clear', 'terminal'].includes(value.status)) {
285
256
  throw new Error('direct review version/status is invalid');
@@ -289,8 +260,8 @@ export function validateDirectReview(value, context) {
289
260
  if (!Number.isSafeInteger(value.targetRevision) || value.targetRevision < 0)
290
261
  throw new Error('direct review target revision is invalid');
291
262
  assertSha256(value.targetFingerprint, 'direct review target fingerprint');
263
+ const previousTarget = validatePreviousTarget(value.previousTarget, value.targetRevision);
292
264
  const review = validateTrack(value.review, 'review', value.targetRevision);
293
- const invocation = hasOwn(value, 'invocation') ? validateInvocation(value.invocation) : undefined;
294
265
  const repairFindings = validateRepairFindings(value.repairFindings, value.targetRevision);
295
266
  const terminalCode = hasOwn(value, 'terminalCode') ? value.terminalCode : undefined;
296
267
  if (terminalCode !== undefined)
@@ -304,7 +275,6 @@ export function validateDirectReview(value, context) {
304
275
  stage: value.stage,
305
276
  targetRevision: value.targetRevision,
306
277
  review,
307
- invocation,
308
278
  repairFindings,
309
279
  terminalCode: terminalCode,
310
280
  terminalOutcome,
@@ -315,19 +285,43 @@ export function validateDirectReview(value, context) {
315
285
  stage: value.stage,
316
286
  targetRevision: value.targetRevision,
317
287
  targetFingerprint: value.targetFingerprint,
288
+ previousTarget,
318
289
  review,
319
- ...(invocation ? { invocation } : {}),
320
290
  repairFindings,
321
291
  ...(terminalCode ? { terminalCode: terminalCode } : {}),
322
292
  ...(terminalOutcome ? { terminalOutcome } : {}),
323
293
  };
324
294
  }
295
+ function validatePreviousTarget(value, targetRevision) {
296
+ if (value === null)
297
+ return null;
298
+ assertExactObject(value, ['targetRevision', 'targetFingerprint', 'candidateTreeSha'], 'direct review previous target');
299
+ if (!Number.isSafeInteger(value.targetRevision)
300
+ || (value.targetRevision !== targetRevision && value.targetRevision !== targetRevision - 1)) {
301
+ throw new Error('direct review previous target revision is invalid');
302
+ }
303
+ assertSha256(value.targetFingerprint, 'direct review previous target fingerprint');
304
+ assertGitSha(value.candidateTreeSha, 'direct review previous target tree');
305
+ return {
306
+ targetRevision: value.targetRevision,
307
+ targetFingerprint: value.targetFingerprint,
308
+ candidateTreeSha: value.candidateTreeSha,
309
+ };
310
+ }
325
311
  function validateComposite(value, context) {
326
312
  if (value.status === 'terminal') {
327
- if (value.stage === null || value.targetRevision < 1 || value.invocation || !value.terminalOutcome
313
+ if (value.stage === null || value.targetRevision < 1 || !value.terminalOutcome
328
314
  || !['blocked', 'transport-failed', 'cancelled', 'internal-error'].includes(context.lifecycle)) {
329
315
  throw new Error('terminal direct review composite is invalid');
330
316
  }
317
+ if (value.terminalOutcome.status === 'internal-error') {
318
+ if (value.terminalCode === undefined || value.terminalCode !== context.terminalCode) {
319
+ throw new Error('terminal direct review code does not match run terminal outcome');
320
+ }
321
+ }
322
+ else if (value.terminalCode !== undefined || context.terminalCode !== undefined) {
323
+ throw new Error('non-internal terminal direct review has a terminal code');
324
+ }
331
325
  return;
332
326
  }
333
327
  if (value.terminalOutcome || value.terminalCode)
@@ -335,36 +329,26 @@ function validateComposite(value, context) {
335
329
  if (value.stage === null || value.targetRevision < 1)
336
330
  throw new Error('active direct review requires a stage and revision');
337
331
  if (value.status === 'active') {
338
- if (context.lifecycle !== 'implementing' && context.lifecycle !== 'safe-halt')
332
+ const allowed = value.stage === 'review'
333
+ ? ['checking', 'proving', 'reviewing', 'safe-halt']
334
+ : ['implementing', 'checking', 'proving', 'safe-halt'];
335
+ if (!allowed.includes(context.lifecycle))
339
336
  throw new Error('active direct review lifecycle is invalid');
340
337
  if (value.review.disposition !== 'active')
341
338
  throw new Error('active direct review stage has no active track');
342
- validateStageFields(value.stage, value.review, value.invocation, value.repairFindings);
339
+ validateStageFields(value.stage, value.review, value.repairFindings);
343
340
  }
344
341
  else {
345
- const proofSafeHalt = context.lifecycle === 'safe-halt' && context.process?.purpose === 'proof'
346
- && context.process.resumeLifecycle === 'proving' && context.process.resumeReviewStage === null;
347
- if ((!['checking', 'proving', 'publishing', 'review-ready'].includes(context.lifecycle) && !proofSafeHalt)
348
- || value.invocation || value.review.disposition !== 'clear') {
342
+ if (!['publishing', 'safe-halt', 'review-ready'].includes(context.lifecycle)
343
+ || value.review.disposition !== 'clear') {
349
344
  throw new Error('clear direct review composite is invalid');
350
345
  }
351
346
  }
352
- if (value.status === 'active' && context.lifecycle === 'safe-halt') {
353
- const reviewProcess = context.process?.purpose === 'code-review'
354
- && context.process.resumeLifecycle === 'implementing'
355
- && context.process.resumeReviewStage === value.stage;
356
- const feedbackImplementationProcess = value.stage === 'review-repair'
357
- && context.process?.purpose === 'implementation'
358
- && context.process.resumeLifecycle === 'implementing'
359
- && context.process.resumeReviewStage === null;
360
- if (!reviewProcess && !feedbackImplementationProcess)
361
- throw new Error('safe-halt review stage/process mismatch');
362
- }
363
347
  }
364
348
  function validateTerminalProjection(value, lifecycle) {
365
349
  if (lifecycle === 'blocked') {
366
350
  assertExactObject(value, ['status', 'kind'], 'direct review terminal outcome');
367
- if (value.status !== 'blocked' || !['external', 'safety', 'exhausted'].includes(value.kind)) {
351
+ if (value.status !== 'blocked' || !['external', 'safety', 'decision-delta', 'out-of-scope', 'authority-boundary'].includes(value.kind)) {
368
352
  throw new Error('direct review blocked projection is invalid');
369
353
  }
370
354
  }
@@ -376,97 +360,53 @@ function validateTerminalProjection(value, lifecycle) {
376
360
  }
377
361
  return structuredClone(value);
378
362
  }
379
- function validateStageFields(stage, track, invocation, findings) {
380
- const suffix = stage.split('-').at(-1);
381
- if (suffix === 'full') {
382
- if (track.mode !== 'full' || track.acceptedReportSha256 !== null || track.affectedDefectIds.length !== 0) {
383
- throw new Error('Full review stage fields are invalid');
384
- }
363
+ function validateStageFields(stage, track, findings) {
364
+ if (stage === 'review') {
365
+ if (track.acceptedReportSha256 !== null)
366
+ throw new Error('review stage fields are invalid');
385
367
  }
386
- else if (suffix === 'repair') {
387
- if (invocation || track.acceptedReportSha256 === null || (track.defects.every((defect) => defect.status !== 'open' && defect.status !== 'reopened')
368
+ else {
369
+ if (track.acceptedReportSha256 === null || (track.defects.every((defect) => defect.status !== 'open' && defect.status !== 'reopened')
388
370
  && findings.every((finding) => finding.status !== 'open' && finding.status !== 'reopened'))) {
389
371
  throw new Error('review repair stage fields are invalid');
390
372
  }
391
373
  }
392
- else if (track.mode !== 'closure' || track.acceptedReportSha256 === null || track.affectedDefectIds.length === 0
393
- || !track.affectedDefectIds.every((id) => track.defects.some((defect) => defect.id === id && defect.status === 'fixed')
394
- || findings.some((finding) => finding.id === id && finding.status === 'fixed'))) {
395
- throw new Error('Closure review stage fields are invalid');
396
- }
397
- if (invocation) {
398
- if (invocation.operation !== 'code-review' || invocation.mode !== track.mode || invocation.reviewerSessionId !== track.reviewerSessionId) {
399
- throw new Error('review invocation does not match active stage');
400
- }
401
- }
402
374
  }
403
375
  function validateTrack(value, field, targetRevision) {
404
376
  assertExactObject(value, [
405
- 'version', 'disposition', 'profile', 'reviewerSessionId', 'mode', 'reportRepairs', 'transportRetries',
406
- 'coverage', 'defects', 'affectedDefectIds', 'acceptedReportSha256',
377
+ 'version', 'disposition', 'profile', 'reviewerSessionId', 'reportRepairs', 'transportRetries',
378
+ 'coverage', 'defects', 'acceptedReportSha256',
407
379
  ], `direct review ${field} track`);
408
380
  if (value.version !== 1 || !['active', 'clear'].includes(value.disposition)
409
381
  || !['simple', 'medium', 'high'].includes(value.profile)
410
- || !isReportRepairCount(value.reportRepairs) || !isBit(value.transportRetries))
382
+ || !isNonNegativeInteger(value.reportRepairs) || !isNonNegativeInteger(value.transportRetries))
411
383
  throw new Error(`direct review ${field} track is invalid`);
412
384
  if (value.reviewerSessionId !== null)
413
385
  assertText(value.reviewerSessionId, `${field} reviewer session ID`);
414
- if (value.mode !== null && value.mode !== 'full' && value.mode !== 'closure')
415
- throw new Error(`${field} review mode is invalid`);
416
386
  const coverage = sortedUniqueStrings(value.coverage, `${field} coverage`);
417
- const affectedDefectIds = sortedUniqueStrings(value.affectedDefectIds, `${field} affected defect IDs`);
418
387
  const defects = validateCodeReviewDefects(value.defects, targetRevision);
419
388
  if (value.acceptedReportSha256 !== null)
420
389
  assertSha256(value.acceptedReportSha256, `${field} accepted report hash`);
421
- if (value.disposition === 'active' && (value.reviewerSessionId === null || value.mode === null))
390
+ if (value.disposition === 'active' && value.reviewerSessionId === null)
422
391
  throw new Error(`${field} active track lacks identity`);
423
- if (value.disposition === 'clear' && (value.reviewerSessionId === null || value.mode === null || value.acceptedReportSha256 === null
424
- || affectedDefectIds.length !== 0 || defects.some((defect) => (defect.class === 'blocker' || defect.class === 'execution-risk')
425
- && defect.status !== 'verified' && defect.status !== 'superseded')))
392
+ if (value.disposition === 'clear' && (value.reviewerSessionId === null || value.acceptedReportSha256 === null
393
+ || defects.some((defect) => (defect.class === 'blocker' || defect.class === 'execution-risk')
394
+ && defect.status !== 'verified' && defect.status !== 'superseded')))
426
395
  throw new Error(`${field} clear track is invalid`);
427
396
  return {
428
397
  version: 1,
429
398
  disposition: value.disposition,
430
399
  profile: value.profile,
431
400
  reviewerSessionId: value.reviewerSessionId,
432
- mode: value.mode,
433
401
  reportRepairs: value.reportRepairs,
434
402
  transportRetries: value.transportRetries,
435
403
  coverage,
436
404
  defects,
437
- affectedDefectIds,
438
405
  acceptedReportSha256: value.acceptedReportSha256,
439
406
  };
440
407
  }
441
- function validateInvocation(value) {
442
- assertExactObject(value, [
443
- 'attemptId', 'operation', 'mode', 'reviewerSessionId', 'targetRevision', 'targetFingerprint',
444
- 'closureRequestSha256', 'status', 'pid', 'processGroupId',
445
- ], 'review invocation');
446
- for (const field of ['attemptId', 'reviewerSessionId'])
447
- assertText(value[field], `review invocation.${field}`);
448
- if (value.operation !== 'code-review')
449
- throw new Error('review invocation operation is invalid');
450
- if (value.mode !== 'full' && value.mode !== 'closure')
451
- throw new Error('review invocation mode is invalid');
452
- if (!Number.isSafeInteger(value.targetRevision) || value.targetRevision < 1)
453
- throw new Error('review invocation target revision is invalid');
454
- assertSha256(value.targetFingerprint, 'review invocation target fingerprint');
455
- if (value.closureRequestSha256 !== null)
456
- assertSha256(value.closureRequestSha256, 'review invocation Closure hash');
457
- if (!['prepared', 'launched', 'abandoned'].includes(value.status))
458
- throw new Error('review invocation status is invalid');
459
- const launched = value.status === 'launched';
460
- if (launched !== (Number.isSafeInteger(value.pid) && value.pid > 0
461
- && Number.isSafeInteger(value.processGroupId) && value.processGroupId > 0)) {
462
- throw new Error('review invocation process identity is invalid');
463
- }
464
- if (!launched && (value.pid !== null || value.processGroupId !== null))
465
- throw new Error('non-launched invocation has process identity');
466
- return structuredClone(value);
467
- }
468
408
  function validateRepairFindings(value, targetRevision) {
469
- if (!Array.isArray(value) || value.length > MAX_ITEMS)
409
+ if (!Array.isArray(value))
470
410
  throw new Error('direct repair findings are invalid');
471
411
  const findings = value.map((finding, index) => {
472
412
  assertExactObject(finding, ['id', 'provenance', 'sourceId', 'targetRevision', 'summary', 'affectedContracts', 'status'], `repair finding[${index}]`);
@@ -485,21 +425,20 @@ function validateRepairFindings(value, targetRevision) {
485
425
  assertUnique(findings.map((finding) => finding.id), 'repair finding IDs');
486
426
  return findings;
487
427
  }
488
- function activeTrack(reviewerSessionId, mode) {
428
+ function activeTrack(reviewerSessionId) {
489
429
  return {
490
- version: 1, disposition: 'active', profile: 'high', reviewerSessionId, mode,
491
- reportRepairs: 0, transportRetries: 0, coverage: [], defects: [], affectedDefectIds: [], acceptedReportSha256: null,
430
+ version: 1, disposition: 'active', profile: 'high', reviewerSessionId,
431
+ reportRepairs: 0, transportRetries: 0, coverage: [], defects: [], acceptedReportSha256: null,
492
432
  };
493
433
  }
494
434
  function isStage(value) {
495
- return ['review-full', 'review-repair', 'review-closure'].includes(value);
435
+ return value === 'review' || value === 'review-repair';
496
436
  }
497
- function isBit(value) { return value === 0 || value === 1; }
498
- function isReportRepairCount(value) {
499
- return Number.isSafeInteger(value) && value >= 0 && value <= MAX_DIRECT_REVIEW_REPORT_REPAIRS;
437
+ function isNonNegativeInteger(value) {
438
+ return Number.isSafeInteger(value) && value >= 0;
500
439
  }
501
440
  function sortedUniqueStrings(value, field) {
502
- if (!Array.isArray(value) || value.length > MAX_ITEMS)
441
+ if (!Array.isArray(value))
503
442
  throw new Error(`${field} is invalid`);
504
443
  for (const item of value)
505
444
  assertText(item, `${field} entry`);
@@ -515,6 +454,10 @@ function assertSha256(value, field) {
515
454
  if (typeof value !== 'string' || !SHA256.test(value))
516
455
  throw new Error(`${field} is invalid`);
517
456
  }
457
+ function assertGitSha(value, field) {
458
+ if (typeof value !== 'string' || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(value))
459
+ throw new Error(`${field} is invalid`);
460
+ }
518
461
  function assertText(value, field) {
519
462
  if (typeof value !== 'string' || value.length === 0 || value.length > 16 * 1024)
520
463
  throw new Error(`${field} is invalid`);