oh-my-super-agent 0.1.0

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 (461) hide show
  1. package/.agents/agents/advisor.md +15 -0
  2. package/.agents/agents/conductor.md +32 -0
  3. package/.agents/agents/dreamer.md +13 -0
  4. package/.agents/agents/executor.md +17 -0
  5. package/.agents/agents/grader.md +15 -0
  6. package/.agents/config/endurance-policy.json +8 -0
  7. package/.agents/config/oms-config.json +119 -0
  8. package/.agents/config/release-policy.json +12 -0
  9. package/.agents/config/secret-scan-policy.json +7 -0
  10. package/.agents/config/verification-plan.json +33 -0
  11. package/.agents/registry.json +11 -0
  12. package/.agents/rules/constitution.md +7 -0
  13. package/.agents/skills/conductor/SKILL.md +23 -0
  14. package/.agents/skills/dream/SKILL.md +14 -0
  15. package/.agents/skills/executor/SKILL.md +13 -0
  16. package/.agents/skills/oms/SKILL.md +52 -0
  17. package/.agents/skills/quota-aware/SKILL.md +12 -0
  18. package/.agents/skills/schedule/SKILL.md +13 -0
  19. package/.agents/skills/team-protocol/SKILL.md +20 -0
  20. package/LICENSE +21 -0
  21. package/README.md +146 -0
  22. package/dist/src/audit/run-ledger.d.ts +183 -0
  23. package/dist/src/audit/run-ledger.js +1655 -0
  24. package/dist/src/audit/run-ledger.js.map +1 -0
  25. package/dist/src/cli.d.ts +61 -0
  26. package/dist/src/cli.js +2096 -0
  27. package/dist/src/cli.js.map +1 -0
  28. package/dist/src/commands/github-queue-probe.d.ts +26 -0
  29. package/dist/src/commands/github-queue-probe.js +51 -0
  30. package/dist/src/commands/github-queue-probe.js.map +1 -0
  31. package/dist/src/commands/trace-fixture.d.ts +20 -0
  32. package/dist/src/commands/trace-fixture.js +72 -0
  33. package/dist/src/commands/trace-fixture.js.map +1 -0
  34. package/dist/src/conductor/activity.d.ts +46 -0
  35. package/dist/src/conductor/activity.js +224 -0
  36. package/dist/src/conductor/activity.js.map +1 -0
  37. package/dist/src/conductor/bridge.d.ts +53 -0
  38. package/dist/src/conductor/bridge.js +652 -0
  39. package/dist/src/conductor/bridge.js.map +1 -0
  40. package/dist/src/conductor/notifications.d.ts +59 -0
  41. package/dist/src/conductor/notifications.js +381 -0
  42. package/dist/src/conductor/notifications.js.map +1 -0
  43. package/dist/src/conductor/recovery.d.ts +29 -0
  44. package/dist/src/conductor/recovery.js +198 -0
  45. package/dist/src/conductor/recovery.js.map +1 -0
  46. package/dist/src/conductor/start.d.ts +73 -0
  47. package/dist/src/conductor/start.js +519 -0
  48. package/dist/src/conductor/start.js.map +1 -0
  49. package/dist/src/conductor/waiter.d.ts +88 -0
  50. package/dist/src/conductor/waiter.js +427 -0
  51. package/dist/src/conductor/waiter.js.map +1 -0
  52. package/dist/src/config.d.ts +9 -0
  53. package/dist/src/config.js +40 -0
  54. package/dist/src/config.js.map +1 -0
  55. package/dist/src/dispatch/advice-source.d.ts +61 -0
  56. package/dist/src/dispatch/advice-source.js +198 -0
  57. package/dist/src/dispatch/advice-source.js.map +1 -0
  58. package/dist/src/dispatch/advice-store.d.ts +41 -0
  59. package/dist/src/dispatch/advice-store.js +201 -0
  60. package/dist/src/dispatch/advice-store.js.map +1 -0
  61. package/dist/src/dispatch/advice.d.ts +34 -0
  62. package/dist/src/dispatch/advice.js +371 -0
  63. package/dist/src/dispatch/advice.js.map +1 -0
  64. package/dist/src/dispatch/authority.d.ts +124 -0
  65. package/dist/src/dispatch/authority.js +1202 -0
  66. package/dist/src/dispatch/authority.js.map +1 -0
  67. package/dist/src/dispatch/intent.d.ts +66 -0
  68. package/dist/src/dispatch/intent.js +71 -0
  69. package/dist/src/dispatch/intent.js.map +1 -0
  70. package/dist/src/dispatch/runtime-authority.d.ts +42 -0
  71. package/dist/src/dispatch/runtime-authority.js +52 -0
  72. package/dist/src/dispatch/runtime-authority.js.map +1 -0
  73. package/dist/src/doctor.d.ts +20 -0
  74. package/dist/src/doctor.js +85 -0
  75. package/dist/src/doctor.js.map +1 -0
  76. package/dist/src/domain/records.d.ts +44 -0
  77. package/dist/src/domain/records.js +138 -0
  78. package/dist/src/domain/records.js.map +1 -0
  79. package/dist/src/domain/role-policy.d.ts +67 -0
  80. package/dist/src/domain/role-policy.js +547 -0
  81. package/dist/src/domain/role-policy.js.map +1 -0
  82. package/dist/src/events/bus.d.ts +32 -0
  83. package/dist/src/events/bus.js +115 -0
  84. package/dist/src/events/bus.js.map +1 -0
  85. package/dist/src/events/kinds.d.ts +126 -0
  86. package/dist/src/events/kinds.js +14 -0
  87. package/dist/src/events/kinds.js.map +1 -0
  88. package/dist/src/evidence/immutable-file.d.ts +12 -0
  89. package/dist/src/evidence/immutable-file.js +141 -0
  90. package/dist/src/evidence/immutable-file.js.map +1 -0
  91. package/dist/src/evidence/store.d.ts +125 -0
  92. package/dist/src/evidence/store.js +1189 -0
  93. package/dist/src/evidence/store.js.map +1 -0
  94. package/dist/src/evidence/verdict.d.ts +17 -0
  95. package/dist/src/evidence/verdict.js +23 -0
  96. package/dist/src/evidence/verdict.js.map +1 -0
  97. package/dist/src/generation/agent.d.ts +5 -0
  98. package/dist/src/generation/agent.js +34 -0
  99. package/dist/src/generation/agent.js.map +1 -0
  100. package/dist/src/generation/frontmatter.d.ts +7 -0
  101. package/dist/src/generation/frontmatter.js +33 -0
  102. package/dist/src/generation/frontmatter.js.map +1 -0
  103. package/dist/src/github/adapter.d.ts +44 -0
  104. package/dist/src/github/adapter.js +592 -0
  105. package/dist/src/github/adapter.js.map +1 -0
  106. package/dist/src/github/dispatch-eligibility.d.ts +9 -0
  107. package/dist/src/github/dispatch-eligibility.js +94 -0
  108. package/dist/src/github/dispatch-eligibility.js.map +1 -0
  109. package/dist/src/github/projector.d.ts +23 -0
  110. package/dist/src/github/projector.js +181 -0
  111. package/dist/src/github/projector.js.map +1 -0
  112. package/dist/src/github/review.d.ts +39 -0
  113. package/dist/src/github/review.js +1450 -0
  114. package/dist/src/github/review.js.map +1 -0
  115. package/dist/src/github/sync.d.ts +21 -0
  116. package/dist/src/github/sync.js +518 -0
  117. package/dist/src/github/sync.js.map +1 -0
  118. package/dist/src/github/types.d.ts +134 -0
  119. package/dist/src/github/types.js +11 -0
  120. package/dist/src/github/types.js.map +1 -0
  121. package/dist/src/goals/artifact-store.d.ts +31 -0
  122. package/dist/src/goals/artifact-store.js +241 -0
  123. package/dist/src/goals/artifact-store.js.map +1 -0
  124. package/dist/src/goals/authority.d.ts +112 -0
  125. package/dist/src/goals/authority.js +621 -0
  126. package/dist/src/goals/authority.js.map +1 -0
  127. package/dist/src/goals/native-snapshot.d.ts +12 -0
  128. package/dist/src/goals/native-snapshot.js +53 -0
  129. package/dist/src/goals/native-snapshot.js.map +1 -0
  130. package/dist/src/goals/runtime-authority.d.ts +7 -0
  131. package/dist/src/goals/runtime-authority.js +20 -0
  132. package/dist/src/goals/runtime-authority.js.map +1 -0
  133. package/dist/src/health/release-assessment.d.ts +11 -0
  134. package/dist/src/health/release-assessment.js +83 -0
  135. package/dist/src/health/release-assessment.js.map +1 -0
  136. package/dist/src/health/release-policy.d.ts +16 -0
  137. package/dist/src/health/release-policy.js +40 -0
  138. package/dist/src/health/release-policy.js.map +1 -0
  139. package/dist/src/hooks/native.d.ts +30 -0
  140. package/dist/src/hooks/native.js +495 -0
  141. package/dist/src/hooks/native.js.map +1 -0
  142. package/dist/src/hooks/provider-runtime.d.ts +55 -0
  143. package/dist/src/hooks/provider-runtime.js +479 -0
  144. package/dist/src/hooks/provider-runtime.js.map +1 -0
  145. package/dist/src/hooks/provider.d.ts +102 -0
  146. package/dist/src/hooks/provider.js +358 -0
  147. package/dist/src/hooks/provider.js.map +1 -0
  148. package/dist/src/live/contact-journal.d.ts +73 -0
  149. package/dist/src/live/contact-journal.js +439 -0
  150. package/dist/src/live/contact-journal.js.map +1 -0
  151. package/dist/src/live-probes/gh-adapter.d.ts +28 -0
  152. package/dist/src/live-probes/gh-adapter.js +188 -0
  153. package/dist/src/live-probes/gh-adapter.js.map +1 -0
  154. package/dist/src/live-probes/github-queue.d.ts +1 -0
  155. package/dist/src/live-probes/github-queue.js +2 -0
  156. package/dist/src/live-probes/github-queue.js.map +1 -0
  157. package/dist/src/notifications/slack-request.d.ts +13 -0
  158. package/dist/src/notifications/slack-request.js +40 -0
  159. package/dist/src/notifications/slack-request.js.map +1 -0
  160. package/dist/src/outbox/processor.d.ts +27 -0
  161. package/dist/src/outbox/processor.js +63 -0
  162. package/dist/src/outbox/processor.js.map +1 -0
  163. package/dist/src/outbox/store.d.ts +158 -0
  164. package/dist/src/outbox/store.js +834 -0
  165. package/dist/src/outbox/store.js.map +1 -0
  166. package/dist/src/package-root.d.ts +1 -0
  167. package/dist/src/package-root.js +26 -0
  168. package/dist/src/package-root.js.map +1 -0
  169. package/dist/src/project.d.ts +12 -0
  170. package/dist/src/project.js +60 -0
  171. package/dist/src/project.js.map +1 -0
  172. package/dist/src/projection/generated-file.d.ts +15 -0
  173. package/dist/src/projection/generated-file.js +72 -0
  174. package/dist/src/projection/generated-file.js.map +1 -0
  175. package/dist/src/projection/project-workspace.d.ts +11 -0
  176. package/dist/src/projection/project-workspace.js +94 -0
  177. package/dist/src/projection/project-workspace.js.map +1 -0
  178. package/dist/src/projection/registry.d.ts +15 -0
  179. package/dist/src/projection/registry.js +59 -0
  180. package/dist/src/projection/registry.js.map +1 -0
  181. package/dist/src/projection/safe-path.d.ts +4 -0
  182. package/dist/src/projection/safe-path.js +106 -0
  183. package/dist/src/projection/safe-path.js.map +1 -0
  184. package/dist/src/providers/claude-interactive.d.ts +38 -0
  185. package/dist/src/providers/claude-interactive.js +287 -0
  186. package/dist/src/providers/claude-interactive.js.map +1 -0
  187. package/dist/src/providers/claude-structured-bridge.d.ts +32 -0
  188. package/dist/src/providers/claude-structured-bridge.js +215 -0
  189. package/dist/src/providers/claude-structured-bridge.js.map +1 -0
  190. package/dist/src/providers/claude-structured-completion.d.ts +23 -0
  191. package/dist/src/providers/claude-structured-completion.js +229 -0
  192. package/dist/src/providers/claude-structured-completion.js.map +1 -0
  193. package/dist/src/providers/claude-structured-pane.d.ts +23 -0
  194. package/dist/src/providers/claude-structured-pane.js +156 -0
  195. package/dist/src/providers/claude-structured-pane.js.map +1 -0
  196. package/dist/src/providers/claude-structured-store.d.ts +48 -0
  197. package/dist/src/providers/claude-structured-store.js +212 -0
  198. package/dist/src/providers/claude-structured-store.js.map +1 -0
  199. package/dist/src/providers/claude-structured.d.ts +56 -0
  200. package/dist/src/providers/claude-structured.js +250 -0
  201. package/dist/src/providers/claude-structured.js.map +1 -0
  202. package/dist/src/providers/codex-exec.d.ts +29 -0
  203. package/dist/src/providers/codex-exec.js +127 -0
  204. package/dist/src/providers/codex-exec.js.map +1 -0
  205. package/dist/src/providers/grading.d.ts +2 -0
  206. package/dist/src/providers/grading.js +20 -0
  207. package/dist/src/providers/grading.js.map +1 -0
  208. package/dist/src/providers/native-dispatch.d.ts +24 -0
  209. package/dist/src/providers/native-dispatch.js +34 -0
  210. package/dist/src/providers/native-dispatch.js.map +1 -0
  211. package/dist/src/providers/preflight.d.ts +65 -0
  212. package/dist/src/providers/preflight.js +256 -0
  213. package/dist/src/providers/preflight.js.map +1 -0
  214. package/dist/src/providers/process.d.ts +18 -0
  215. package/dist/src/providers/process.js +21 -0
  216. package/dist/src/providers/process.js.map +1 -0
  217. package/dist/src/queue/model.d.ts +37 -0
  218. package/dist/src/queue/model.js +51 -0
  219. package/dist/src/queue/model.js.map +1 -0
  220. package/dist/src/queue/service.d.ts +1 -0
  221. package/dist/src/queue/service.js +2 -0
  222. package/dist/src/queue/service.js.map +1 -0
  223. package/dist/src/reflections/store.d.ts +21 -0
  224. package/dist/src/reflections/store.js +60 -0
  225. package/dist/src/reflections/store.js.map +1 -0
  226. package/dist/src/reports/daily-trust.d.ts +11 -0
  227. package/dist/src/reports/daily-trust.js +98 -0
  228. package/dist/src/reports/daily-trust.js.map +1 -0
  229. package/dist/src/runs/controller.d.ts +187 -0
  230. package/dist/src/runs/controller.js +1182 -0
  231. package/dist/src/runs/controller.js.map +1 -0
  232. package/dist/src/runtime/composition.d.ts +9 -0
  233. package/dist/src/runtime/composition.js +35 -0
  234. package/dist/src/runtime/composition.js.map +1 -0
  235. package/dist/src/runtime/endurance-authorities.d.ts +44 -0
  236. package/dist/src/runtime/endurance-authorities.js +101 -0
  237. package/dist/src/runtime/endurance-authorities.js.map +1 -0
  238. package/dist/src/runtime/endurance-policy.d.ts +12 -0
  239. package/dist/src/runtime/endurance-policy.js +13 -0
  240. package/dist/src/runtime/endurance-policy.js.map +1 -0
  241. package/dist/src/runtime/lock.d.ts +63 -0
  242. package/dist/src/runtime/lock.js +577 -0
  243. package/dist/src/runtime/lock.js.map +1 -0
  244. package/dist/src/runtime/process-identity.d.ts +10 -0
  245. package/dist/src/runtime/process-identity.js +43 -0
  246. package/dist/src/runtime/process-identity.js.map +1 -0
  247. package/dist/src/runtime/quota.d.ts +130 -0
  248. package/dist/src/runtime/quota.js +590 -0
  249. package/dist/src/runtime/quota.js.map +1 -0
  250. package/dist/src/runtime/record-store.d.ts +45 -0
  251. package/dist/src/runtime/record-store.js +272 -0
  252. package/dist/src/runtime/record-store.js.map +1 -0
  253. package/dist/src/runtime/schema.d.ts +28 -0
  254. package/dist/src/runtime/schema.js +194 -0
  255. package/dist/src/runtime/schema.js.map +1 -0
  256. package/dist/src/runtime/stable-id.d.ts +3 -0
  257. package/dist/src/runtime/stable-id.js +27 -0
  258. package/dist/src/runtime/stable-id.js.map +1 -0
  259. package/dist/src/runtime/system-clock.d.ts +8 -0
  260. package/dist/src/runtime/system-clock.js +35 -0
  261. package/dist/src/runtime/system-clock.js.map +1 -0
  262. package/dist/src/schedule/service-plan.d.ts +58 -0
  263. package/dist/src/schedule/service-plan.js +327 -0
  264. package/dist/src/schedule/service-plan.js.map +1 -0
  265. package/dist/src/schedule/user-installation.d.ts +62 -0
  266. package/dist/src/schedule/user-installation.js +701 -0
  267. package/dist/src/schedule/user-installation.js.map +1 -0
  268. package/dist/src/schedule/user-plan.d.ts +47 -0
  269. package/dist/src/schedule/user-plan.js +286 -0
  270. package/dist/src/schedule/user-plan.js.map +1 -0
  271. package/dist/src/scheduler/minute.d.ts +31 -0
  272. package/dist/src/scheduler/minute.js +441 -0
  273. package/dist/src/scheduler/minute.js.map +1 -0
  274. package/dist/src/schemas/registry.d.ts +10 -0
  275. package/dist/src/schemas/registry.js +36 -0
  276. package/dist/src/schemas/registry.js.map +1 -0
  277. package/dist/src/security/secret-scan.d.ts +50 -0
  278. package/dist/src/security/secret-scan.js +384 -0
  279. package/dist/src/security/secret-scan.js.map +1 -0
  280. package/dist/src/sessions/registry.d.ts +89 -0
  281. package/dist/src/sessions/registry.js +443 -0
  282. package/dist/src/sessions/registry.js.map +1 -0
  283. package/dist/src/state.d.ts +27 -0
  284. package/dist/src/state.js +235 -0
  285. package/dist/src/state.js.map +1 -0
  286. package/dist/src/status/hud.d.ts +37 -0
  287. package/dist/src/status/hud.js +88 -0
  288. package/dist/src/status/hud.js.map +1 -0
  289. package/dist/src/team/plan.d.ts +31 -0
  290. package/dist/src/team/plan.js +184 -0
  291. package/dist/src/team/plan.js.map +1 -0
  292. package/dist/src/team/projection.d.ts +24 -0
  293. package/dist/src/team/projection.js +79 -0
  294. package/dist/src/team/projection.js.map +1 -0
  295. package/dist/src/tickets/admission.d.ts +34 -0
  296. package/dist/src/tickets/admission.js +169 -0
  297. package/dist/src/tickets/admission.js.map +1 -0
  298. package/dist/src/tickets/materializer.d.ts +1 -0
  299. package/dist/src/tickets/materializer.js +2 -0
  300. package/dist/src/tickets/materializer.js.map +1 -0
  301. package/dist/src/tickets/runtime-authority.d.ts +32 -0
  302. package/dist/src/tickets/runtime-authority.js +46 -0
  303. package/dist/src/tickets/runtime-authority.js.map +1 -0
  304. package/dist/src/tickets/store.d.ts +221 -0
  305. package/dist/src/tickets/store.js +2025 -0
  306. package/dist/src/tickets/store.js.map +1 -0
  307. package/dist/src/tracer/one-ticket.d.ts +58 -0
  308. package/dist/src/tracer/one-ticket.js +578 -0
  309. package/dist/src/tracer/one-ticket.js.map +1 -0
  310. package/dist/src/trust/assessment.d.ts +24 -0
  311. package/dist/src/trust/assessment.js +124 -0
  312. package/dist/src/trust/assessment.js.map +1 -0
  313. package/dist/src/trust/runs.d.ts +13 -0
  314. package/dist/src/trust/runs.js +46 -0
  315. package/dist/src/trust/runs.js.map +1 -0
  316. package/dist/src/types.d.ts +6 -0
  317. package/dist/src/types.js +2 -0
  318. package/dist/src/types.js.map +1 -0
  319. package/dist/src/utils/fs.d.ts +13 -0
  320. package/dist/src/utils/fs.js +188 -0
  321. package/dist/src/utils/fs.js.map +1 -0
  322. package/dist/src/verification/authority.d.ts +28 -0
  323. package/dist/src/verification/authority.js +153 -0
  324. package/dist/src/verification/authority.js.map +1 -0
  325. package/dist/src/verification/grade-source.d.ts +17 -0
  326. package/dist/src/verification/grade-source.js +30 -0
  327. package/dist/src/verification/grade-source.js.map +1 -0
  328. package/dist/src/verification/plan.d.ts +20 -0
  329. package/dist/src/verification/plan.js +128 -0
  330. package/dist/src/verification/plan.js.map +1 -0
  331. package/dist/src/verification/protected-runner.d.ts +50 -0
  332. package/dist/src/verification/protected-runner.js +171 -0
  333. package/dist/src/verification/protected-runner.js.map +1 -0
  334. package/dist/src/verification/result-gate-store.d.ts +72 -0
  335. package/dist/src/verification/result-gate-store.js +193 -0
  336. package/dist/src/verification/result-gate-store.js.map +1 -0
  337. package/dist/src/verification/result-gate.d.ts +41 -0
  338. package/dist/src/verification/result-gate.js +731 -0
  339. package/dist/src/verification/result-gate.js.map +1 -0
  340. package/dist/src/verification/runner.d.ts +38 -0
  341. package/dist/src/verification/runner.js +143 -0
  342. package/dist/src/verification/runner.js.map +1 -0
  343. package/dist/src/verification/sandbox.d.ts +16 -0
  344. package/dist/src/verification/sandbox.js +199 -0
  345. package/dist/src/verification/sandbox.js.map +1 -0
  346. package/dist/src/verify.d.ts +8 -0
  347. package/dist/src/verify.js +35 -0
  348. package/dist/src/verify.js.map +1 -0
  349. package/dist/src/watchdog/reconcile.d.ts +42 -0
  350. package/dist/src/watchdog/reconcile.js +440 -0
  351. package/dist/src/watchdog/reconcile.js.map +1 -0
  352. package/dist/src/workers/bridge.d.ts +35 -0
  353. package/dist/src/workers/bridge.js +426 -0
  354. package/dist/src/workers/bridge.js.map +1 -0
  355. package/dist/src/workers/delivery.d.ts +31 -0
  356. package/dist/src/workers/delivery.js +876 -0
  357. package/dist/src/workers/delivery.js.map +1 -0
  358. package/dist/src/workers/panes.d.ts +32 -0
  359. package/dist/src/workers/panes.js +212 -0
  360. package/dist/src/workers/panes.js.map +1 -0
  361. package/dist/src/workers/registry.d.ts +80 -0
  362. package/dist/src/workers/registry.js +351 -0
  363. package/dist/src/workers/registry.js.map +1 -0
  364. package/dist/src/workers/runtime.d.ts +44 -0
  365. package/dist/src/workers/runtime.js +21 -0
  366. package/dist/src/workers/runtime.js.map +1 -0
  367. package/dist/src/worktrees/manager.d.ts +35 -0
  368. package/dist/src/worktrees/manager.js +315 -0
  369. package/dist/src/worktrees/manager.js.map +1 -0
  370. package/docs/ARCHITECTURE.md +323 -0
  371. package/docs/PLAN.md +216 -0
  372. package/docs/REFERENCES.md +42 -0
  373. package/docs/ROADMAP.md +327 -0
  374. package/docs/SECURITY.md +242 -0
  375. package/docs/adr/0001-tracer-first-file-backed-certification.md +41 -0
  376. package/docs/adr/0002-conductor-first-typed-dispatch.md +102 -0
  377. package/docs/adr/0003-mandatory-pre-dispatch-advice.md +59 -0
  378. package/docs/compatibility/claude-code-2.1.207.md +25 -0
  379. package/loop/contract.md +102 -0
  380. package/package.json +54 -0
  381. package/schemas/advisor-evidence.schema.json +54 -0
  382. package/schemas/advisor-output.schema.json +31 -0
  383. package/schemas/conductor-activity-payload.schema.json +38 -0
  384. package/schemas/conductor-decision.schema.json +29 -0
  385. package/schemas/conductor-dispatch-intent.schema.json +71 -0
  386. package/schemas/conductor-dispatch-preparation.schema.json +46 -0
  387. package/schemas/conductor-dispatch-proposal.schema.json +82 -0
  388. package/schemas/conductor-goal-proposal.schema.json +30 -0
  389. package/schemas/conductor-manage-worker.schema.json +17 -0
  390. package/schemas/conductor-notification-payload.schema.json +39 -0
  391. package/schemas/conductor-process-result.schema.json +14 -0
  392. package/schemas/conductor-route.schema.json +51 -0
  393. package/schemas/conductor-start-team.schema.json +16 -0
  394. package/schemas/conductor-waiter-payload.schema.json +68 -0
  395. package/schemas/deterministic-check.schema.json +107 -0
  396. package/schemas/dispatch-acknowledgement.schema.json +15 -0
  397. package/schemas/dispatch-advice-payload.schema.json +64 -0
  398. package/schemas/dispatch-payload.schema.json +131 -0
  399. package/schemas/dispatch-result.schema.json +28 -0
  400. package/schemas/endurance-policy.schema.json +16 -0
  401. package/schemas/events/dispatch-delivery-requested.schema.json +17 -0
  402. package/schemas/events/github-observed.schema.json +28 -0
  403. package/schemas/events/native-hook-observed.schema.json +35 -0
  404. package/schemas/events/provider-observed.schema.json +64 -0
  405. package/schemas/events/run-heartbeat-due.schema.json +10 -0
  406. package/schemas/events/slack-notification-requested.schema.json +14 -0
  407. package/schemas/events/structured-role-completed.schema.json +54 -0
  408. package/schemas/events/ticket-resume-due.schema.json +28 -0
  409. package/schemas/events/watchdog-lease-expired.schema.json +14 -0
  410. package/schemas/events/watchdog-outbox-stalled.schema.json +12 -0
  411. package/schemas/events/watchdog-session-stalled.schema.json +11 -0
  412. package/schemas/evidence-record.schema.json +37 -0
  413. package/schemas/executor-result.schema.json +17 -0
  414. package/schemas/github-observation.schema.json +33 -0
  415. package/schemas/github-watermark.schema.json +66 -0
  416. package/schemas/goal-artifact.schema.json +19 -0
  417. package/schemas/goal-create-input.schema.json +14 -0
  418. package/schemas/goal-executor-result.schema.json +18 -0
  419. package/schemas/goal-payload.schema.json +95 -0
  420. package/schemas/grader-output.schema.json +33 -0
  421. package/schemas/lease-expiry.schema.json +19 -0
  422. package/schemas/live-disclosure.schema.json +16 -0
  423. package/schemas/live-outcome.schema.json +52 -0
  424. package/schemas/native-goal-snapshot.schema.json +27 -0
  425. package/schemas/provider-observed.schema.json +64 -0
  426. package/schemas/quota-observation.schema.json +72 -0
  427. package/schemas/reflection-evidence.schema.json +61 -0
  428. package/schemas/reflection.schema.json +13 -0
  429. package/schemas/regression-request.schema.json +54 -0
  430. package/schemas/release-assessment.schema.json +34 -0
  431. package/schemas/release-policy.schema.json +30 -0
  432. package/schemas/result-gate-payload.schema.json +244 -0
  433. package/schemas/review-delivery-payload.schema.json +42 -0
  434. package/schemas/role-policy.schema.json +111 -0
  435. package/schemas/run-abort-input.schema.json +11 -0
  436. package/schemas/run-completion-proof.schema.json +62 -0
  437. package/schemas/run-ledger-entry.schema.json +78 -0
  438. package/schemas/run-manifest.schema.json +74 -0
  439. package/schemas/run-start-intent.schema.json +74 -0
  440. package/schemas/runtime-record.schema.json +111 -0
  441. package/schemas/service-plan.schema.json +35 -0
  442. package/schemas/session-payload.schema.json +80 -0
  443. package/schemas/structured-role-read-request.schema.json +14 -0
  444. package/schemas/structured-role-request.schema.json +44 -0
  445. package/schemas/structured-role-result.schema.json +38 -0
  446. package/schemas/structured-role-submit-result.schema.json +15 -0
  447. package/schemas/team-checkpoint.schema.json +42 -0
  448. package/schemas/team-plan.schema.json +30 -0
  449. package/schemas/ticket-content.schema.json +38 -0
  450. package/schemas/ticket-queue-payload.schema.json +191 -0
  451. package/schemas/trust-report.schema.json +68 -0
  452. package/schemas/trust-run.schema.json +47 -0
  453. package/schemas/ultragoal-checkpoint.schema.json +49 -0
  454. package/schemas/user-schedule-installation-v1.schema.json +32 -0
  455. package/schemas/user-schedule-installation.schema.json +38 -0
  456. package/schemas/user-schedule-plan.schema.json +39 -0
  457. package/schemas/verification-plan-evidence.schema.json +37 -0
  458. package/schemas/worker-acknowledge-dispatch.schema.json +12 -0
  459. package/schemas/worker-payload.schema.json +47 -0
  460. package/schemas/worker-read-dispatch.schema.json +11 -0
  461. package/schemas/worker-record-dispatch-result.schema.json +14 -0
@@ -0,0 +1,2025 @@
1
+ import { createHash } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import { assertActorId, assertLeaseToken, assertLockId, assertOpaqueId, assertRecordId, assertTransitionNonce, recordLockId, } from "../domain/records.js";
4
+ import { assertEvidenceStore, } from "../evidence/store.js";
5
+ import { assertFileInside, openStableDirectory, readImmutableFile, writeImmutableFile, } from "../evidence/immutable-file.js";
6
+ import { assertTrackedRolePolicyGate, ROLE_IDS, } from "../domain/role-policy.js";
7
+ import { findPackageRoot } from "../package-root.js";
8
+ import { freezeTrackedVerificationPlan } from "../verification/plan.js";
9
+ import { MAX_QUEUE_ATTEMPTS, assertTimestamp, canonicalTimestamp, initialQueueLifecycle, isActiveTicketState, isIdleTicketState, isTerminalTicketState, isTicketState, } from "../queue/model.js";
10
+ import { assertExhaustedQuotaGate, assertFreshQuotaGate, } from "../runtime/quota.js";
11
+ import { acquireDirectoryLock, LockBusyError } from "../runtime/lock.js";
12
+ import { createRecordStore } from "../runtime/record-store.js";
13
+ import { assertSchema, loadJsonSchema } from "../runtime/schema.js";
14
+ import { createStableId, createTransitionNonce } from "../runtime/stable-id.js";
15
+ import { isRecord, validateExistingDirectory } from "../utils/fs.js";
16
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/;
17
+ const COMMIT_PATTERN = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/;
18
+ const TIMESTAMP_PATTERN = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.([0-9]{1,3}))?Z$/;
19
+ const MAX_TICKET_BYTES = 2 * 1024 * 1024;
20
+ const MAX_SYNC_OUTCOMES = 64;
21
+ const SYNC_LOCK_ATTEMPTS = 20;
22
+ const SYNC_LOCK_RETRY_MS = 10;
23
+ const TICKET_QUEUE_STORE = Symbol("OMS ticket queue store");
24
+ const TICKET_CONTENT_STORES = new WeakSet();
25
+ const QUEUE_INTERNALS = new WeakMap();
26
+ export function assertTicketQueueStore(value) {
27
+ if (typeof value !== "object" || value === null || !QUEUE_INTERNALS.has(value)) {
28
+ throw new Error("ticket operation requires the canonical OMS ticket queue store");
29
+ }
30
+ if (!Object.isFrozen(value))
31
+ throw new Error("ticket queue store lost its frozen authority boundary");
32
+ }
33
+ const MATERIALIZER_INTERNALS = new WeakMap();
34
+ export function deriveGitHubTicketIdentity(input) {
35
+ const issueId = assertGitHubNodeId(input.issue_node_id);
36
+ const sourceRefSha256 = sha256(Buffer.from(`github\0${issueId}`, "utf8"));
37
+ return {
38
+ ticket_id: assertTicketRecordId(`gh-${sourceRefSha256.slice(0, 48)}`),
39
+ source_ref_sha256: sourceRefSha256,
40
+ };
41
+ }
42
+ export function createTicketQueueStore(options) {
43
+ const packageRoot = options.packageRoot ?? findPackageRoot();
44
+ const policyRoot = options.policyRoot ?? packageRoot;
45
+ const recordsRoot = options.recordsRoot;
46
+ const evidenceStore = options.evidenceStore;
47
+ const policyGate = options.policyGate;
48
+ const queueSchema = loadJsonSchema(join(packageRoot, "schemas", "ticket-queue-payload.schema.json"));
49
+ const locksRoot = validateExistingDirectory(options.locksRoot, "ticket queue locks root");
50
+ const systemActors = new Set([...options.systemActors].map((actor) => assertActorId(actor, "system actor")));
51
+ if (systemActors.size === 0)
52
+ throw new Error("ticket queue requires at least one system actor");
53
+ assertTrackedRolePolicyGate(policyGate, policyRoot);
54
+ const policyIdentity = Object.freeze({
55
+ policy_version: policyGate.version,
56
+ policy_digest: policyGate.digest,
57
+ });
58
+ if (evidenceStore !== undefined)
59
+ assertEvidenceStore(evidenceStore);
60
+ let syncMutationDepth = 0;
61
+ let lifecycleMutationDepth = 0;
62
+ let authorityRepairMutationDepth = 0;
63
+ const store = createRecordStore({
64
+ recordsRoot,
65
+ locksRoot,
66
+ recordValidator: (record) => validateTicketRuntimeRecord(record, queueSchema, evidenceStore),
67
+ createValidator: (record) => validateInitialTicketRecord(record, systemActors, policyIdentity),
68
+ transitionValidator: (current, next) => validateTicketTransition(current, next, queueSchema, syncMutationDepth > 0, lifecycleMutationDepth > 0, authorityRepairMutationDepth > 0, systemActors, evidenceStore),
69
+ });
70
+ const publicStore = Object.freeze({
71
+ [TICKET_QUEUE_STORE]: true,
72
+ read(recordId) {
73
+ return store.read(recordId);
74
+ },
75
+ readIfExists(recordId) {
76
+ return store.readIfExists(recordId);
77
+ },
78
+ list() {
79
+ return store.list();
80
+ },
81
+ });
82
+ QUEUE_INTERNALS.set(publicStore, {
83
+ store,
84
+ systemActors,
85
+ policyIdentity,
86
+ evidenceStore,
87
+ assertCurrentPolicy() {
88
+ assertTrackedRolePolicyGate(policyGate, policyRoot);
89
+ },
90
+ currentVerificationGateDigest() {
91
+ return freezeTrackedVerificationPlan(policyRoot).digest;
92
+ },
93
+ synchronized(ticketId, ownerId, operation) {
94
+ assertTrackedRolePolicyGate(policyGate, policyRoot);
95
+ const validatedTicketId = assertTicketRecordId(ticketId);
96
+ const actor = assertActorId(ownerId);
97
+ const controllerLock = acquireSyncLockWithRetry({
98
+ locksRoot,
99
+ lockId: assertLockId("run-controller"),
100
+ ownerId: actor,
101
+ note: `queue-watermark:${validatedTicketId}`,
102
+ });
103
+ try {
104
+ const lock = acquireSyncLockWithRetry({
105
+ locksRoot,
106
+ lockId: assertLockId(`sync-${validatedTicketId}`),
107
+ ownerId: actor,
108
+ note: `ticket-sync:${validatedTicketId}`,
109
+ });
110
+ try {
111
+ assertTrackedRolePolicyGate(policyGate, policyRoot);
112
+ return operation();
113
+ }
114
+ finally {
115
+ lock.release();
116
+ }
117
+ }
118
+ finally {
119
+ controllerLock.release();
120
+ }
121
+ },
122
+ transitionSync(transition) {
123
+ assertTrackedRolePolicyGate(policyGate, policyRoot);
124
+ syncMutationDepth += 1;
125
+ try {
126
+ return store.transition(transition);
127
+ }
128
+ finally {
129
+ syncMutationDepth -= 1;
130
+ }
131
+ },
132
+ transitionLifecycle(transition) {
133
+ assertTrackedRolePolicyGate(policyGate, policyRoot);
134
+ lifecycleMutationDepth += 1;
135
+ try {
136
+ return store.transition(transition);
137
+ }
138
+ finally {
139
+ lifecycleMutationDepth -= 1;
140
+ }
141
+ },
142
+ transitionAuthorityRepair(transition) {
143
+ authorityRepairMutationDepth += 1;
144
+ try {
145
+ return store.transition(transition);
146
+ }
147
+ finally {
148
+ authorityRepairMutationDepth -= 1;
149
+ }
150
+ },
151
+ validatePayload(value) {
152
+ return validateTicketQueuePayload(value, queueSchema);
153
+ },
154
+ });
155
+ return publicStore;
156
+ }
157
+ export function createTicketLifecycleService(store) {
158
+ const internal = QUEUE_INTERNALS.get(store);
159
+ if (!internal)
160
+ throw new Error("ticket lifecycle service requires the canonical ticket queue store");
161
+ const authority = internal;
162
+ function read(ticketId) {
163
+ return store.read(assertRecordId(ticketId, "ticket id"));
164
+ }
165
+ function mutate(action, input, identityParts, build) {
166
+ authority.assertCurrentPolicy();
167
+ const normalized = validateMutationInput(input);
168
+ const commandId = createStableId("command", action, normalized.operationId, String(normalized.expectedVersion), String(normalized.expectedAttempt), normalized.expectedActor, normalized.ownerId, normalized.expectedLeaseToken === null ? "lease:null" : `lease:${normalized.expectedLeaseToken}`, normalized.at, ...identityParts);
169
+ const nonce = createTransitionNonce(action, normalized.ticketId, commandId);
170
+ const current = read(normalized.ticketId);
171
+ if (current.payload.policy_version !== authority.policyIdentity.policy_version
172
+ || current.payload.policy_digest !== authority.policyIdentity.policy_digest) {
173
+ throw new Error("stale role policy cannot authorize a ticket transition");
174
+ }
175
+ if (current.payload.gate_digest !== null
176
+ && current.payload.gate_digest !== authority.currentVerificationGateDigest()) {
177
+ throw new Error("stale verification gate cannot authorize a ticket transition");
178
+ }
179
+ if (current.transition_outcomes.some((outcome) => outcome.nonce === nonce)) {
180
+ return authority.transitionLifecycle({
181
+ recordId: current.record_id,
182
+ ownerId: normalized.ownerId,
183
+ expectedVersion: current.version,
184
+ nonce,
185
+ expectedActor: current.actor,
186
+ expectedAttempt: current.attempt,
187
+ expectedLeaseToken: current.lease_token,
188
+ fromStates: [current.state],
189
+ toState: current.state,
190
+ });
191
+ }
192
+ assertExpectedMutation(current, normalized);
193
+ return authority.transitionLifecycle(build(current, nonce, normalized));
194
+ }
195
+ function mutateActive(action, input, fromState, toState, updatePayload) {
196
+ return mutate(action, input, [], (current, nonce, normalized) => {
197
+ assertWorkerOwner(current, normalized, action);
198
+ assertActiveAvailable(current, normalized.at);
199
+ assertLifecycleTimeAdvances(current, normalized.at);
200
+ if (current.state !== fromState)
201
+ throw new Error(`ticket cannot ${action} from ${current.state}`);
202
+ return lifecycleTransitionOptions(nonce, normalized, current, toState, {
203
+ updatePayload: (payload) => ({
204
+ ...(updatePayload ? updatePayload(payload) : payload),
205
+ lifecycle_updated_at: normalized.at,
206
+ }),
207
+ });
208
+ });
209
+ }
210
+ return Object.freeze({
211
+ read,
212
+ authorityStatus(ticketId) {
213
+ authority.assertCurrentPolicy();
214
+ const current = read(ticketId);
215
+ if (current.payload.policy_version !== authority.policyIdentity.policy_version
216
+ || current.payload.policy_digest !== authority.policyIdentity.policy_digest)
217
+ return "stale-policy";
218
+ if (current.payload.gate_digest !== null
219
+ && current.payload.gate_digest !== authority.currentVerificationGateDigest())
220
+ return "stale-gate";
221
+ return "current";
222
+ },
223
+ prepare(input) {
224
+ const gateDigest = resolvePreparedGateDigest(authority, input.ticketId, input.verificationPlanRef);
225
+ if (gateDigest !== authority.currentVerificationGateDigest()) {
226
+ throw new Error("ticket preparation requires the current verification gate");
227
+ }
228
+ return mutate("prepare", input, [evidenceKey(input.verificationPlanRef), worktreeKey(input.worktree), gateDigest], (current, nonce, normalized) => {
229
+ assertSystemOwner(normalized.ownerId, authority.systemActors, "prepare");
230
+ assertLifecycleTimeAdvances(current, normalized.at);
231
+ if (current.state !== "pending")
232
+ throw new Error(`ticket cannot be prepared from ${current.state}`);
233
+ if (current.payload.verification_plan_ref !== null || current.payload.worktree !== null) {
234
+ throw new Error("ticket is already prepared");
235
+ }
236
+ return lifecycleTransitionOptions(nonce, normalized, current, "pending", {
237
+ updatePayload: (payload) => ({
238
+ ...payload,
239
+ lifecycle_updated_at: normalized.at,
240
+ gate_digest: gateDigest,
241
+ verification_plan_ref: input.verificationPlanRef,
242
+ worktree: input.worktree,
243
+ }),
244
+ });
245
+ });
246
+ },
247
+ lease(input) {
248
+ const leaseExpiresAt = canonicalTimestamp(input.leaseExpiresAt, "lease expiry");
249
+ return mutate("lease", input, [input.workerId, input.sessionId, leaseExpiresAt], (current, nonce, normalized) => {
250
+ assertSystemOwner(normalized.ownerId, authority.systemActors, "lease");
251
+ assertLifecycleTimeAdvances(current, normalized.at);
252
+ if (current.state !== "pending" && current.state !== "retryable") {
253
+ throw new Error(`ticket cannot be leased from ${current.state}`);
254
+ }
255
+ if (current.payload.verification_plan_ref === null || current.payload.worktree === null) {
256
+ throw new Error("ticket must be prepared before leasing");
257
+ }
258
+ if (current.state === "retryable"
259
+ && retryFailureRequiresReflection(current, authority.evidenceStore)
260
+ && current.payload.prior_reflection_evidence === null) {
261
+ throw new Error("retry lease requires the bounded reflection for its verification failure");
262
+ }
263
+ if (assertTimestamp(leaseExpiresAt, "lease expiry") <= assertTimestamp(normalized.at, "lease timestamp")) {
264
+ throw new Error("lease expiry must be after the lease timestamp");
265
+ }
266
+ const nextAttempt = current.state === "retryable" ? current.attempt + 1 : current.attempt;
267
+ if (nextAttempt > current.max_attempts)
268
+ throw new Error("attempt budget exhausted");
269
+ const worker = assertActorId(input.workerId, "worker id");
270
+ const sessionId = assertOpaqueId(input.sessionId, "session id");
271
+ const leaseToken = assertLeaseToken(createStableId("lease", current.record_id, worker, nonce));
272
+ return lifecycleTransitionOptions(nonce, normalized, current, "leased", {
273
+ nextActor: worker,
274
+ nextAttempt,
275
+ nextLeaseToken: leaseToken,
276
+ updatePayload: (payload) => ({
277
+ ...payload,
278
+ lifecycle_updated_at: normalized.at,
279
+ lease_expires_at: leaseExpiresAt,
280
+ resume_at: null,
281
+ resume_state: null,
282
+ park_reason: null,
283
+ resume_gate: null,
284
+ session_id: sessionId,
285
+ last_error: null,
286
+ executor_evidence: null,
287
+ verification_evidence: null,
288
+ grade_evidence: null,
289
+ }),
290
+ });
291
+ });
292
+ },
293
+ renew(input) {
294
+ const leaseExpiresAt = canonicalTimestamp(input.leaseExpiresAt, "lease expiry");
295
+ return mutate("renew", input, [leaseExpiresAt], (current, nonce, normalized) => {
296
+ assertWorkerOwner(current, normalized, "renew");
297
+ assertActiveAvailable(current, normalized.at);
298
+ assertLifecycleTimeAdvances(current, normalized.at);
299
+ const currentExpiry = current.payload.lease_expires_at;
300
+ if (currentExpiry === null || assertTimestamp(leaseExpiresAt, "lease expiry") <= assertTimestamp(currentExpiry, "stored lease expiry")) {
301
+ throw new Error("renewed lease expiry must extend the current lease");
302
+ }
303
+ return lifecycleTransitionOptions(nonce, normalized, current, current.state, {
304
+ updatePayload: (payload) => ({
305
+ ...payload,
306
+ lifecycle_updated_at: normalized.at,
307
+ lease_expires_at: leaseExpiresAt,
308
+ }),
309
+ });
310
+ });
311
+ },
312
+ start(input) {
313
+ return mutateActive("start", input, "leased", "running");
314
+ },
315
+ beginVerification(input) {
316
+ return mutateActive("verify", input, "running", "verifying", (payload) => ({ ...payload, executor_evidence: input.executorEvidence }));
317
+ },
318
+ complete(input) {
319
+ return mutate("complete", input, [evidenceKey(input.verificationEvidence), evidenceKey(input.gradeEvidence)], (current, nonce, normalized) => {
320
+ assertWorkerOwner(current, normalized, "complete");
321
+ assertActiveAvailable(current, normalized.at);
322
+ assertLifecycleTimeAdvances(current, normalized.at);
323
+ if (current.state !== "verifying")
324
+ throw new Error(`ticket cannot complete from ${current.state}`);
325
+ return lifecycleTransitionOptions(nonce, normalized, current, "completed", {
326
+ nextLeaseToken: null,
327
+ updatePayload: (payload) => ({
328
+ ...payload,
329
+ lifecycle_updated_at: normalized.at,
330
+ lease_expires_at: null,
331
+ session_id: null,
332
+ last_error: null,
333
+ verification_evidence: input.verificationEvidence,
334
+ grade_evidence: input.gradeEvidence,
335
+ }),
336
+ });
337
+ });
338
+ },
339
+ failAttempt(input) {
340
+ return mutate("fail", input, [
341
+ input.error,
342
+ optionalEvidenceKey(input.executorEvidence),
343
+ optionalEvidenceKey(input.verificationEvidence),
344
+ optionalEvidenceKey(input.gradeEvidence),
345
+ optionalEvidenceKey(input.failureEvidence),
346
+ optionalEvidenceKey(input.reflectionEvidence),
347
+ ], (current, nonce, normalized) => {
348
+ assertWorkerOwner(current, normalized, "fail attempt");
349
+ assertActiveAvailable(current, normalized.at);
350
+ assertLifecycleTimeAdvances(current, normalized.at);
351
+ if (!isActiveTicketState(current.state))
352
+ throw new Error(`ticket cannot fail an attempt from ${current.state}`);
353
+ const target = current.attempt >= current.max_attempts ? "blocked" : "retryable";
354
+ const failureEvidence = input.failureEvidence
355
+ ?? input.gradeEvidence
356
+ ?? input.verificationEvidence
357
+ ?? input.executorEvidence
358
+ ?? payloadFailureReference(current.payload);
359
+ if (failureEvidence === null)
360
+ throw new Error("failed attempt requires immutable failure evidence");
361
+ return lifecycleTransitionOptions(nonce, normalized, current, target, {
362
+ nextLeaseToken: null,
363
+ updatePayload: (payload) => ({
364
+ ...payload,
365
+ lifecycle_updated_at: normalized.at,
366
+ lease_expires_at: null,
367
+ resume_at: null,
368
+ resume_state: null,
369
+ park_reason: null,
370
+ resume_gate: null,
371
+ session_id: null,
372
+ last_error: assertBoundedText(input.error, "attempt error", 2048),
373
+ executor_evidence: input.executorEvidence ?? payload.executor_evidence,
374
+ verification_evidence: input.verificationEvidence ?? payload.verification_evidence,
375
+ grade_evidence: input.gradeEvidence ?? payload.grade_evidence,
376
+ prior_failure_evidence: failureEvidence,
377
+ prior_reflection_evidence: input.reflectionEvidence ?? null,
378
+ }),
379
+ });
380
+ });
381
+ },
382
+ blockVerification(input) {
383
+ return mutate("block-verification", input, [input.error, optionalEvidenceKey(input.verificationEvidence), optionalEvidenceKey(input.gradeEvidence)], (current, nonce, normalized) => {
384
+ assertSystemOwner(normalized.ownerId, authority.systemActors, "block verification");
385
+ assertActiveAvailable(current, normalized.at);
386
+ assertLifecycleTimeAdvances(current, normalized.at);
387
+ if (current.state !== "verifying") {
388
+ throw new Error(`ticket verification cannot be blocked from ${current.state}`);
389
+ }
390
+ return lifecycleTransitionOptions(nonce, normalized, current, "blocked", {
391
+ nextActor: normalized.ownerId,
392
+ nextLeaseToken: null,
393
+ updatePayload: (payload) => ({
394
+ ...payload,
395
+ lifecycle_updated_at: normalized.at,
396
+ lease_expires_at: null,
397
+ session_id: null,
398
+ last_error: assertBoundedText(input.error, "verification block reason", 2048),
399
+ verification_evidence: input.verificationEvidence ?? payload.verification_evidence,
400
+ grade_evidence: input.gradeEvidence ?? payload.grade_evidence,
401
+ }),
402
+ });
403
+ });
404
+ },
405
+ attachRetryReflection(input) {
406
+ return mutate("attach-retry-reflection", input, [evidenceKey(input.reflectionEvidence)], (current, nonce, normalized) => {
407
+ assertSystemOwner(normalized.ownerId, authority.systemActors, "attach retry reflection");
408
+ assertLifecycleTimeAdvances(current, normalized.at);
409
+ if (current.state !== "retryable") {
410
+ throw new Error(`retry reflection cannot attach during ${current.state}`);
411
+ }
412
+ if (current.payload.prior_failure_evidence === null) {
413
+ throw new Error("retry reflection requires prior failure evidence");
414
+ }
415
+ if (current.payload.prior_reflection_evidence !== null) {
416
+ throw new Error("retry reflection is already attached");
417
+ }
418
+ return lifecycleTransitionOptions(nonce, normalized, current, "retryable", {
419
+ nextActor: normalized.ownerId,
420
+ updatePayload: (payload) => ({
421
+ ...payload,
422
+ lifecycle_updated_at: normalized.at,
423
+ prior_reflection_evidence: input.reflectionEvidence,
424
+ }),
425
+ });
426
+ });
427
+ },
428
+ park(input) {
429
+ const resumeAt = canonicalTimestamp(input.resumeAt, "resume timestamp");
430
+ const quotaGate = input.quotaGate;
431
+ if (quotaGate !== undefined)
432
+ assertExhaustedQuotaGate(quotaGate);
433
+ const resumeGate = quotaGate === undefined
434
+ ? {
435
+ kind: "time",
436
+ role_id: null,
437
+ engine: null,
438
+ session_id: null,
439
+ limited_session_version: null,
440
+ limited_observation_id: null,
441
+ limited_observed_at: null,
442
+ }
443
+ : {
444
+ kind: "quota",
445
+ role_id: quotaGate.role_id,
446
+ engine: quotaGate.engine,
447
+ session_id: quotaGate.session_id,
448
+ limited_session_version: quotaGate.source_session_version,
449
+ limited_observation_id: quotaGate.observation.observation_id,
450
+ limited_observed_at: quotaGate.observed_at,
451
+ };
452
+ return mutate("park", input, [resumeAt, input.reason, JSON.stringify(resumeGate)], (current, nonce, normalized) => {
453
+ assertSystemOwner(normalized.ownerId, authority.systemActors, "park");
454
+ assertLifecycleTimeAdvances(current, normalized.at);
455
+ if (isTerminalTicketState(current.state))
456
+ throw new Error(`terminal ticket cannot be parked: ${current.state}`);
457
+ if (current.state === "parked")
458
+ throw new Error("ticket is already parked");
459
+ if (isActiveTicketState(current.state))
460
+ assertActiveAvailable(current, normalized.at);
461
+ if (assertTimestamp(resumeAt, "resume timestamp") <= assertTimestamp(normalized.at, "park timestamp")) {
462
+ throw new Error("resume timestamp must be after the park timestamp");
463
+ }
464
+ if (quotaGate !== undefined) {
465
+ if (!isActiveTicketState(current.state) || current.payload.session_id !== quotaGate.session_id) {
466
+ throw new Error("quota park gate is not bound to the active ticket session");
467
+ }
468
+ if (quotaGate.resume_at !== resumeAt
469
+ || quotaGate.checked_at !== normalized.at
470
+ || quotaGate.policy_version !== current.payload.policy_version
471
+ || quotaGate.policy_digest !== current.payload.policy_digest) {
472
+ throw new Error("quota park gate does not match ticket policy or schedule");
473
+ }
474
+ }
475
+ const resumeState = current.state === "retryable" ? "retryable" : "pending";
476
+ const clearAttemptEvidence = isActiveTicketState(current.state);
477
+ return lifecycleTransitionOptions(nonce, normalized, current, "parked", {
478
+ nextActor: normalized.ownerId,
479
+ nextLeaseToken: null,
480
+ updatePayload: (payload) => ({
481
+ ...payload,
482
+ lifecycle_updated_at: normalized.at,
483
+ lease_expires_at: null,
484
+ resume_at: resumeAt,
485
+ resume_state: resumeState,
486
+ park_reason: assertBoundedText(input.reason, "park reason", 512),
487
+ resume_gate: resumeGate,
488
+ session_id: null,
489
+ ...(clearAttemptEvidence
490
+ ? { executor_evidence: null, verification_evidence: null, grade_evidence: null }
491
+ : {}),
492
+ }),
493
+ });
494
+ });
495
+ },
496
+ resume(input) {
497
+ const quotaGate = input.quotaGate;
498
+ if (quotaGate !== undefined)
499
+ assertFreshQuotaGate(quotaGate);
500
+ return mutate("resume", input, [quotaGate?.observation.observation_id ?? "quota:null"], (current, nonce, normalized) => {
501
+ assertSystemOwner(normalized.ownerId, authority.systemActors, "resume");
502
+ assertLifecycleTimeAdvances(current, normalized.at);
503
+ if (current.state !== "parked")
504
+ throw new Error(`ticket is not parked: ${current.state}`);
505
+ const resumeAt = current.payload.resume_at;
506
+ const resumeState = current.payload.resume_state;
507
+ if (resumeAt === null || resumeState === null || current.payload.park_reason === null) {
508
+ throw new Error("ticket is not durably parked");
509
+ }
510
+ if (assertTimestamp(normalized.at, "resume timestamp") < assertTimestamp(resumeAt, "stored resume timestamp")) {
511
+ throw new Error("ticket resume is not due");
512
+ }
513
+ const storedGate = current.payload.resume_gate;
514
+ if (storedGate === null)
515
+ throw new Error("parked ticket is missing its resume gate");
516
+ if (storedGate.kind === "quota") {
517
+ if (quotaGate === undefined)
518
+ throw new Error("quota-parked ticket requires a fresh quota gate");
519
+ if (quotaGate.role_id !== storedGate.role_id
520
+ || quotaGate.engine !== storedGate.engine
521
+ || quotaGate.session_id !== storedGate.session_id
522
+ || quotaGate.source_session_version <= storedGate.limited_session_version
523
+ || quotaGate.policy_version !== current.payload.policy_version
524
+ || quotaGate.policy_digest !== current.payload.policy_digest
525
+ || quotaGate.checked_at !== normalized.at
526
+ || quotaGate.observation.observation_id === storedGate.limited_observation_id
527
+ || Date.parse(quotaGate.observed_at) <= Date.parse(storedGate.limited_observed_at)) {
528
+ throw new Error("fresh quota gate does not satisfy the parked ticket resume authority");
529
+ }
530
+ }
531
+ else if (quotaGate !== undefined) {
532
+ throw new Error("time-parked ticket must not consume quota authority");
533
+ }
534
+ return lifecycleTransitionOptions(nonce, normalized, current, resumeState, {
535
+ updatePayload: (payload) => ({
536
+ ...payload,
537
+ lifecycle_updated_at: normalized.at,
538
+ lease_expires_at: null,
539
+ resume_at: null,
540
+ resume_state: null,
541
+ park_reason: null,
542
+ resume_gate: null,
543
+ session_id: null,
544
+ }),
545
+ });
546
+ });
547
+ },
548
+ quarantineStaleAuthority(input) {
549
+ const normalized = validateMutationInput(input);
550
+ const reason = assertBoundedText(input.reason, "quarantine reason", 2048);
551
+ const commandId = createStableId("command", "quarantine-stale-authority", normalized.operationId, String(normalized.expectedVersion), String(normalized.expectedAttempt), normalized.expectedActor, normalized.ownerId, normalized.expectedLeaseToken === null ? "lease:null" : `lease:${normalized.expectedLeaseToken}`, normalized.at, reason);
552
+ const nonce = createTransitionNonce("quarantine", normalized.ticketId, commandId);
553
+ const current = read(normalized.ticketId);
554
+ if (current.transition_outcomes.some((outcome) => outcome.nonce === nonce)) {
555
+ return authority.transitionAuthorityRepair({
556
+ recordId: current.record_id,
557
+ ownerId: normalized.ownerId,
558
+ expectedVersion: current.version,
559
+ nonce,
560
+ expectedActor: current.actor,
561
+ expectedAttempt: current.attempt,
562
+ expectedLeaseToken: current.lease_token,
563
+ fromStates: [current.state],
564
+ toState: current.state,
565
+ });
566
+ }
567
+ assertExpectedMutation(current, normalized);
568
+ assertSystemOwner(normalized.ownerId, authority.systemActors, "quarantine stale ticket authority");
569
+ let stalePolicy = current.payload.policy_version !== authority.policyIdentity.policy_version
570
+ || current.payload.policy_digest !== authority.policyIdentity.policy_digest;
571
+ try {
572
+ authority.assertCurrentPolicy();
573
+ }
574
+ catch {
575
+ stalePolicy = true;
576
+ }
577
+ let staleGate = false;
578
+ try {
579
+ staleGate = current.payload.gate_digest !== authority.currentVerificationGateDigest();
580
+ }
581
+ catch {
582
+ staleGate = true;
583
+ }
584
+ if (!stalePolicy && !staleGate) {
585
+ throw new Error("ticket authority quarantine requires stale policy or verification gate evidence");
586
+ }
587
+ if (current.payload.gate_digest === null) {
588
+ throw new Error("unprepared tickets cannot use the prepared-ticket quarantine path");
589
+ }
590
+ return authority.transitionAuthorityRepair(lifecycleTransitionOptions(nonce, normalized, current, "blocked", {
591
+ nextActor: normalized.ownerId,
592
+ nextLeaseToken: null,
593
+ updatePayload: (payload) => ({
594
+ ...payload,
595
+ lifecycle_updated_at: normalized.at,
596
+ lease_expires_at: null,
597
+ resume_at: null,
598
+ resume_state: null,
599
+ park_reason: null,
600
+ resume_gate: null,
601
+ session_id: null,
602
+ last_error: reason,
603
+ executor_evidence: null,
604
+ verification_evidence: null,
605
+ grade_evidence: null,
606
+ }),
607
+ }));
608
+ },
609
+ rebindUnpreparedAuthority(input) {
610
+ const normalized = validateMutationInput(input);
611
+ const commandId = createStableId("command", "rebind-unprepared-authority", normalized.operationId, String(normalized.expectedVersion), normalized.ownerId, normalized.at);
612
+ const nonce = createTransitionNonce("authority-rebind", normalized.ticketId, commandId);
613
+ const current = read(normalized.ticketId);
614
+ if (current.transition_outcomes.some((outcome) => outcome.nonce === nonce)) {
615
+ return authority.transitionAuthorityRepair({
616
+ recordId: current.record_id,
617
+ ownerId: normalized.ownerId,
618
+ expectedVersion: current.version,
619
+ nonce,
620
+ expectedActor: current.actor,
621
+ expectedAttempt: current.attempt,
622
+ expectedLeaseToken: current.lease_token,
623
+ fromStates: [current.state],
624
+ toState: current.state,
625
+ });
626
+ }
627
+ assertExpectedMutation(current, normalized);
628
+ assertSystemOwner(normalized.ownerId, authority.systemActors, "rebind unprepared ticket authority");
629
+ authority.assertCurrentPolicy();
630
+ if (current.state !== "pending"
631
+ || current.payload.gate_digest !== null
632
+ || current.payload.verification_plan_ref !== null
633
+ || current.payload.worktree !== null) {
634
+ throw new Error("only an unprepared pending ticket can rebind current authority");
635
+ }
636
+ if (current.payload.policy_version === authority.policyIdentity.policy_version
637
+ && current.payload.policy_digest === authority.policyIdentity.policy_digest) {
638
+ throw new Error("unprepared ticket already has the current authority");
639
+ }
640
+ return authority.transitionAuthorityRepair(lifecycleTransitionOptions(nonce, normalized, current, "pending", {
641
+ nextActor: normalized.ownerId,
642
+ updatePayload: (payload) => ({
643
+ ...payload,
644
+ lifecycle_updated_at: normalized.at,
645
+ policy_version: authority.policyIdentity.policy_version,
646
+ policy_digest: authority.policyIdentity.policy_digest,
647
+ last_error: "unprepared ticket authority rebound to current policy",
648
+ }),
649
+ }));
650
+ },
651
+ recoverExpiredLease(input) {
652
+ return mutate("recover", input, [], (current, nonce, normalized) => {
653
+ assertSystemOwner(normalized.ownerId, authority.systemActors, "recover expired lease");
654
+ assertLifecycleTimeAdvances(current, normalized.at);
655
+ if (!isActiveTicketState(current.state))
656
+ throw new Error(`ticket has no active lease in ${current.state}`);
657
+ const expiresAt = current.payload.lease_expires_at;
658
+ if (expiresAt === null || assertTimestamp(expiresAt, "stored lease expiry") > assertTimestamp(normalized.at, "recovery timestamp")) {
659
+ throw new Error("ticket lease has not expired");
660
+ }
661
+ const leaseToken = current.lease_token;
662
+ const worktree = current.payload.worktree;
663
+ const evidenceStore = authority.evidenceStore;
664
+ if (leaseToken === null || worktree === null || evidenceStore === undefined) {
665
+ throw new Error("expired lease recovery lacks trusted retry authority");
666
+ }
667
+ const reason = `lease expired at ${expiresAt}`;
668
+ const worktreeStartCommit = activeAttemptStartCommit(current, evidenceStore);
669
+ const failureEvidence = evidenceStore.write({
670
+ schema_version: 1,
671
+ evidence_id: createStableId("lease-expiry", current.record_id, String(current.attempt), leaseToken),
672
+ evidence_kind: "lease-expiry",
673
+ ticket_id: current.record_id,
674
+ producer_role: "oms.watchdog",
675
+ created_at: normalized.at,
676
+ payload: {
677
+ ticket_id: current.record_id,
678
+ attempt: current.attempt,
679
+ lease_token: leaseToken,
680
+ worktree_start_commit: worktreeStartCommit,
681
+ expired_at: expiresAt,
682
+ reason,
683
+ },
684
+ }).ref;
685
+ const target = current.attempt >= current.max_attempts ? "blocked" : "retryable";
686
+ return lifecycleTransitionOptions(nonce, normalized, current, target, {
687
+ nextActor: normalized.ownerId,
688
+ nextLeaseToken: null,
689
+ updatePayload: (payload) => ({
690
+ ...payload,
691
+ lifecycle_updated_at: normalized.at,
692
+ lease_expires_at: null,
693
+ resume_at: null,
694
+ resume_state: null,
695
+ park_reason: null,
696
+ resume_gate: null,
697
+ session_id: null,
698
+ last_error: reason,
699
+ prior_failure_evidence: failureEvidence,
700
+ prior_reflection_evidence: null,
701
+ }),
702
+ });
703
+ });
704
+ },
705
+ });
706
+ }
707
+ export function createTicketMaterializer(options) {
708
+ const queueStore = options.queueStore;
709
+ const ticketStore = options.ticketStore;
710
+ const queueInternal = QUEUE_INTERNALS.get(queueStore);
711
+ if (!queueInternal)
712
+ throw new Error("ticket materializer requires the canonical ticket queue store");
713
+ assertTicketContentStore(ticketStore);
714
+ const actorId = assertActorId(options.actorId, "ticket materializer actor");
715
+ if (!queueInternal.systemActors.has(actorId)) {
716
+ throw new Error("ticket materializer actor must be an allowed system actor");
717
+ }
718
+ const materializer = Object.freeze({
719
+ syncGitHub(input) {
720
+ return syncGitHubTicket({
721
+ ticketStore,
722
+ queueStore,
723
+ actorId,
724
+ input,
725
+ });
726
+ },
727
+ syncLocal(input) {
728
+ return syncLocalTicket({
729
+ ticketStore,
730
+ queueStore,
731
+ actorId,
732
+ input,
733
+ });
734
+ },
735
+ readContent(ref) {
736
+ return ticketStore.read(ref);
737
+ },
738
+ read(recordId) {
739
+ return queueStore.read(recordId);
740
+ },
741
+ readIfExists(recordId) {
742
+ return queueStore.readIfExists(recordId);
743
+ },
744
+ });
745
+ MATERIALIZER_INTERNALS.set(materializer, {
746
+ queueStore,
747
+ ticketStore,
748
+ });
749
+ return materializer;
750
+ }
751
+ export function assertTicketMaterializer(value) {
752
+ if (typeof value !== "object" || value === null || !MATERIALIZER_INTERNALS.has(value)) {
753
+ throw new Error("ticket operation requires a real OMS TicketMaterializer");
754
+ }
755
+ if (!Object.isFrozen(value))
756
+ throw new Error("ticket materializer lost its frozen authority boundary");
757
+ }
758
+ export function assertTicketMaterializerEvidenceStore(materializer, evidenceStore) {
759
+ assertTicketMaterializer(materializer);
760
+ assertEvidenceStore(evidenceStore);
761
+ const materializerInternal = MATERIALIZER_INTERNALS.get(materializer);
762
+ const queueInternal = materializerInternal && QUEUE_INTERNALS.get(materializerInternal.queueStore);
763
+ if (!queueInternal || queueInternal.evidenceStore !== evidenceStore) {
764
+ throw new Error("ticket materializer is not bound to the supplied evidence store");
765
+ }
766
+ }
767
+ function assertTicketContentStore(value) {
768
+ if (typeof value !== "object" || value === null || !TICKET_CONTENT_STORES.has(value)) {
769
+ throw new Error("ticket operation requires a real ticket content store");
770
+ }
771
+ if (!Object.isFrozen(value))
772
+ throw new Error("ticket content store lost its frozen authority boundary");
773
+ }
774
+ function lifecycleTransitionOptions(nonce, input, current, toState, options = {}) {
775
+ return {
776
+ recordId: current.record_id,
777
+ ownerId: input.ownerId,
778
+ expectedVersion: input.expectedVersion,
779
+ nonce,
780
+ expectedActor: input.expectedActor,
781
+ expectedAttempt: input.expectedAttempt,
782
+ expectedLeaseToken: input.expectedLeaseToken,
783
+ nextActor: options.nextActor,
784
+ nextAttempt: options.nextAttempt,
785
+ nextLeaseToken: options.nextLeaseToken,
786
+ fromStates: [current.state],
787
+ toState,
788
+ updatePayload: options.updatePayload
789
+ ? (record) => options.updatePayload?.(record.payload) ?? record.payload
790
+ : undefined,
791
+ };
792
+ }
793
+ function validateMutationInput(input) {
794
+ assertRecordId(input.ticketId, "ticket id");
795
+ assertOpaqueId(input.operationId, "operation id");
796
+ assertActorId(input.ownerId, "operation owner id");
797
+ assertActorId(input.expectedActor, "expected actor");
798
+ if (input.expectedLeaseToken !== null)
799
+ assertLeaseToken(input.expectedLeaseToken, "expected lease token");
800
+ if (!Number.isSafeInteger(input.expectedVersion) || input.expectedVersion < 0)
801
+ throw new Error("invalid expected version");
802
+ if (!Number.isSafeInteger(input.expectedAttempt) || input.expectedAttempt < 1)
803
+ throw new Error("invalid expected attempt");
804
+ return { ...input, at: canonicalTimestamp(input.at, "operation timestamp") };
805
+ }
806
+ function assertExpectedMutation(current, input) {
807
+ if (current.version !== input.expectedVersion)
808
+ throw new Error("stale version");
809
+ if (current.attempt !== input.expectedAttempt)
810
+ throw new Error("attempt mismatch");
811
+ if (current.actor !== input.expectedActor)
812
+ throw new Error("actor mismatch");
813
+ if (current.lease_token !== input.expectedLeaseToken)
814
+ throw new Error("lease token mismatch");
815
+ }
816
+ function assertLifecycleTimeAdvances(current, at) {
817
+ if (assertTimestamp(at, "operation timestamp") <= assertTimestamp(current.payload.lifecycle_updated_at, "lifecycle timestamp")) {
818
+ throw new Error("ticket lifecycle timestamp must advance");
819
+ }
820
+ }
821
+ function assertActiveAvailable(current, at) {
822
+ if (!isActiveTicketState(current.state))
823
+ throw new Error(`ticket is not active: ${current.state}`);
824
+ const expiresAt = current.payload.lease_expires_at;
825
+ if (expiresAt === null || assertTimestamp(expiresAt, "stored lease expiry") <= assertTimestamp(at, "operation timestamp")) {
826
+ throw new Error("ticket lease has expired");
827
+ }
828
+ }
829
+ function assertWorkerOwner(current, input, action) {
830
+ if (input.ownerId !== current.actor)
831
+ throw new Error(`${action} requires the current worker actor`);
832
+ }
833
+ function assertSystemOwner(ownerId, systemActors, action) {
834
+ if (!systemActors.has(ownerId))
835
+ throw new Error(`${action} requires an allowed system actor`);
836
+ }
837
+ function assertBoundedText(value, label, maxLength) {
838
+ if (typeof value !== "string" || value.length < 1 || value.length > maxLength)
839
+ throw new Error(`invalid ${label}`);
840
+ return value;
841
+ }
842
+ function evidenceKey(reference) {
843
+ return `${reference.evidence_id}:${reference.sha256}:${reference.file_name}`;
844
+ }
845
+ function optionalEvidenceKey(reference) {
846
+ return reference ? evidenceKey(reference) : "evidence:null";
847
+ }
848
+ function payloadFailureReference(payload) {
849
+ return payload.grade_evidence ?? payload.verification_evidence ?? payload.executor_evidence;
850
+ }
851
+ function worktreeKey(worktree) {
852
+ return `${worktree.branch}:${worktree.base_commit}`;
853
+ }
854
+ function resolvePreparedGateDigest(internal, ticketId, reference) {
855
+ if (!internal.evidenceStore)
856
+ throw new Error("ticket preparation requires an evidence store");
857
+ const evidence = internal.evidenceStore.read(reference);
858
+ if (evidence.evidence_kind !== "verification-plan" || evidence.ticket_id !== assertRecordId(ticketId, "ticket id")) {
859
+ throw new Error("ticket preparation requires its own verification plan evidence");
860
+ }
861
+ const digest = evidence.payload.digest;
862
+ if (typeof digest !== "string")
863
+ throw new Error("verification plan evidence is missing its gate digest");
864
+ validateSha256(digest, "verification gate digest");
865
+ return digest;
866
+ }
867
+ function acquireSyncLockWithRetry(options) {
868
+ let lastBusy;
869
+ for (let attempt = 1; attempt <= SYNC_LOCK_ATTEMPTS; attempt += 1) {
870
+ try {
871
+ return acquireDirectoryLock(options);
872
+ }
873
+ catch (error) {
874
+ if (!(error instanceof LockBusyError))
875
+ throw error;
876
+ lastBusy = error;
877
+ if (attempt < SYNC_LOCK_ATTEMPTS)
878
+ sleepSync(SYNC_LOCK_RETRY_MS);
879
+ }
880
+ }
881
+ throw new Error(`ticket sync lock remained busy after ${SYNC_LOCK_ATTEMPTS} attempts`, { cause: lastBusy });
882
+ }
883
+ function sleepSync(milliseconds) {
884
+ const signal = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
885
+ Atomics.wait(signal, 0, 0, milliseconds);
886
+ }
887
+ export function createTicketContentStore(options) {
888
+ const root = openStableDirectory(options.ticketsRoot, "tickets root");
889
+ const packageRoot = options.packageRoot ?? findPackageRoot();
890
+ const contentSchema = loadJsonSchema(join(packageRoot, "schemas", "ticket-content.schema.json"));
891
+ function prepareContent(content, sourceRefSha256) {
892
+ root.assertUnchanged();
893
+ validateTicketContent(content, contentSchema);
894
+ validateSha256(sourceRefSha256, "ticket source digest");
895
+ if (sourceRefForContent(content) !== sourceRefSha256)
896
+ throw new Error("ticket source identity failed verification");
897
+ const bytes = canonicalJson(content);
898
+ if (bytes.byteLength > MAX_TICKET_BYTES)
899
+ throw new Error(`ticket content exceeds ${MAX_TICKET_BYTES} bytes`);
900
+ const digest = sha256(bytes);
901
+ return {
902
+ content,
903
+ source_ref_sha256: sourceRefSha256,
904
+ bytes,
905
+ ref: {
906
+ ticket_id: content.ticket_id,
907
+ sha256: digest,
908
+ file_name: `${content.ticket_id}.${digest}.json`,
909
+ },
910
+ };
911
+ }
912
+ function readStored(ref) {
913
+ validateTicketRef(ref);
914
+ const path = join(root.path, ref.file_name);
915
+ assertFileInside(root, path);
916
+ const bytes = readImmutableFile(path, MAX_TICKET_BYTES);
917
+ if (sha256(bytes) !== ref.sha256)
918
+ throw new Error(`ticket content digest mismatch: ${ref.ticket_id}`);
919
+ const content = validateTicketContent(parseJson(bytes, "ticket content"), contentSchema);
920
+ if (content.ticket_id !== ref.ticket_id)
921
+ throw new Error("stored ticket content has the wrong ticket id");
922
+ return freezeJsonSnapshot(content);
923
+ }
924
+ const store = Object.freeze({
925
+ prepareGitHub(input) {
926
+ const repository = assertGitHubRepository(input.repository);
927
+ const identity = deriveGitHubTicketIdentity(input);
928
+ return prepareContent({
929
+ schema_version: 1,
930
+ ticket_id: identity.ticket_id,
931
+ source: "github",
932
+ repository,
933
+ issue_node_id: input.issue_node_id,
934
+ title: input.title,
935
+ body: input.body,
936
+ labels: normalizeLabels(input.labels),
937
+ url: input.url,
938
+ updated_at: input.updated_at,
939
+ }, identity.source_ref_sha256);
940
+ },
941
+ prepareLocal(input) {
942
+ const ticketId = assertTicketRecordId(input.ticket_id);
943
+ const sourceRefSha256 = sha256(Buffer.from(`local\0${ticketId}`, "utf8"));
944
+ return prepareContent({
945
+ schema_version: 1,
946
+ ticket_id: ticketId,
947
+ source: "local",
948
+ repository: null,
949
+ issue_node_id: null,
950
+ title: input.title,
951
+ body: input.body,
952
+ labels: normalizeLabels(input.labels),
953
+ url: input.url,
954
+ updated_at: input.updated_at,
955
+ }, sourceRefSha256);
956
+ },
957
+ write(prepared) {
958
+ validateTicketRef(prepared.ref);
959
+ validateTicketContent(prepared.content, contentSchema);
960
+ if (sourceRefForContent(prepared.content) !== prepared.source_ref_sha256) {
961
+ throw new Error("prepared ticket source identity failed verification");
962
+ }
963
+ const expected = canonicalJson(prepared.content);
964
+ if (!expected.equals(prepared.bytes) || sha256(prepared.bytes) !== prepared.ref.sha256) {
965
+ throw new Error("prepared ticket content failed digest verification");
966
+ }
967
+ const path = join(root.path, prepared.ref.file_name);
968
+ assertFileInside(root, path);
969
+ const status = writeImmutableFile(path, prepared.bytes);
970
+ readStored(prepared.ref);
971
+ return status;
972
+ },
973
+ read(ref) {
974
+ return readStored(ref);
975
+ },
976
+ });
977
+ TICKET_CONTENT_STORES.add(store);
978
+ return store;
979
+ }
980
+ export function syncGitHubTicket(options) {
981
+ assertTicketContentStore(options.ticketStore);
982
+ if (!Array.isArray(options.input.labels) || !options.input.labels.includes("oms:ready")) {
983
+ throw new Error("GitHub ticket is missing ready-label intent: oms:ready");
984
+ }
985
+ return freezeSyncTicketResult(syncPreparedTicket(options, options.ticketStore.prepareGitHub(options.input)));
986
+ }
987
+ export function syncLocalTicket(options) {
988
+ assertTicketContentStore(options.ticketStore);
989
+ return freezeSyncTicketResult(syncPreparedTicket(options, options.ticketStore.prepareLocal(options.input)));
990
+ }
991
+ function syncPreparedTicket(options, prepared) {
992
+ assertTicketContentStore(options.ticketStore);
993
+ const internal = QUEUE_INTERNALS.get(options.queueStore);
994
+ if (!internal)
995
+ throw new Error("ticket sync requires a validated ticket queue store");
996
+ const actor = assertActorId(options.actorId);
997
+ if (!internal.systemActors.has(actor))
998
+ throw new Error("ticket sync requires an allowed system actor");
999
+ const maxAttempts = options.maxAttempts ?? MAX_QUEUE_ATTEMPTS;
1000
+ if (maxAttempts !== MAX_QUEUE_ATTEMPTS) {
1001
+ throw new Error(`ticket maxAttempts must equal ${MAX_QUEUE_ATTEMPTS}`);
1002
+ }
1003
+ return internal.synchronized(prepared.ref.ticket_id, actor, () => {
1004
+ const current = options.queueStore.readIfExists(prepared.ref.ticket_id);
1005
+ if (!current) {
1006
+ const operationSha256 = syncOperationFingerprint(prepared, maxAttempts, internal.policyIdentity);
1007
+ const nonce = assertTransitionNonce(`sync-${operationSha256.slice(0, 59)}`);
1008
+ options.ticketStore.write(prepared);
1009
+ const record = initialTicketRecord(prepared, actor, nonce, maxAttempts, operationSha256, internal.policyIdentity);
1010
+ return { status: "created", ref: prepared.ref, record: internal.store.create(record) };
1011
+ }
1012
+ const currentPayload = internal.validatePayload(current.payload);
1013
+ const boundPolicy = policyIdentityFromPayload(currentPayload);
1014
+ const operationSha256 = syncOperationFingerprint(prepared, maxAttempts, boundPolicy);
1015
+ const nonce = assertTransitionNonce(`sync-${operationSha256.slice(0, 59)}`);
1016
+ assertQueuePayload(current, currentPayload, prepared);
1017
+ if (current.max_attempts !== maxAttempts) {
1018
+ throw new Error(`maxAttempts conflicts with the stored ticket record: ${current.max_attempts}`);
1019
+ }
1020
+ const replay = currentPayload.sync_outcomes.find((outcome) => outcome.nonce === nonce);
1021
+ if (replay)
1022
+ return replaySyncOutcome(replay, operationSha256, current, options.ticketStore);
1023
+ if (current.transition_outcomes.some((outcome) => outcome.nonce === nonce)) {
1024
+ throw new Error(`sync nonce was already used by another transition: ${nonce}`);
1025
+ }
1026
+ const incomingTime = timestampEpoch(prepared.content.updated_at, "incoming ticket timestamp");
1027
+ const currentTime = timestampEpoch(currentPayload.source_updated_at, "stored ticket timestamp");
1028
+ if (incomingTime < currentTime)
1029
+ throw new Error("ticket source update is older than the stored version");
1030
+ if (incomingTime === currentTime && currentPayload.content_sha256 !== prepared.ref.sha256) {
1031
+ throw new Error("ticket source timestamp conflicts with different content");
1032
+ }
1033
+ const status = currentPayload.content_sha256 === prepared.ref.sha256 ? "unchanged" : "refreshed";
1034
+ if (status === "refreshed" && current.state !== "pending") {
1035
+ throw new Error(`ticket content refresh requires pending state: ${prepared.ref.ticket_id} is ${current.state}`);
1036
+ }
1037
+ if (status === "refreshed")
1038
+ options.ticketStore.write(prepared);
1039
+ else
1040
+ options.ticketStore.read(prepared.ref);
1041
+ const syncOutcome = makeSyncOutcome(nonce, operationSha256, status, prepared.ref, current.version + 1);
1042
+ const transitioned = internal.transitionSync({
1043
+ recordId: current.record_id,
1044
+ ownerId: actor,
1045
+ expectedVersion: current.version,
1046
+ nonce,
1047
+ expectedActor: current.actor,
1048
+ expectedAttempt: current.attempt,
1049
+ expectedLeaseToken: current.lease_token,
1050
+ fromStates: [current.state],
1051
+ toState: current.state,
1052
+ updatePayload: () => appendSyncOutcome(currentPayload, prepared, syncOutcome),
1053
+ });
1054
+ const storedOutcome = internal.validatePayload(transitioned.record.payload).sync_outcomes.find((outcome) => outcome.nonce === nonce);
1055
+ if (!storedOutcome || storedOutcome.operation_sha256 !== operationSha256) {
1056
+ throw new Error(`ticket sync outcome was not durably recorded: ${nonce}`);
1057
+ }
1058
+ return { status, ref: prepared.ref, record: transitioned.record };
1059
+ });
1060
+ }
1061
+ function replaySyncOutcome(outcome, operationSha256, current, ticketStore) {
1062
+ if (outcome.operation_sha256 !== operationSha256) {
1063
+ throw new Error(`sync nonce is bound to a different operation: ${outcome.nonce}`);
1064
+ }
1065
+ const ref = {
1066
+ ticket_id: current.record_id,
1067
+ sha256: outcome.content_sha256,
1068
+ file_name: outcome.content_file,
1069
+ };
1070
+ ticketStore.read(ref);
1071
+ return { status: outcome.status, ref, record: current };
1072
+ }
1073
+ function initialTicketRecord(prepared, actor, nonce, maxAttempts, operationSha256, policyIdentity) {
1074
+ return {
1075
+ schema_version: 1,
1076
+ record_id: assertRecordId(prepared.ref.ticket_id),
1077
+ record_kind: assertOpaqueId("ticket"),
1078
+ version: 0,
1079
+ state: "pending",
1080
+ attempt: 1,
1081
+ max_attempts: maxAttempts,
1082
+ actor,
1083
+ lease_token: null,
1084
+ transition_nonce: nonce,
1085
+ payload: initialQueuePayload(prepared, makeSyncOutcome(nonce, operationSha256, "created", prepared.ref, 0), policyIdentity),
1086
+ transition_outcomes: [{ nonce, version: 0, state: "pending", attempt: 1, actor, lease_token: null }],
1087
+ };
1088
+ }
1089
+ function initialQueuePayload(prepared, syncOutcome, policyIdentity) {
1090
+ return {
1091
+ ...sourceQueuePayload(prepared),
1092
+ ...initialQueueLifecycle(prepared.content.updated_at),
1093
+ ...policyIdentity,
1094
+ gate_digest: null,
1095
+ verification_plan_ref: null,
1096
+ worktree: null,
1097
+ executor_evidence: null,
1098
+ verification_evidence: null,
1099
+ grade_evidence: null,
1100
+ prior_failure_evidence: null,
1101
+ prior_reflection_evidence: null,
1102
+ merge_disposition: "human_hold",
1103
+ sync_outcomes: [syncOutcome],
1104
+ };
1105
+ }
1106
+ function appendSyncOutcome(current, prepared, outcome) {
1107
+ if (current.sync_outcomes.length >= MAX_SYNC_OUTCOMES)
1108
+ throw new Error("ticket sync outcome history is full");
1109
+ const source = outcome.status === "refreshed" ? sourceQueuePayload(prepared) : {};
1110
+ return { ...current, ...source, sync_outcomes: [...current.sync_outcomes, outcome] };
1111
+ }
1112
+ function sourceQueuePayload(prepared) {
1113
+ return {
1114
+ ticket_id: prepared.ref.ticket_id,
1115
+ source: prepared.content.source,
1116
+ source_ref_sha256: prepared.source_ref_sha256,
1117
+ content_sha256: prepared.ref.sha256,
1118
+ content_file: prepared.ref.file_name,
1119
+ source_updated_at: prepared.content.updated_at,
1120
+ ready_intent: true,
1121
+ };
1122
+ }
1123
+ function makeSyncOutcome(nonce, operationSha256, status, ref, recordVersion) {
1124
+ return {
1125
+ nonce,
1126
+ operation_sha256: operationSha256,
1127
+ status,
1128
+ content_sha256: ref.sha256,
1129
+ content_file: ref.file_name,
1130
+ record_version: recordVersion,
1131
+ };
1132
+ }
1133
+ function syncOperationFingerprint(prepared, maxAttempts, policyIdentity) {
1134
+ return sha256(canonicalJson({
1135
+ ticket_id: prepared.ref.ticket_id,
1136
+ source: prepared.content.source,
1137
+ source_ref_sha256: prepared.source_ref_sha256,
1138
+ content_sha256: prepared.ref.sha256,
1139
+ source_updated_at: prepared.content.updated_at,
1140
+ max_attempts: maxAttempts,
1141
+ policy_version: policyIdentity.policy_version,
1142
+ policy_digest: policyIdentity.policy_digest,
1143
+ }));
1144
+ }
1145
+ function assertQueuePayload(current, payload, prepared) {
1146
+ if (current.record_kind !== "ticket" || payload.ticket_id !== current.record_id) {
1147
+ throw new Error(`record is not a ticket queue record: ${current.record_id}`);
1148
+ }
1149
+ if (payload.source !== prepared.content.source
1150
+ || payload.source_ref_sha256 !== prepared.source_ref_sha256
1151
+ || payload.ready_intent !== true) {
1152
+ throw new Error(`ticket source identity mismatch: ${current.record_id}`);
1153
+ }
1154
+ }
1155
+ function validateTicketRuntimeRecord(record, schema, evidenceStore) {
1156
+ if (record.record_kind !== "ticket" || !isTicketState(record.state))
1157
+ throw new Error("invalid ticket runtime record");
1158
+ if (record.max_attempts !== MAX_QUEUE_ATTEMPTS) {
1159
+ throw new Error(`ticket max_attempts must equal ${MAX_QUEUE_ATTEMPTS}`);
1160
+ }
1161
+ for (const outcome of record.transition_outcomes) {
1162
+ if (!isTicketState(outcome.state))
1163
+ throw new Error("ticket transition contains an invalid lifecycle state");
1164
+ }
1165
+ const payload = validateTicketQueuePayload(record.payload, schema);
1166
+ if (payload.ticket_id !== record.record_id)
1167
+ throw new Error("ticket queue payload id does not match its record");
1168
+ validateStateInvariants(record, payload);
1169
+ const resolved = resolveLifecycleEvidence(payload, record, evidenceStore);
1170
+ validateStatePayloadRequirements(record.state, payload, resolved);
1171
+ const firstSync = payload.sync_outcomes[0];
1172
+ if (!firstSync || firstSync.status !== "created" || firstSync.record_version !== 0) {
1173
+ throw new Error("ticket sync history must begin with record creation");
1174
+ }
1175
+ for (const outcome of payload.sync_outcomes) {
1176
+ const transition = record.transition_outcomes.find((candidate) => candidate.nonce === outcome.nonce);
1177
+ if (!transition || transition.version !== outcome.record_version) {
1178
+ throw new Error(`ticket sync outcome is not bound to a runtime transition: ${outcome.nonce}`);
1179
+ }
1180
+ }
1181
+ }
1182
+ function validateInitialTicketRecord(record, systemActors, policyIdentity) {
1183
+ if (record.version !== 0
1184
+ || record.state !== "pending"
1185
+ || record.attempt !== 1
1186
+ || record.max_attempts !== MAX_QUEUE_ATTEMPTS
1187
+ || record.lease_token !== null
1188
+ || record.transition_outcomes.length !== 1) {
1189
+ throw new Error("initial ticket record must be pending at version zero and attempt one of three");
1190
+ }
1191
+ if (!systemActors.has(record.actor))
1192
+ throw new Error("initial ticket actor must be an allowed system actor");
1193
+ const payload = record.payload;
1194
+ if (payload.policy_version !== policyIdentity.policy_version
1195
+ || payload.policy_digest !== policyIdentity.policy_digest
1196
+ || payload.gate_digest !== null) {
1197
+ throw new Error("initial ticket policy identity does not match the materializer policy");
1198
+ }
1199
+ }
1200
+ function validateStatePayloadRequirements(state, payload, resolved) {
1201
+ if (["leased", "running", "verifying", "retryable", "blocked", "completed"].includes(state)) {
1202
+ if (payload.verification_plan_ref === null || payload.worktree === null || payload.gate_digest === null || !resolved.plan) {
1203
+ throw new Error(`ticket state ${state} requires a verification plan and worktree`);
1204
+ }
1205
+ }
1206
+ if (["verifying", "completed"].includes(state) && (payload.executor_evidence === null || !resolved.executor)) {
1207
+ throw new Error(`ticket state ${state} requires executor evidence`);
1208
+ }
1209
+ if (["verifying", "completed"].includes(state) && resolved.executor && !executorSucceeded(resolved.executor)) {
1210
+ throw new Error(`ticket state ${state} requires successful executor evidence`);
1211
+ }
1212
+ if (state === "completed" && (payload.verification_evidence === null
1213
+ || payload.grade_evidence === null
1214
+ || !resolved.verification
1215
+ || !resolved.grade)) {
1216
+ throw new Error("completed ticket requires verification and grade evidence");
1217
+ }
1218
+ if (state === "completed" && resolved.verification?.payload.passed !== true) {
1219
+ throw new Error("completed ticket requires a passing deterministic check");
1220
+ }
1221
+ if (state === "completed" && resolved.verification?.payload.certification !== "live") {
1222
+ throw new Error("completed ticket requires protected live deterministic evidence");
1223
+ }
1224
+ if (state === "completed" && resolved.grade?.payload.verdict !== "pass") {
1225
+ throw new Error("completed ticket requires a passing grader verdict");
1226
+ }
1227
+ if (payload.prior_reflection_evidence !== null && !resolved.priorReflection) {
1228
+ throw new Error("ticket prior reflection reference is invalid");
1229
+ }
1230
+ }
1231
+ function validateTicketTransition(currentRecord, nextRecord, schema, syncMutation, lifecycleMutation, authorityRepairMutation, systemActors, evidenceStore) {
1232
+ if (Number(syncMutation) + Number(lifecycleMutation) + Number(authorityRepairMutation) !== 1) {
1233
+ throw new Error("ticket transition requires exactly one authorized mutation path");
1234
+ }
1235
+ const current = validateTicketQueuePayload(currentRecord.payload, schema);
1236
+ const next = validateTicketQueuePayload(nextRecord.payload, schema);
1237
+ const prefix = next.sync_outcomes.slice(0, current.sync_outcomes.length);
1238
+ if (JSON.stringify(prefix) !== JSON.stringify(current.sync_outcomes)) {
1239
+ throw new Error("ticket sync outcome history is immutable");
1240
+ }
1241
+ const added = next.sync_outcomes.length - current.sync_outcomes.length;
1242
+ if (added < 0 || added > 1)
1243
+ throw new Error("ticket transition changed sync outcome history illegally");
1244
+ if (authorityRepairMutation) {
1245
+ if (added !== 0)
1246
+ throw new Error("ticket authority repair cannot append sync outcomes");
1247
+ if (!sameSourceMetadata(current, next))
1248
+ throw new Error("ticket authority repair cannot modify source metadata");
1249
+ validateAuthorityRepairTransition(currentRecord, nextRecord, current, next, systemActors, evidenceStore);
1250
+ return;
1251
+ }
1252
+ if (lifecycleMutation) {
1253
+ if (added !== 0)
1254
+ throw new Error("ticket lifecycle transition cannot append sync outcomes");
1255
+ if (!sameSourceMetadata(current, next))
1256
+ throw new Error("ticket lifecycle transition cannot modify source metadata");
1257
+ validateLifecycleTransition(currentRecord, nextRecord, current, next, systemActors, evidenceStore);
1258
+ return;
1259
+ }
1260
+ if (currentRecord.state !== nextRecord.state
1261
+ || currentRecord.attempt !== nextRecord.attempt
1262
+ || currentRecord.actor !== nextRecord.actor
1263
+ || currentRecord.lease_token !== nextRecord.lease_token
1264
+ || !sameLifecycleFields(current, next)) {
1265
+ throw new Error("ticket sync must preserve lifecycle authority");
1266
+ }
1267
+ if (added === 0) {
1268
+ if (!sameSourceMetadata(current, next))
1269
+ throw new Error("ticket source metadata requires a sync transition");
1270
+ return;
1271
+ }
1272
+ const outcome = next.sync_outcomes.at(-1);
1273
+ if (!outcome || outcome.nonce !== nextRecord.transition_nonce || outcome.record_version !== nextRecord.version) {
1274
+ throw new Error("ticket sync outcome does not match its runtime transition");
1275
+ }
1276
+ if (outcome.status === "created")
1277
+ throw new Error("created sync outcomes are only valid on record creation");
1278
+ if (outcome.status === "unchanged" && !sameSourceMetadata(current, next)) {
1279
+ throw new Error("unchanged sync outcome modified source metadata");
1280
+ }
1281
+ if (outcome.status === "refreshed") {
1282
+ if (currentRecord.state !== "pending" || nextRecord.state !== "pending") {
1283
+ throw new Error("ticket refresh transition requires pending state");
1284
+ }
1285
+ if (timestampEpoch(next.source_updated_at, "next source timestamp") <= timestampEpoch(current.source_updated_at, "current source timestamp")) {
1286
+ throw new Error("ticket refresh timestamp must advance");
1287
+ }
1288
+ }
1289
+ }
1290
+ function validateAuthorityRepairTransition(currentRecord, nextRecord, current, next, systemActors, evidenceStore) {
1291
+ const from = currentRecord.state;
1292
+ if (from === "pending" && nextRecord.state === "pending" && current.gate_digest === null) {
1293
+ if (current.verification_plan_ref !== null
1294
+ || current.worktree !== null
1295
+ || next.gate_digest !== null
1296
+ || next.verification_plan_ref !== null
1297
+ || next.worktree !== null
1298
+ || nextRecord.attempt !== currentRecord.attempt
1299
+ || nextRecord.lease_token !== null
1300
+ || !systemActors.has(nextRecord.actor)) {
1301
+ throw new Error("unprepared ticket authority rebind changed lifecycle ownership");
1302
+ }
1303
+ if (current.policy_version === next.policy_version
1304
+ && current.policy_digest === next.policy_digest) {
1305
+ throw new Error("unprepared ticket authority rebind must change its policy identity");
1306
+ }
1307
+ if (assertTimestamp(next.lifecycle_updated_at, "authority rebind timestamp") <= assertTimestamp(current.lifecycle_updated_at, "current lifecycle timestamp")) {
1308
+ throw new Error("unprepared ticket authority rebind timestamp must advance");
1309
+ }
1310
+ const expected = {
1311
+ ...current,
1312
+ lifecycle_updated_at: next.lifecycle_updated_at,
1313
+ policy_version: next.policy_version,
1314
+ policy_digest: next.policy_digest,
1315
+ last_error: "unprepared ticket authority rebound to current policy",
1316
+ };
1317
+ if (JSON.stringify(next) !== JSON.stringify(expected)) {
1318
+ throw new Error("unprepared ticket authority rebind changed fields outside its projection");
1319
+ }
1320
+ return;
1321
+ }
1322
+ if (isTerminalTicketState(from) || nextRecord.state !== "blocked") {
1323
+ throw new Error(`ticket authority quarantine is not valid from ${from}`);
1324
+ }
1325
+ if (current.gate_digest === null || current.verification_plan_ref === null || current.worktree === null) {
1326
+ throw new Error("only a prepared ticket can be quarantined for stale authority");
1327
+ }
1328
+ if (nextRecord.attempt !== currentRecord.attempt
1329
+ || nextRecord.lease_token !== null
1330
+ || !systemActors.has(nextRecord.actor)) {
1331
+ throw new Error("ticket authority quarantine must release ownership to a system actor");
1332
+ }
1333
+ if (current.policy_version !== next.policy_version || current.policy_digest !== next.policy_digest) {
1334
+ throw new Error("ticket authority quarantine cannot rewrite policy history");
1335
+ }
1336
+ if (assertTimestamp(next.lifecycle_updated_at, "quarantine timestamp") <= assertTimestamp(current.lifecycle_updated_at, "current lifecycle timestamp")) {
1337
+ throw new Error("ticket authority quarantine timestamp must advance");
1338
+ }
1339
+ if (next.last_error === null)
1340
+ throw new Error("ticket authority quarantine requires a reason");
1341
+ assertBoundedText(next.last_error, "quarantine reason", 2048);
1342
+ const expected = {
1343
+ ...current,
1344
+ lifecycle_updated_at: next.lifecycle_updated_at,
1345
+ lease_expires_at: null,
1346
+ resume_at: null,
1347
+ resume_state: null,
1348
+ park_reason: null,
1349
+ resume_gate: null,
1350
+ session_id: null,
1351
+ last_error: next.last_error,
1352
+ executor_evidence: null,
1353
+ verification_evidence: null,
1354
+ grade_evidence: null,
1355
+ };
1356
+ if (JSON.stringify(next) !== JSON.stringify(expected)) {
1357
+ throw new Error("ticket authority quarantine changed fields outside its fail-closed projection");
1358
+ }
1359
+ resolveLifecycleEvidence(next, nextRecord, evidenceStore);
1360
+ }
1361
+ const LEGAL_TICKET_EDGES = {
1362
+ pending: ["pending", "leased", "parked"],
1363
+ leased: ["leased", "running", "retryable", "blocked", "parked"],
1364
+ running: ["running", "verifying", "retryable", "blocked", "parked"],
1365
+ verifying: ["verifying", "completed", "retryable", "blocked", "parked"],
1366
+ retryable: ["retryable", "leased", "parked"],
1367
+ parked: ["parked", "pending", "retryable"],
1368
+ blocked: [],
1369
+ completed: [],
1370
+ };
1371
+ function validateLifecycleTransition(currentRecord, nextRecord, current, next, systemActors, evidenceStore) {
1372
+ const from = currentRecord.state;
1373
+ const to = nextRecord.state;
1374
+ if (!LEGAL_TICKET_EDGES[from].includes(to))
1375
+ throw new Error(`illegal ticket lifecycle transition: ${from} -> ${to}`);
1376
+ if (current.policy_version !== next.policy_version || current.policy_digest !== next.policy_digest) {
1377
+ throw new Error("ticket policy identity is immutable");
1378
+ }
1379
+ const retryLease = from === "retryable" && to === "leased";
1380
+ const expectedAttempt = retryLease ? currentRecord.attempt + 1 : currentRecord.attempt;
1381
+ if (nextRecord.attempt !== expectedAttempt)
1382
+ throw new Error("ticket lifecycle attempt changed illegally");
1383
+ if (to === "retryable" && isActiveTicketState(from) && currentRecord.attempt >= MAX_QUEUE_ATTEMPTS) {
1384
+ throw new Error("attempt three must become blocked");
1385
+ }
1386
+ const explicitVerificationBlock = from === "verifying" && to === "blocked" && systemActors.has(nextRecord.actor);
1387
+ const systemRecoveryBlock = to === "blocked"
1388
+ && isActiveTicketState(from)
1389
+ && systemActors.has(nextRecord.actor)
1390
+ && next.prior_failure_evidence !== null
1391
+ && !sameNullableEvidenceReference(current.prior_failure_evidence, next.prior_failure_evidence);
1392
+ if (to === "blocked"
1393
+ && isActiveTicketState(from)
1394
+ && currentRecord.attempt < MAX_QUEUE_ATTEMPTS
1395
+ && !explicitVerificationBlock
1396
+ && !systemRecoveryBlock) {
1397
+ throw new Error("attempts one and two must remain retryable");
1398
+ }
1399
+ const acquiringLease = (from === "pending" || from === "retryable") && to === "leased";
1400
+ const parking = to === "parked" && from !== "parked";
1401
+ const failedOrRecovered = isActiveTicketState(from) && (to === "retryable" || to === "blocked");
1402
+ const completing = from === "verifying" && to === "completed";
1403
+ const attachingRetryReflection = from === "retryable"
1404
+ && to === "retryable"
1405
+ && systemActors.has(nextRecord.actor)
1406
+ && current.prior_reflection_evidence === null
1407
+ && next.prior_reflection_evidence !== null
1408
+ && sameNullableEvidenceReference(current.prior_failure_evidence, next.prior_failure_evidence);
1409
+ if (acquiringLease) {
1410
+ if (currentRecord.lease_token !== null || nextRecord.lease_token === null) {
1411
+ throw new Error("ticket lease acquisition requires a new lease token");
1412
+ }
1413
+ }
1414
+ else if (parking) {
1415
+ if (nextRecord.lease_token !== null || !systemActors.has(nextRecord.actor)) {
1416
+ throw new Error("ticket park must release ownership to an allowed system actor");
1417
+ }
1418
+ }
1419
+ else if (failedOrRecovered) {
1420
+ if (nextRecord.lease_token !== null || (nextRecord.actor !== currentRecord.actor && !systemActors.has(nextRecord.actor))) {
1421
+ throw new Error("ticket failure or recovery must release the active lease");
1422
+ }
1423
+ }
1424
+ else if (completing) {
1425
+ if (nextRecord.lease_token !== null || nextRecord.actor !== currentRecord.actor) {
1426
+ throw new Error("ticket completion must release the current worker lease");
1427
+ }
1428
+ }
1429
+ else if (attachingRetryReflection) {
1430
+ if (nextRecord.lease_token !== null)
1431
+ throw new Error("retry reflection cannot acquire a lease");
1432
+ }
1433
+ else if (nextRecord.actor !== currentRecord.actor
1434
+ || nextRecord.lease_token !== currentRecord.lease_token) {
1435
+ throw new Error("ticket lifecycle actor or lease changed illegally");
1436
+ }
1437
+ if (isActiveTicketState(to) && nextRecord.lease_token === null)
1438
+ throw new Error("active ticket transition requires a lease");
1439
+ if (to === "parked") {
1440
+ const expectedResumeState = from === "retryable" ? "retryable" : "pending";
1441
+ if (next.resume_state !== expectedResumeState)
1442
+ throw new Error(`parked ticket must resume to ${expectedResumeState}`);
1443
+ }
1444
+ if (from === "parked" && to !== "parked" && to !== current.resume_state) {
1445
+ throw new Error("parked ticket resumed to a state other than its durable target");
1446
+ }
1447
+ if (JSON.stringify(lifecycleFields(current)) === JSON.stringify(lifecycleFields(next))) {
1448
+ throw new Error("ticket lifecycle transition must update lifecycle fields");
1449
+ }
1450
+ if (assertTimestamp(next.lifecycle_updated_at, "next lifecycle timestamp") <= assertTimestamp(current.lifecycle_updated_at, "current lifecycle timestamp")) {
1451
+ throw new Error("ticket lifecycle timestamp must advance");
1452
+ }
1453
+ validateMonotonicField("verification_plan_ref", current.verification_plan_ref, next.verification_plan_ref);
1454
+ validateMonotonicField("worktree", current.worktree, next.worktree);
1455
+ validateMonotonicField("gate_digest", current.gate_digest, next.gate_digest);
1456
+ const activePark = isActiveTicketState(from) && to === "parked";
1457
+ if (retryLease || activePark) {
1458
+ if (next.executor_evidence !== null || next.verification_evidence !== null || next.grade_evidence !== null) {
1459
+ throw new Error(`ticket ${activePark ? "active park" : "retry lease"} must clear current-attempt evidence`);
1460
+ }
1461
+ }
1462
+ else {
1463
+ validateMonotonicField("executor_evidence", current.executor_evidence, next.executor_evidence);
1464
+ validateMonotonicField("verification_evidence", current.verification_evidence, next.verification_evidence);
1465
+ validateMonotonicField("grade_evidence", current.grade_evidence, next.grade_evidence);
1466
+ }
1467
+ const workerFailedAttempt = failedOrRecovered && nextRecord.actor === currentRecord.actor;
1468
+ const systemRecoveredAttempt = failedOrRecovered
1469
+ && systemActors.has(nextRecord.actor)
1470
+ && nextRecord.actor !== currentRecord.actor
1471
+ && next.prior_failure_evidence !== null
1472
+ && !sameNullableEvidenceReference(current.prior_failure_evidence, next.prior_failure_evidence);
1473
+ if (!workerFailedAttempt && !systemRecoveredAttempt && !attachingRetryReflection && (!sameNullableEvidenceReference(current.prior_failure_evidence, next.prior_failure_evidence)
1474
+ || !sameNullableEvidenceReference(current.prior_reflection_evidence, next.prior_reflection_evidence))) {
1475
+ throw new Error("ticket retry lineage can change only when an active attempt fails");
1476
+ }
1477
+ if ((workerFailedAttempt || systemRecoveredAttempt) && next.prior_failure_evidence === null) {
1478
+ throw new Error("failed ticket attempt requires prior failure evidence");
1479
+ }
1480
+ validateAttachmentPhase("verification_plan_ref", current.verification_plan_ref, next.verification_plan_ref, from, ["pending"]);
1481
+ validateAttachmentPhase("worktree", current.worktree, next.worktree, from, ["pending"]);
1482
+ validateAttachmentPhase("gate_digest", current.gate_digest, next.gate_digest, from, ["pending"]);
1483
+ validateAttachmentPhase("executor_evidence", current.executor_evidence, next.executor_evidence, from, ["running"]);
1484
+ validateAttachmentPhase("verification_evidence", current.verification_evidence, next.verification_evidence, from, ["verifying"]);
1485
+ validateAttachmentPhase("grade_evidence", current.grade_evidence, next.grade_evidence, from, ["verifying"]);
1486
+ const resolved = resolveLifecycleEvidence(next, nextRecord, evidenceStore);
1487
+ if ((workerFailedAttempt || systemRecoveredAttempt) && resolved.priorFailure) {
1488
+ const failedAttempt = priorFailureAttempt(resolved.priorFailure, evidenceStore, nextRecord.record_id);
1489
+ if (failedAttempt !== currentRecord.attempt) {
1490
+ throw new Error("ticket prior failure does not belong to the active failed attempt");
1491
+ }
1492
+ }
1493
+ if (retryLease && resolved.priorFailure) {
1494
+ const failedAttempt = priorFailureAttempt(resolved.priorFailure, evidenceStore, nextRecord.record_id);
1495
+ if (failedAttempt !== nextRecord.attempt - 1) {
1496
+ throw new Error("retry lease lineage does not belong to the immediately prior attempt");
1497
+ }
1498
+ }
1499
+ if (to === "leased" && (next.verification_plan_ref === null || next.worktree === null)) {
1500
+ throw new Error("ticket lease requires a verification plan and worktree");
1501
+ }
1502
+ if (["verifying", "completed"].includes(to) && next.executor_evidence === null) {
1503
+ throw new Error("ticket verification requires executor evidence");
1504
+ }
1505
+ const workerReleasedAttempt = nextRecord.actor === currentRecord.actor;
1506
+ if (from === "running" && ["verifying", "retryable", "blocked"].includes(to) && workerReleasedAttempt && next.executor_evidence === null) {
1507
+ throw new Error(`ticket ${to} transition requires executor evidence`);
1508
+ }
1509
+ if (from === "verifying" && ["completed", "retryable", "blocked"].includes(to) && workerReleasedAttempt && next.verification_evidence === null) {
1510
+ throw new Error(`ticket ${to} transition requires verification evidence`);
1511
+ }
1512
+ if (to === "completed" && (next.verification_evidence === null || next.grade_evidence === null)) {
1513
+ throw new Error("ticket completion requires verification and grade evidence");
1514
+ }
1515
+ if (next.verification_evidence !== null) {
1516
+ if (!resolved.plan || !resolved.verification)
1517
+ throw new Error("verification evidence requires its verification plan");
1518
+ if (resolved.verification.payload.plan_digest !== resolved.plan.payload.digest) {
1519
+ throw new Error("verification evidence plan digest does not match the ticket plan");
1520
+ }
1521
+ }
1522
+ }
1523
+ function validateStateInvariants(record, payload) {
1524
+ const state = record.state;
1525
+ assertTimestamp(payload.lifecycle_updated_at, "lifecycle timestamp");
1526
+ if (payload.lease_expires_at !== null)
1527
+ assertTimestamp(payload.lease_expires_at, "lease expiry");
1528
+ if (payload.resume_at !== null)
1529
+ assertTimestamp(payload.resume_at, "resume timestamp");
1530
+ if (payload.session_id !== null)
1531
+ assertOpaqueId(payload.session_id, "session id");
1532
+ validateResumeGate(payload.resume_gate);
1533
+ if ((payload.verification_plan_ref === null) !== (payload.worktree === null)) {
1534
+ throw new Error("ticket verification plan and worktree must be prepared together");
1535
+ }
1536
+ if ((payload.verification_plan_ref === null) !== (payload.gate_digest === null)) {
1537
+ throw new Error("ticket verification plan and gate digest must be prepared together");
1538
+ }
1539
+ if (isActiveTicketState(state)) {
1540
+ if (record.lease_token === null || payload.lease_expires_at === null || payload.session_id === null) {
1541
+ throw new Error(`ticket state ${state} requires lease and session ownership`);
1542
+ }
1543
+ if (payload.resume_at !== null || payload.resume_state !== null || payload.park_reason !== null || payload.resume_gate !== null) {
1544
+ throw new Error("active ticket must not retain parked state");
1545
+ }
1546
+ }
1547
+ else if (isIdleTicketState(state)) {
1548
+ if (record.lease_token !== null
1549
+ || payload.lease_expires_at !== null
1550
+ || payload.resume_at !== null
1551
+ || payload.resume_state !== null
1552
+ || payload.park_reason !== null
1553
+ || payload.resume_gate !== null
1554
+ || payload.session_id !== null) {
1555
+ throw new Error("idle ticket must not retain lease, session, or resume state");
1556
+ }
1557
+ }
1558
+ else if (state === "parked") {
1559
+ if (record.lease_token !== null
1560
+ || payload.lease_expires_at !== null
1561
+ || payload.resume_at === null
1562
+ || payload.resume_state === null
1563
+ || payload.park_reason === null
1564
+ || payload.resume_gate === null
1565
+ || payload.session_id !== null) {
1566
+ throw new Error("parked ticket must retain only its resume schedule");
1567
+ }
1568
+ }
1569
+ else if (isTerminalTicketState(state) && (record.lease_token !== null
1570
+ || payload.lease_expires_at !== null
1571
+ || payload.resume_at !== null
1572
+ || payload.resume_state !== null
1573
+ || payload.park_reason !== null
1574
+ || payload.resume_gate !== null
1575
+ || payload.session_id !== null)) {
1576
+ throw new Error("terminal ticket must not retain lease, session, or resume state");
1577
+ }
1578
+ }
1579
+ function validateResumeGate(value) {
1580
+ if (value === null)
1581
+ return;
1582
+ if (value.kind === "time") {
1583
+ if (value.role_id !== null
1584
+ || value.engine !== null
1585
+ || value.session_id !== null
1586
+ || value.limited_session_version !== null
1587
+ || value.limited_observation_id !== null
1588
+ || value.limited_observed_at !== null) {
1589
+ throw new Error("time resume gate must not carry quota identity");
1590
+ }
1591
+ return;
1592
+ }
1593
+ if (value.kind !== "quota")
1594
+ throw new Error("invalid ticket resume gate kind");
1595
+ if (!ROLE_IDS.includes(value.role_id))
1596
+ throw new Error("invalid quota resume role id");
1597
+ if (value.engine !== "claude" && value.engine !== "codex")
1598
+ throw new Error("invalid quota resume engine");
1599
+ assertOpaqueId(value.session_id, "quota resume session id");
1600
+ if (!Number.isSafeInteger(value.limited_session_version) || value.limited_session_version < 0) {
1601
+ throw new Error("invalid limited quota session version");
1602
+ }
1603
+ if (!/^quota-[a-f0-9]{48}$/.test(value.limited_observation_id)) {
1604
+ throw new Error("invalid limited quota observation id");
1605
+ }
1606
+ assertTimestamp(value.limited_observed_at, "limited quota observation timestamp");
1607
+ }
1608
+ function validateMonotonicField(label, current, next) {
1609
+ if (current !== null && JSON.stringify(current) !== JSON.stringify(next)) {
1610
+ throw new Error(`${label} is immutable once established`);
1611
+ }
1612
+ }
1613
+ function validateAttachmentPhase(label, current, next, from, allowedFrom) {
1614
+ if (current === null && next !== null && !allowedFrom.includes(from)) {
1615
+ throw new Error(`${label} cannot be attached during ${from}`);
1616
+ }
1617
+ }
1618
+ function resolveLifecycleEvidence(payload, record, evidenceStore) {
1619
+ const ticketId = record.record_id;
1620
+ const resolved = {};
1621
+ resolved.plan = resolveEvidence(payload.verification_plan_ref, ["verification-plan"], ticketId, evidenceStore);
1622
+ resolved.executor = resolveEvidence(payload.executor_evidence, ["executor-result", "goal-executor-result"], ticketId, evidenceStore);
1623
+ resolved.verification = resolveEvidence(payload.verification_evidence, ["deterministic-check"], ticketId, evidenceStore);
1624
+ resolved.grade = resolveEvidence(payload.grade_evidence, ["grader-output"], ticketId, evidenceStore);
1625
+ resolved.priorFailure = resolveEvidence(payload.prior_failure_evidence, ["executor-result", "goal-executor-result", "lease-expiry", "deterministic-check", "grader-output"], ticketId, evidenceStore);
1626
+ resolved.priorReflection = resolveEvidence(payload.prior_reflection_evidence, ["reflection"], ticketId, evidenceStore);
1627
+ if (resolved.plan && payload.gate_digest !== resolved.plan.payload.digest) {
1628
+ throw new Error("ticket gate digest does not match its prepared verification plan");
1629
+ }
1630
+ if (resolved.executor)
1631
+ validateAttemptLeaseBinding(resolved.executor, record, "executor");
1632
+ if (resolved.verification)
1633
+ validateAttemptLeaseBinding(resolved.verification, record, "verification");
1634
+ if (resolved.verification && resolved.plan && resolved.verification.payload.plan_digest !== resolved.plan.payload.digest) {
1635
+ throw new Error("verification evidence plan digest does not match the ticket plan");
1636
+ }
1637
+ if (resolved.verification)
1638
+ validateVerificationPolicyBinding(resolved.verification, payload);
1639
+ if (resolved.grade) {
1640
+ if (!resolved.executor)
1641
+ throw new Error("grader evidence requires executor evidence");
1642
+ if (!payload.verification_evidence)
1643
+ throw new Error("grader evidence requires deterministic verification evidence");
1644
+ if (!sameEvidenceReference(resolved.grade.payload.deterministic_evidence, payload.verification_evidence)) {
1645
+ throw new Error("grader evidence is not bound to the ticket deterministic check");
1646
+ }
1647
+ const expectedProducer = resolved.executor.evidence_kind === "executor-result"
1648
+ ? "oms.grader.codex"
1649
+ : "oms.grader.claude";
1650
+ if (resolved.grade.producer_role !== expectedProducer) {
1651
+ throw new Error("cross-engine grader mismatch for executor evidence");
1652
+ }
1653
+ }
1654
+ if (resolved.priorReflection && payload.prior_failure_evidence === null) {
1655
+ throw new Error("ticket prior reflection requires prior failure evidence");
1656
+ }
1657
+ if (resolved.priorReflection && resolved.priorFailure && payload.prior_failure_evidence) {
1658
+ const reflectedFailure = resolved.priorReflection.payload.failure_evidence;
1659
+ if (!sameEvidenceReference(reflectedFailure, payload.prior_failure_evidence)) {
1660
+ throw new Error("ticket prior reflection does not reference the exact prior failure");
1661
+ }
1662
+ const failureAttempt = priorFailureAttempt(resolved.priorFailure, evidenceStore, ticketId);
1663
+ if (resolved.priorReflection.payload.attempt !== failureAttempt) {
1664
+ throw new Error("ticket prior reflection attempt does not match the prior failure");
1665
+ }
1666
+ }
1667
+ return resolved;
1668
+ }
1669
+ function priorFailureAttempt(failure, evidenceStore, ticketId) {
1670
+ if (failure.evidence_kind !== "grader-output")
1671
+ return failure.payload.attempt;
1672
+ if (!evidenceStore)
1673
+ throw new Error("grader failure lineage requires the ticket evidence store");
1674
+ const deterministic = resolveEvidence(failure.payload.deterministic_evidence, ["deterministic-check"], ticketId, evidenceStore);
1675
+ if (!deterministic)
1676
+ throw new Error("grader failure lineage lacks deterministic evidence");
1677
+ return deterministic.payload.attempt;
1678
+ }
1679
+ function activeAttemptStartCommit(record, evidenceStore) {
1680
+ const worktree = record.payload.worktree;
1681
+ if (worktree === null)
1682
+ throw new Error("active ticket lacks its worktree authority");
1683
+ if (record.attempt === 1)
1684
+ return worktree.base_commit;
1685
+ const reference = record.payload.prior_failure_evidence;
1686
+ if (reference === null)
1687
+ throw new Error("retry attempt lacks prior failure authority");
1688
+ const failure = evidenceStore.read(reference);
1689
+ let commit;
1690
+ if (failure.evidence_kind === "executor-result" || failure.evidence_kind === "goal-executor-result") {
1691
+ commit = failure.payload.worktree_commit;
1692
+ }
1693
+ else if (failure.evidence_kind === "lease-expiry") {
1694
+ commit = failure.payload.worktree_start_commit;
1695
+ }
1696
+ else {
1697
+ const deterministicReference = failure.evidence_kind === "deterministic-check"
1698
+ ? reference
1699
+ : failure.evidence_kind === "grader-output"
1700
+ ? failure.payload.deterministic_evidence
1701
+ : null;
1702
+ if (deterministicReference === null)
1703
+ throw new Error("retry failure cannot establish its active worktree start");
1704
+ const deterministic = evidenceStore.read(deterministicReference);
1705
+ const binding = deterministic.payload.binding;
1706
+ commit = isRecord(binding) ? binding.expected_commit : null;
1707
+ }
1708
+ if (typeof commit !== "string" || !COMMIT_PATTERN.test(commit)) {
1709
+ throw new Error("retry attempt lacks a trusted worktree start commit");
1710
+ }
1711
+ return commit;
1712
+ }
1713
+ function validateAttemptLeaseBinding(evidence, record, label) {
1714
+ if (evidence.payload.attempt !== record.attempt) {
1715
+ throw new Error(`${label} evidence attempt does not match the ticket attempt`);
1716
+ }
1717
+ const leaseToken = authoritativeLeaseToken(record);
1718
+ if (evidence.payload.lease_token !== leaseToken) {
1719
+ throw new Error(`${label} evidence lease token does not match the ticket attempt lease`);
1720
+ }
1721
+ }
1722
+ function authoritativeLeaseToken(record) {
1723
+ for (let index = record.transition_outcomes.length - 1; index >= 0; index -= 1) {
1724
+ const outcome = record.transition_outcomes[index];
1725
+ if (outcome?.attempt === record.attempt && outcome.state === "leased" && outcome.lease_token !== null) {
1726
+ return outcome.lease_token;
1727
+ }
1728
+ }
1729
+ throw new Error(`ticket attempt ${record.attempt} has no authoritative lease token`);
1730
+ }
1731
+ function resolveEvidence(reference, expectedKinds, ticketId, evidenceStore) {
1732
+ if (reference === null)
1733
+ return undefined;
1734
+ if (!evidenceStore)
1735
+ throw new Error("ticket evidence store is required before attaching lifecycle references");
1736
+ assertEvidenceStore(evidenceStore);
1737
+ const evidence = evidenceStore.read(reference);
1738
+ if (!expectedKinds.includes(evidence.evidence_kind)) {
1739
+ throw new Error(`ticket evidence has wrong kind: expected ${expectedKinds.join(" or ")}`);
1740
+ }
1741
+ if (evidence.ticket_id !== ticketId)
1742
+ throw new Error("ticket evidence belongs to a different ticket");
1743
+ return evidence;
1744
+ }
1745
+ function retryFailureRequiresReflection(record, evidenceStore) {
1746
+ const reference = record.payload.prior_failure_evidence;
1747
+ if (reference === null || evidenceStore === undefined)
1748
+ return false;
1749
+ const evidence = evidenceStore.read(reference);
1750
+ return evidence.evidence_kind === "deterministic-check" || evidence.evidence_kind === "grader-output";
1751
+ }
1752
+ function executorSucceeded(evidence) {
1753
+ return evidence.evidence_kind === "executor-result"
1754
+ ? evidence.payload.status === "done"
1755
+ : evidence.payload.status === "complete";
1756
+ }
1757
+ function validateVerificationPolicyBinding(evidence, payload) {
1758
+ if (evidence.payload.policy_version !== payload.policy_version
1759
+ || evidence.payload.policy_digest !== payload.policy_digest
1760
+ || evidence.payload.plan_digest !== payload.gate_digest) {
1761
+ throw new Error("deterministic verification evidence does not match the ticket policy identity");
1762
+ }
1763
+ }
1764
+ function sameEvidenceReference(left, right) {
1765
+ return left.evidence_id === right.evidence_id && left.sha256 === right.sha256 && left.file_name === right.file_name;
1766
+ }
1767
+ function sameNullableEvidenceReference(left, right) {
1768
+ return left === null ? right === null : right !== null && sameEvidenceReference(left, right);
1769
+ }
1770
+ function validateTicketContent(value, schema) {
1771
+ assertSchema(value, schema, "ticket content");
1772
+ const content = value;
1773
+ assertTicketRecordId(content.ticket_id);
1774
+ timestampEpoch(content.updated_at, "ticket content timestamp");
1775
+ if (content.source === "github") {
1776
+ if (content.repository === null || content.issue_node_id === null || content.url === null) {
1777
+ throw new Error("GitHub ticket content is missing source metadata");
1778
+ }
1779
+ assertGitHubRepository(content.repository);
1780
+ const identity = deriveGitHubTicketIdentity({ issue_node_id: content.issue_node_id });
1781
+ if (identity.ticket_id !== content.ticket_id)
1782
+ throw new Error("GitHub ticket content has the wrong ticket id");
1783
+ }
1784
+ else if (content.repository !== null || content.issue_node_id !== null) {
1785
+ throw new Error("local ticket content must not contain GitHub source metadata");
1786
+ }
1787
+ return content;
1788
+ }
1789
+ function validateTicketQueuePayload(value, schema) {
1790
+ assertSchema(value, schema, "ticket queue payload");
1791
+ const payload = value;
1792
+ assertTicketRecordId(payload.ticket_id);
1793
+ validateSha256(payload.source_ref_sha256, "queue source digest");
1794
+ validateSha256(payload.content_sha256, "queue content digest");
1795
+ timestampEpoch(payload.source_updated_at, "queue source timestamp");
1796
+ validatePolicyIdentity(payload, "ticket queue policy identity");
1797
+ if (payload.gate_digest !== null)
1798
+ validateSha256(payload.gate_digest, "ticket gate digest");
1799
+ if (payload.content_file !== `${payload.ticket_id}.${payload.content_sha256}.json`) {
1800
+ throw new Error("ticket queue content filename does not match its id and digest");
1801
+ }
1802
+ validateEvidenceRef(payload.verification_plan_ref, "verification plan reference");
1803
+ validateEvidenceRef(payload.executor_evidence, "executor evidence reference");
1804
+ validateEvidenceRef(payload.verification_evidence, "verification evidence reference");
1805
+ validateEvidenceRef(payload.grade_evidence, "grade evidence reference");
1806
+ if (payload.worktree !== null) {
1807
+ if (!isSafeBranch(payload.worktree.branch))
1808
+ throw new Error("invalid ticket worktree branch");
1809
+ if (!COMMIT_PATTERN.test(payload.worktree.base_commit))
1810
+ throw new Error("invalid ticket worktree base commit");
1811
+ }
1812
+ const seenNonces = new Set();
1813
+ let previousVersion = -1;
1814
+ for (const outcome of payload.sync_outcomes) {
1815
+ assertTransitionNonce(outcome.nonce, "ticket sync nonce");
1816
+ validateSha256(outcome.operation_sha256, "ticket sync operation fingerprint");
1817
+ if (outcome.nonce !== `sync-${outcome.operation_sha256.slice(0, 59)}`) {
1818
+ throw new Error("ticket sync nonce does not match its operation fingerprint");
1819
+ }
1820
+ validateSha256(outcome.content_sha256, "ticket sync content digest");
1821
+ if (outcome.content_file !== `${payload.ticket_id}.${outcome.content_sha256}.json`) {
1822
+ throw new Error("ticket sync outcome content filename is invalid");
1823
+ }
1824
+ if (!Number.isSafeInteger(outcome.record_version) || outcome.record_version < 0 || outcome.record_version <= previousVersion) {
1825
+ throw new Error("ticket sync outcome versions must increase");
1826
+ }
1827
+ if (seenNonces.has(outcome.nonce))
1828
+ throw new Error("ticket sync outcome nonces must be unique");
1829
+ seenNonces.add(outcome.nonce);
1830
+ previousVersion = outcome.record_version;
1831
+ }
1832
+ return payload;
1833
+ }
1834
+ function validateEvidenceRef(ref, label) {
1835
+ if (ref === null)
1836
+ return;
1837
+ const evidenceId = assertOpaqueId(ref.evidence_id, label);
1838
+ validateSha256(ref.sha256, label);
1839
+ if (ref.file_name !== `${evidenceId}.json`)
1840
+ throw new Error(`invalid ${label} filename`);
1841
+ }
1842
+ function sourceRefForContent(content) {
1843
+ if (content.source === "github") {
1844
+ if (content.issue_node_id === null)
1845
+ throw new Error("GitHub source metadata is missing");
1846
+ return deriveGitHubTicketIdentity({ issue_node_id: content.issue_node_id }).source_ref_sha256;
1847
+ }
1848
+ return sha256(Buffer.from(`local\0${content.ticket_id}`, "utf8"));
1849
+ }
1850
+ function validateTicketRef(ref) {
1851
+ const ticketId = assertTicketRecordId(ref.ticket_id);
1852
+ validateSha256(ref.sha256, "ticket content digest");
1853
+ const expected = `${ticketId}.${ref.sha256}.json`;
1854
+ if (ref.file_name !== expected)
1855
+ throw new Error("ticket content filename does not match its id and digest");
1856
+ }
1857
+ function assertTicketRecordId(value) {
1858
+ const ticketId = assertRecordId(value, "ticket id");
1859
+ recordLockId(ticketId);
1860
+ assertLockId(`sync-${ticketId}`);
1861
+ return ticketId;
1862
+ }
1863
+ function assertGitHubRepository(value) {
1864
+ if (typeof value !== "string" || value.length > 201)
1865
+ throw new Error("invalid GitHub repository");
1866
+ const segments = value.split("/");
1867
+ const owner = segments[0];
1868
+ const name = segments[1];
1869
+ if (segments.length !== 2
1870
+ || !owner
1871
+ || !name
1872
+ || !/^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/.test(owner)
1873
+ || !/^[A-Za-z0-9._-]{1,100}$/.test(name)
1874
+ || name === "."
1875
+ || name === "..") {
1876
+ throw new Error("invalid GitHub repository");
1877
+ }
1878
+ return value;
1879
+ }
1880
+ function assertGitHubNodeId(value) {
1881
+ if (typeof value !== "string" || !/^[A-Za-z0-9_=-]{1,512}$/.test(value))
1882
+ throw new Error("invalid GitHub issue node id");
1883
+ return value;
1884
+ }
1885
+ function normalizeLabels(value) {
1886
+ if (!Array.isArray(value) || value.length > 100)
1887
+ throw new Error("invalid ticket labels");
1888
+ const labels = new Set();
1889
+ for (const label of value) {
1890
+ if (typeof label !== "string" || label.length < 1 || label.length > 100)
1891
+ throw new Error("invalid ticket label");
1892
+ labels.add(label);
1893
+ }
1894
+ return [...labels].sort();
1895
+ }
1896
+ function validateSha256(value, label) {
1897
+ if (!SHA256_PATTERN.test(value))
1898
+ throw new Error(`invalid ${label}`);
1899
+ }
1900
+ function validatePolicyIdentity(value, label) {
1901
+ if (typeof value !== "object" || value === null)
1902
+ throw new Error(`invalid ${label}`);
1903
+ const candidate = value;
1904
+ if (!Number.isSafeInteger(candidate.policy_version) || candidate.policy_version < 1) {
1905
+ throw new Error(`invalid ${label} policy_version`);
1906
+ }
1907
+ if (typeof candidate.policy_digest !== "string")
1908
+ throw new Error(`invalid ${label} policy_digest`);
1909
+ validateSha256(candidate.policy_digest, `${label} policy_digest`);
1910
+ return {
1911
+ policy_version: candidate.policy_version,
1912
+ policy_digest: candidate.policy_digest,
1913
+ };
1914
+ }
1915
+ function policyIdentityFromPayload(payload) {
1916
+ return validatePolicyIdentity(payload, "stored ticket policy identity");
1917
+ }
1918
+ function timestampEpoch(value, label) {
1919
+ if (typeof value !== "string")
1920
+ throw new Error(`invalid ${label}`);
1921
+ const match = TIMESTAMP_PATTERN.exec(value);
1922
+ if (!match)
1923
+ throw new Error(`invalid ${label}`);
1924
+ const [, yearRaw, monthRaw, dayRaw, hourRaw, minuteRaw, secondRaw, fractionRaw = ""] = match;
1925
+ const year = Number(yearRaw);
1926
+ const month = Number(monthRaw);
1927
+ const day = Number(dayRaw);
1928
+ const hour = Number(hourRaw);
1929
+ const minute = Number(minuteRaw);
1930
+ const second = Number(secondRaw);
1931
+ const millisecond = Number(fractionRaw.padEnd(3, "0").slice(0, 3));
1932
+ const date = new Date(Date.UTC(year, month - 1, day, hour, minute, second, millisecond));
1933
+ if (date.getUTCFullYear() !== year
1934
+ || date.getUTCMonth() !== month - 1
1935
+ || date.getUTCDate() !== day
1936
+ || date.getUTCHours() !== hour
1937
+ || date.getUTCMinutes() !== minute
1938
+ || date.getUTCSeconds() !== second) {
1939
+ throw new Error(`invalid ${label}`);
1940
+ }
1941
+ return date.getTime();
1942
+ }
1943
+ function sameSourceMetadata(left, right) {
1944
+ return [
1945
+ "ticket_id",
1946
+ "source",
1947
+ "source_ref_sha256",
1948
+ "content_sha256",
1949
+ "content_file",
1950
+ "source_updated_at",
1951
+ "ready_intent",
1952
+ ].every((field) => JSON.stringify(left[field]) === JSON.stringify(right[field]));
1953
+ }
1954
+ function sameLifecycleFields(left, right) {
1955
+ return [
1956
+ "policy_version",
1957
+ "policy_digest",
1958
+ "gate_digest",
1959
+ "lifecycle_updated_at",
1960
+ "lease_expires_at",
1961
+ "resume_at",
1962
+ "resume_state",
1963
+ "park_reason",
1964
+ "resume_gate",
1965
+ "session_id",
1966
+ "last_error",
1967
+ "verification_plan_ref",
1968
+ "worktree",
1969
+ "executor_evidence",
1970
+ "verification_evidence",
1971
+ "grade_evidence",
1972
+ "merge_disposition",
1973
+ ].every((field) => JSON.stringify(left[field]) === JSON.stringify(right[field]));
1974
+ }
1975
+ function lifecycleFields(payload) {
1976
+ return {
1977
+ lifecycle_updated_at: payload.lifecycle_updated_at,
1978
+ lease_expires_at: payload.lease_expires_at,
1979
+ resume_at: payload.resume_at,
1980
+ resume_state: payload.resume_state,
1981
+ park_reason: payload.park_reason,
1982
+ resume_gate: payload.resume_gate,
1983
+ session_id: payload.session_id,
1984
+ last_error: payload.last_error,
1985
+ };
1986
+ }
1987
+ function isSafeBranch(value) {
1988
+ return typeof value === "string"
1989
+ && /^[A-Za-z0-9][A-Za-z0-9._/-]{0,254}$/.test(value)
1990
+ && !value.includes("..")
1991
+ && !value.includes("//")
1992
+ && !value.includes("@{")
1993
+ && !value.endsWith("/")
1994
+ && !value.endsWith(".")
1995
+ && !value.endsWith(".lock");
1996
+ }
1997
+ function canonicalJson(value) {
1998
+ return Buffer.from(`${JSON.stringify(value, null, 2)}\n`, "utf8");
1999
+ }
2000
+ function freezeSyncTicketResult(result) {
2001
+ return freezeJsonSnapshot(result);
2002
+ }
2003
+ function freezeJsonSnapshot(value) {
2004
+ return deepFreeze(structuredClone(value));
2005
+ }
2006
+ function deepFreeze(value, seen = new Set()) {
2007
+ if (typeof value !== "object" || value === null || seen.has(value))
2008
+ return value;
2009
+ seen.add(value);
2010
+ for (const child of Object.values(value))
2011
+ deepFreeze(child, seen);
2012
+ return Object.freeze(value);
2013
+ }
2014
+ function parseJson(bytes, label) {
2015
+ try {
2016
+ return JSON.parse(bytes.toString("utf8"));
2017
+ }
2018
+ catch (error) {
2019
+ throw new Error(`invalid JSON in ${label}: ${error instanceof Error ? error.message : String(error)}`);
2020
+ }
2021
+ }
2022
+ function sha256(bytes) {
2023
+ return createHash("sha256").update(bytes).digest("hex");
2024
+ }
2025
+ //# sourceMappingURL=store.js.map