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,1655 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { readdirSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { assertActorId, assertLockId, assertOpaqueId } from "../domain/records.js";
5
+ import { assertFileInside, openStableDirectory, readImmutableFile, writeImmutableFile, } from "../evidence/immutable-file.js";
6
+ import { assertRunCertificationAuthorityReference, assertRunCertificationAuthorityResolver, } from "../outbox/store.js";
7
+ import { findPackageRoot } from "../package-root.js";
8
+ import { acquireDirectoryLock, assertRecoveredLock } from "../runtime/lock.js";
9
+ import { currentProcessInstanceId } from "../runtime/process-identity.js";
10
+ import { assertSchema, loadJsonSchema } from "../runtime/schema.js";
11
+ import { createStableId } from "../runtime/stable-id.js";
12
+ import { systemBootId, systemClockSample } from "../runtime/system-clock.js";
13
+ import { isRecord } from "../utils/fs.js";
14
+ const MAX_ENTRY_BYTES = 1024 * 1024;
15
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/;
16
+ const RUN_ID_PATTERN = /^run-[a-f0-9]{48}$/;
17
+ const ENTRY_ID_PATTERN = /^audit-[a-f0-9]{48}$/;
18
+ const RUN_LEDGER = Symbol("OMS run ledger");
19
+ const RUN_LEDGERS = new WeakMap();
20
+ const AUDIT_RUN_TERMINAL_WRITER = Symbol("OMS audit run terminal writer");
21
+ const AUDIT_RUN_TERMINAL_WRITERS = new WeakMap();
22
+ const LIVE_RUN_WRITER = Symbol("OMS live run writer");
23
+ const LIVE_RUN_WRITERS = new WeakMap();
24
+ const RUN_CERTIFICATION_SNAPSHOT = Symbol("OMS run certification snapshot");
25
+ const RUN_CERTIFICATION_SNAPSHOTS = new WeakSet();
26
+ const TWELVE_HOURS_MS = 12 * 60 * 60 * 1000;
27
+ const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;
28
+ const MAX_RESTART_GAP_MS = 30 * 60 * 1000;
29
+ export class RunLedgerConflictError extends Error {
30
+ constructor(operationId) {
31
+ super(`run ledger operation conflicts with immutable content: ${operationId}`);
32
+ this.name = "RunLedgerConflictError";
33
+ }
34
+ }
35
+ export function createLiveRunLedger(options) {
36
+ const captured = {
37
+ ledgerRoot: options.ledgerRoot,
38
+ locksRoot: options.locksRoot,
39
+ packageRoot: options.packageRoot,
40
+ };
41
+ return createRunLedger(captured, {
42
+ clockClass: "live",
43
+ processId: process.pid,
44
+ processInstanceId: currentProcessInstanceId(),
45
+ bootId: systemBootId(),
46
+ sample: () => {
47
+ const sampled = systemClockSample();
48
+ return { wallTime: sampled.wallTime, monotonicNs: sampled.monotonicNs };
49
+ },
50
+ });
51
+ }
52
+ export function createNonCertifyingRunLedger(options) {
53
+ const captured = {
54
+ ledgerRoot: options.ledgerRoot,
55
+ locksRoot: options.locksRoot,
56
+ clock: options.clock,
57
+ packageRoot: options.packageRoot,
58
+ };
59
+ const sample = captured.clock.sample;
60
+ if (typeof sample !== "function")
61
+ throw new Error("non-certifying run ledger requires a clock sample function");
62
+ return createRunLedger(captured, {
63
+ clockClass: "synthetic",
64
+ processId: process.pid,
65
+ processInstanceId: createStableId("synthetic", randomUUID().toLowerCase()),
66
+ bootId: createStableId("synthetic-boot", randomUUID().toLowerCase()),
67
+ sample: () => sample(),
68
+ });
69
+ }
70
+ export function assertRunLedger(value) {
71
+ if (typeof value !== "object" || value === null || !RUN_LEDGERS.has(value)) {
72
+ throw new Error("run audit requires a real OMS RunLedgerStore");
73
+ }
74
+ if (!Object.isFrozen(value))
75
+ throw new Error("run ledger lost its frozen authority boundary");
76
+ }
77
+ export function createAuditRunTerminalWriter(ledger) {
78
+ assertRunLedger(ledger);
79
+ const metadata = RUN_LEDGERS.get(ledger);
80
+ if (!metadata)
81
+ throw new Error("audit run terminal writer metadata is unavailable");
82
+ const writer = Object.freeze({
83
+ [AUDIT_RUN_TERMINAL_WRITER]: true,
84
+ appendAtHead(input) {
85
+ const captured = captureAppendAtHeadInput(input);
86
+ if (captured.entryKind !== "run-completed" && captured.entryKind !== "run-aborted") {
87
+ throw new Error("audit run terminal writer accepts only terminal entries");
88
+ }
89
+ return metadata.internal.append(captured, "audit", captured.expectedHeadSha256);
90
+ },
91
+ });
92
+ AUDIT_RUN_TERMINAL_WRITERS.set(writer, metadata.internal);
93
+ return writer;
94
+ }
95
+ export function assertAuditRunTerminalWriter(value) {
96
+ if (typeof value !== "object" || value === null || !AUDIT_RUN_TERMINAL_WRITERS.has(value)) {
97
+ throw new Error("audit run termination requires a real OMS terminal writer");
98
+ }
99
+ if (!Object.isFrozen(value))
100
+ throw new Error("audit run terminal writer lost its frozen authority boundary");
101
+ }
102
+ /** Generic ledgers are deliberately audit-only, including live-clock ledgers. */
103
+ export function assertCertifyingRunLedger(_value, _head) {
104
+ throw new Error("certifying a run requires a real typed OMS LiveRunWriter");
105
+ }
106
+ export function createLiveRunWriter(options) {
107
+ const captured = { ledger: options.ledger, authority: options.authority };
108
+ assertRunLedger(captured.ledger);
109
+ assertRunCertificationAuthorityResolver(captured.authority);
110
+ const metadata = RUN_LEDGERS.get(captured.ledger);
111
+ if (!metadata || metadata.clockClass !== "live") {
112
+ throw new Error("live run writer requires a production live-clock ledger");
113
+ }
114
+ const writer = Object.freeze({
115
+ [LIVE_RUN_WRITER]: true,
116
+ start(input) {
117
+ const start = captureStartLiveInput(input);
118
+ if (start.runKind !== "12h" && start.runKind !== "24h") {
119
+ throw new Error("certifying runs must use the 12h or 24h run kind");
120
+ }
121
+ const runId = assertRunId(createStableId("run", start.operationId));
122
+ const authority = captured.authority.resolve(start.source);
123
+ const previous = resolvePreviousCanaryForStart(writer, start, authority);
124
+ assertAuthorityBinding(authority, {
125
+ action: "start",
126
+ runId,
127
+ operationId: start.operationId,
128
+ runKind: start.runKind,
129
+ policyDigest: start.policyDigest,
130
+ ticketSetDigest: start.ticketSetDigest,
131
+ servicePlanDigest: start.servicePlanDigest,
132
+ expectedHead: null,
133
+ previousCanary: previous,
134
+ });
135
+ const payload = {
136
+ authority,
137
+ previous_canary: previous,
138
+ service_plan_digest: assertSha256(start.servicePlanDigest, "run service plan digest"),
139
+ ticket_set_digest: assertSha256(start.ticketSetDigest, "run ticket set digest"),
140
+ };
141
+ const written = metadata.internal.start(start, "trusted-live", payload);
142
+ assertAuthorityPrecedesEntry(authority, written.entry);
143
+ return written;
144
+ },
145
+ heartbeat(input) {
146
+ return appendAuthorityEvent(metadata.internal, captured.authority, input, "heartbeat", "heartbeat");
147
+ },
148
+ recordProcessRestart(input) {
149
+ const event = captureLiveEventInput(input);
150
+ const entries = trustedRecords(metadata.internal, event.runId);
151
+ const authority = captured.authority.resolve(event.source);
152
+ const existing = entries.find((entry) => entry.entry.operation_id === event.operationId);
153
+ if (existing) {
154
+ const previous = entries[existing.entry.sequence - 1];
155
+ if (!previous || existing.entry.entry_kind !== "process-restarted") {
156
+ throw new RunLedgerConflictError(event.operationId);
157
+ }
158
+ assertAuthorityForHistory(authority, "process-restart", entries, previous.ref, event.operationId);
159
+ if (!sameJson(existing.entry.payload.authority, authority))
160
+ throw new RunLedgerConflictError(event.operationId);
161
+ return result("replayed", existing);
162
+ }
163
+ const last = requiredLast(entries);
164
+ if (isTerminal(last.entry.entry_kind))
165
+ throw new Error(`trusted run ledger is terminal: ${event.runId}`);
166
+ assertAuthorityForHistory(authority, "process-restart", entries, last.ref, event.operationId);
167
+ assertAuthorityAfterHead(authority, last.entry);
168
+ return metadata.internal.append({
169
+ runId: event.runId,
170
+ operationId: event.operationId,
171
+ entryKind: "process-restarted",
172
+ payload: {
173
+ authority,
174
+ previous_process_instance_id: last.entry.process_instance_id,
175
+ },
176
+ }, "trusted-live", last.ref.sha256);
177
+ },
178
+ recordLockRecovery(input) {
179
+ const capturedInput = {
180
+ runId: input.runId,
181
+ operationId: input.operationId,
182
+ recovery: input.recovery,
183
+ };
184
+ const runId = assertRunId(capturedInput.runId);
185
+ const operationId = assertOpaqueId(capturedInput.operationId, "run entry operation id");
186
+ assertRecoveredLock(capturedInput.recovery);
187
+ const entries = trustedRecords(metadata.internal, runId);
188
+ const last = requiredLast(entries);
189
+ if (isTerminal(last.entry.entry_kind))
190
+ throw new Error(`trusted run ledger is terminal: ${runId}`);
191
+ const recovery = captureRecoveryEvidence(capturedInput.recovery);
192
+ return metadata.internal.append({
193
+ runId,
194
+ operationId,
195
+ entryKind: "outbox-lock-recovered",
196
+ payload: { lock_recovery: recovery },
197
+ }, "trusted-live", last.ref.sha256);
198
+ },
199
+ complete(input) {
200
+ const completion = captureCompleteInput(input);
201
+ const entries = trustedRecords(metadata.internal, completion.runId);
202
+ const first = requiredFirst(entries);
203
+ const last = requiredLast(entries);
204
+ if (first.entry.run_kind !== completion.expectedRunKind) {
205
+ throw new Error("run completion expected kind does not match the trusted run");
206
+ }
207
+ const authority = captured.authority.resolve(completion.source);
208
+ const existing = entries.find((entry) => entry.entry.operation_id === completion.operationId);
209
+ if (existing) {
210
+ const previous = entries[existing.entry.sequence - 1];
211
+ if (!previous
212
+ ||
213
+ existing.entry.entry_kind !== "run-completed"
214
+ || existing.entry.provenance_class !== "trusted-live"
215
+ || !sameJson(existing.entry.payload.authority, authority)) {
216
+ throw new RunLedgerConflictError(completion.operationId);
217
+ }
218
+ assertAuthorityForHistory(authority, "complete", entries, previous.ref, completion.operationId);
219
+ return writer.certify(existing.ref);
220
+ }
221
+ if (isTerminal(last.entry.entry_kind))
222
+ throw new Error(`trusted run ledger is terminal: ${completion.runId}`);
223
+ assertAuthorityForHistory(authority, "complete", entries, last.ref, completion.operationId);
224
+ assertAuthorityAfterHead(authority, last.entry);
225
+ const minimumDurationMs = minimumDuration(completion.expectedRunKind);
226
+ const previousCanary = first.entry.payload.previous_canary;
227
+ const written = metadata.internal.append({
228
+ runId: completion.runId,
229
+ operationId: completion.operationId,
230
+ entryKind: "run-completed",
231
+ payload: {},
232
+ }, "trusted-live", last.ref.sha256, ({ entries: lockedEntries, sampled }) => {
233
+ const prospective = prospectiveObservation(lockedEntries, completion.operationId, "run-completed", sampled, metadata.internal.clock);
234
+ const timeline = analyzeTimeline([...lockedEntries.map(toObservation), prospective]);
235
+ assertMinimumDuration(timeline, minimumDurationMs);
236
+ const digest = certificationDigest(lockedEntries, prospective, authority, completion.expectedRunKind, minimumDurationMs, previousCanary);
237
+ return {
238
+ authority,
239
+ expected_run_kind: completion.expectedRunKind,
240
+ metrics_sha256: digest,
241
+ minimum_duration_ms: minimumDurationMs,
242
+ previous_canary: previousCanary,
243
+ };
244
+ });
245
+ return writer.certify(written.ref);
246
+ },
247
+ certify(head) {
248
+ return certifyTrustedRun(writer, captureReference(head));
249
+ },
250
+ });
251
+ LIVE_RUN_WRITERS.set(writer, { ledger: captured.ledger, authority: captured.authority, internal: metadata.internal });
252
+ return writer;
253
+ }
254
+ export function assertLiveRunWriter(value) {
255
+ if (typeof value !== "object" || value === null || !LIVE_RUN_WRITERS.has(value)) {
256
+ throw new Error("run certification requires a real OMS LiveRunWriter");
257
+ }
258
+ if (!Object.isFrozen(value))
259
+ throw new Error("live run writer lost its frozen authority boundary");
260
+ }
261
+ export function assertRunCertificationSnapshot(value) {
262
+ if (typeof value !== "object" || value === null || !RUN_CERTIFICATION_SNAPSHOTS.has(value)) {
263
+ throw new Error("run certification requires a real OMS RunCertificationSnapshot");
264
+ }
265
+ if (!Object.isFrozen(value))
266
+ throw new Error("run certification snapshot lost its frozen authority boundary");
267
+ }
268
+ function createRunLedger(options, clock) {
269
+ const captured = {
270
+ ledgerRoot: options.ledgerRoot,
271
+ locksRoot: options.locksRoot,
272
+ packageRoot: options.packageRoot,
273
+ };
274
+ const ledgerRoot = openStableDirectory(captured.ledgerRoot, "run ledger root");
275
+ const locksDirectory = openStableDirectory(captured.locksRoot, "run ledger locks root");
276
+ if (ledgerRoot.path === locksDirectory.path) {
277
+ throw new Error("run ledger and locks roots must be different directories");
278
+ }
279
+ const schema = loadJsonSchema(join(captured.packageRoot ?? findPackageRoot(), "schemas", "run-ledger-entry.schema.json"));
280
+ function start(input, provenance = "audit", suppliedPayload) {
281
+ const startInput = captureStartInput(input);
282
+ const operationId = assertOpaqueId(startInput.operationId, "run start operation id");
283
+ const runKind = assertRunKind(startInput.runKind);
284
+ const policyDigest = assertSha256(startInput.policyDigest, "run policy digest");
285
+ const ticketSetDigest = assertSha256(startInput.ticketSetDigest, "run ticket set digest");
286
+ const servicePlanDigest = assertSha256(startInput.servicePlanDigest, "run service plan digest");
287
+ if (provenance === "trusted-live" && clock.clockClass !== "live") {
288
+ throw new Error("synthetic clocks can never create trusted run provenance");
289
+ }
290
+ const payload = canonicalizeObject(suppliedPayload ?? {
291
+ service_plan_digest: servicePlanDigest,
292
+ ticket_set_digest: ticketSetDigest,
293
+ }, "run start payload");
294
+ if (payload.ticket_set_digest !== ticketSetDigest || payload.service_plan_digest !== servicePlanDigest) {
295
+ throw new Error("run start payload does not match its materialized digests");
296
+ }
297
+ const runId = assertRunId(createStableId("run", operationId));
298
+ const operationDigest = operationSha256({
299
+ entryKind: "run-started",
300
+ runId,
301
+ runKind,
302
+ operationId,
303
+ policyDigest,
304
+ clockClass: clock.clockClass,
305
+ provenance,
306
+ payload,
307
+ });
308
+ locksDirectory.assertUnchanged();
309
+ const lock = acquireDirectoryLock({
310
+ locksRoot: locksDirectory.path,
311
+ lockId: runLockId(runId),
312
+ ownerId: "run-ledger",
313
+ note: `run-start:${runId}`,
314
+ });
315
+ try {
316
+ locksDirectory.assertUnchanged();
317
+ const existing = readRun(runId, schema, ledgerRoot);
318
+ if (existing.length > 0) {
319
+ const replay = findReplay(existing, operationId, operationDigest, clock.clockClass, provenance);
320
+ if (replay)
321
+ return result("replayed", replay);
322
+ throw new RunLedgerConflictError(operationId);
323
+ }
324
+ const entry = makeEntry({
325
+ runId,
326
+ sequence: 0,
327
+ entryKind: "run-started",
328
+ runKind,
329
+ operationId,
330
+ operationDigest,
331
+ policyDigest,
332
+ clock,
333
+ provenance,
334
+ sampled: sampleClock(clock),
335
+ previousSha256: null,
336
+ payload,
337
+ });
338
+ const written = writeEntry(entry, schema, ledgerRoot);
339
+ validateRun(readRun(runId, schema, ledgerRoot));
340
+ return result(written.status, written.stored);
341
+ }
342
+ finally {
343
+ lock.release();
344
+ locksDirectory.assertUnchanged();
345
+ }
346
+ }
347
+ function append(input, provenance = "audit", expectedHeadSha256, payloadFactory) {
348
+ const capturedInput = captureAppendInput(input);
349
+ const runId = assertRunId(capturedInput.runId);
350
+ const operationId = assertOpaqueId(capturedInput.operationId, "run entry operation id");
351
+ const entryKind = assertAppendEntryKind(capturedInput.entryKind);
352
+ const fixedPayload = payloadFactory ? undefined : canonicalizeObject(capturedInput.payload, "run entry payload");
353
+ locksDirectory.assertUnchanged();
354
+ const lock = acquireDirectoryLock({
355
+ locksRoot: locksDirectory.path,
356
+ lockId: runLockId(runId),
357
+ ownerId: "run-ledger",
358
+ note: `run-append:${runId}`,
359
+ });
360
+ try {
361
+ locksDirectory.assertUnchanged();
362
+ const entries = readRun(runId, schema, ledgerRoot);
363
+ validateRun(entries);
364
+ const first = requiredFirst(entries);
365
+ const last = requiredLast(entries);
366
+ const existingOperation = entries.find((entry) => entry.entry.operation_id === operationId);
367
+ if (existingOperation && payloadFactory)
368
+ throw new RunLedgerConflictError(operationId);
369
+ if (existingOperation && fixedPayload) {
370
+ const digest = operationSha256({
371
+ entryKind,
372
+ runId,
373
+ runKind: first.entry.run_kind,
374
+ operationId,
375
+ policyDigest: first.entry.policy_sha256,
376
+ clockClass: clock.clockClass,
377
+ provenance,
378
+ payload: fixedPayload,
379
+ });
380
+ const replay = findReplay(entries, operationId, digest, clock.clockClass, provenance);
381
+ if (replay)
382
+ return result("replayed", replay);
383
+ }
384
+ if (isTerminal(last.entry.entry_kind))
385
+ throw new Error(`run ledger is terminal: ${runId}`);
386
+ if (expectedHeadSha256 !== undefined && last.ref.sha256 !== assertSha256(expectedHeadSha256, "expected run head digest")) {
387
+ throw new Error("run ledger trusted head advanced concurrently");
388
+ }
389
+ if (first.entry.clock_class !== clock.clockClass) {
390
+ throw new Error("run ledger clock class cannot change across processes");
391
+ }
392
+ if (first.entry.provenance_class !== provenance) {
393
+ throw new Error("run ledger provenance class cannot change within a run");
394
+ }
395
+ if (provenance === "trusted-live") {
396
+ if (clock.clockClass !== "live" || entries.some((entry) => entry.entry.provenance_class !== "trusted-live")) {
397
+ throw new Error("trusted run history cannot descend from audit-only or synthetic provenance");
398
+ }
399
+ if (entries.some((entry) => entry.entry.boot_id !== clock.bootId)) {
400
+ throw new Error("trusted run cannot continue across a system boot identity change");
401
+ }
402
+ const changedProcess = last.entry.process_instance_id !== clock.processInstanceId;
403
+ if (changedProcess !== (entryKind === "process-restarted")) {
404
+ throw new Error(changedProcess
405
+ ? "new process must append a typed process-restarted entry first"
406
+ : "process-restarted requires a fresh process instance");
407
+ }
408
+ }
409
+ const sampled = sampleClock(clock);
410
+ assertClockAdvances(last.entry, sampled, clock);
411
+ const payload = canonicalizeObject(payloadFactory ? payloadFactory({ entries, sampled }) : fixedPayload ?? {}, "run entry payload");
412
+ const operationDigest = operationSha256({
413
+ entryKind,
414
+ runId,
415
+ runKind: first.entry.run_kind,
416
+ operationId,
417
+ policyDigest: first.entry.policy_sha256,
418
+ clockClass: clock.clockClass,
419
+ provenance,
420
+ payload,
421
+ });
422
+ const entry = makeEntry({
423
+ runId,
424
+ sequence: last.entry.sequence + 1,
425
+ entryKind,
426
+ runKind: first.entry.run_kind,
427
+ operationId,
428
+ operationDigest,
429
+ policyDigest: first.entry.policy_sha256,
430
+ clock,
431
+ provenance,
432
+ sampled,
433
+ previousSha256: last.ref.sha256,
434
+ payload,
435
+ });
436
+ const written = writeEntry(entry, schema, ledgerRoot);
437
+ validateRun(readRun(runId, schema, ledgerRoot));
438
+ return result(written.status, written.stored);
439
+ }
440
+ finally {
441
+ lock.release();
442
+ locksDirectory.assertUnchanged();
443
+ }
444
+ }
445
+ const store = Object.freeze({
446
+ [RUN_LEDGER]: true,
447
+ start(input) {
448
+ return start(input, "audit");
449
+ },
450
+ append(input) {
451
+ const capturedInput = captureAppendInput(input);
452
+ if (isTerminal(capturedInput.entryKind)) {
453
+ throw new Error("generic run ledger cannot append controller terminal entries");
454
+ }
455
+ return append(capturedInput, "audit");
456
+ },
457
+ appendAtHead(input) {
458
+ const capturedInput = captureAppendAtHeadInput(input);
459
+ if (isTerminal(capturedInput.entryKind)) {
460
+ throw new Error("generic run ledger cannot append controller terminal entries");
461
+ }
462
+ return append(capturedInput, "audit", capturedInput.expectedHeadSha256);
463
+ },
464
+ read(ref) {
465
+ return readReference(captureReference(ref), schema, ledgerRoot).entry;
466
+ },
467
+ list(runId) {
468
+ const entries = readRun(assertRunId(runId), schema, ledgerRoot);
469
+ validateRun(entries);
470
+ return deepFreeze(entries.map((entry) => entry.entry));
471
+ },
472
+ listRunIds() {
473
+ return listStoredRunIds(schema, ledgerRoot);
474
+ },
475
+ head(runId) {
476
+ const entries = readRun(assertRunId(runId), schema, ledgerRoot);
477
+ validateRun(entries);
478
+ return entries.at(-1)?.ref;
479
+ },
480
+ deriveMetrics(head) {
481
+ const pinned = readReference(captureReference(head), schema, ledgerRoot);
482
+ const entries = readRun(pinned.entry.run_id, schema, ledgerRoot);
483
+ validateRun(entries);
484
+ const latest = requiredLast(entries);
485
+ if (!sameReference(latest.ref, pinned.ref)) {
486
+ throw new Error("run ledger head is stale or does not identify the latest entry");
487
+ }
488
+ return deriveMetrics(entries, false);
489
+ },
490
+ });
491
+ const internal = { clock, start, append, records(runId) {
492
+ const entries = readRun(assertRunId(runId), schema, ledgerRoot);
493
+ validateRun(entries);
494
+ return entries;
495
+ } };
496
+ RUN_LEDGERS.set(store, { clockClass: clock.clockClass, internal });
497
+ return store;
498
+ }
499
+ function appendAuthorityEvent(internal, resolver, input, entryKind, authorityAction) {
500
+ const event = captureLiveEventInput(input);
501
+ const entries = trustedRecords(internal, event.runId);
502
+ const authority = resolver.resolve(event.source);
503
+ const existing = entries.find((entry) => entry.entry.operation_id === event.operationId);
504
+ if (existing) {
505
+ const previous = entries[existing.entry.sequence - 1];
506
+ if (!previous || existing.entry.entry_kind !== entryKind)
507
+ throw new RunLedgerConflictError(event.operationId);
508
+ assertAuthorityForHistory(authority, authorityAction, entries, previous.ref, event.operationId);
509
+ if (!sameJson(existing.entry.payload.authority, authority))
510
+ throw new RunLedgerConflictError(event.operationId);
511
+ return result("replayed", existing);
512
+ }
513
+ const last = requiredLast(entries);
514
+ if (isTerminal(last.entry.entry_kind))
515
+ throw new Error(`trusted run ledger is terminal: ${event.runId}`);
516
+ assertAuthorityForHistory(authority, authorityAction, entries, last.ref, event.operationId);
517
+ assertAuthorityAfterHead(authority, last.entry);
518
+ return internal.append({
519
+ runId: event.runId,
520
+ operationId: event.operationId,
521
+ entryKind,
522
+ payload: { authority },
523
+ }, "trusted-live", last.ref.sha256);
524
+ }
525
+ function certifyTrustedRun(writer, head) {
526
+ assertLiveRunWriter(writer);
527
+ const metadata = LIVE_RUN_WRITERS.get(writer);
528
+ if (!metadata)
529
+ throw new Error("live run writer metadata is unavailable");
530
+ const entries = metadata.internal.records(head.run_id);
531
+ const latest = requiredLast(entries);
532
+ if (!sameReference(latest.ref, head))
533
+ throw new Error("certification requires the canonical latest trusted head");
534
+ if (entries.some((entry) => entry.entry.clock_class !== "live" || entry.entry.provenance_class !== "trusted-live")) {
535
+ throw new Error("audit-only or synthetic provenance cannot be certified");
536
+ }
537
+ if (latest.entry.entry_kind !== "run-completed") {
538
+ throw new Error("only a terminal run-completed entry may certify a run");
539
+ }
540
+ validatePersistedAuthorities(metadata.authority, entries);
541
+ const first = requiredFirst(entries);
542
+ const completion = latest.entry.payload;
543
+ const expectedKind = assertCertifyingRunKind(completion.expected_run_kind);
544
+ if (expectedKind !== first.entry.run_kind)
545
+ throw new Error("terminal expected run kind does not match run history");
546
+ const expectedMinimum = minimumDuration(expectedKind);
547
+ if (completion.minimum_duration_ms !== expectedMinimum)
548
+ throw new Error("terminal minimum duration is not policy-derived");
549
+ const timeline = analyzeTimeline(entries.map(toObservation));
550
+ assertMinimumDuration(timeline, expectedMinimum);
551
+ const expectedDigest = certificationDigest(entries.slice(0, -1), toObservation(latest), assertResolvedAuthority(completion.authority), expectedKind, expectedMinimum, completion.previous_canary);
552
+ if (completion.metrics_sha256 !== expectedDigest)
553
+ throw new Error("terminal certification metrics digest is invalid");
554
+ validateCanaryChain(writer, entries);
555
+ const metrics = deriveMetrics(entries, true);
556
+ const snapshot = deepFreeze({
557
+ [RUN_CERTIFICATION_SNAPSHOT]: true,
558
+ reference: {
559
+ run_id: metrics.run_id,
560
+ head: metrics.head,
561
+ metrics_sha256: metrics.metrics_sha256,
562
+ },
563
+ entry: latest.entry,
564
+ metrics,
565
+ });
566
+ RUN_CERTIFICATION_SNAPSHOTS.add(snapshot);
567
+ return snapshot;
568
+ }
569
+ function validateCanaryChain(writer, entries) {
570
+ const first = requiredFirst(entries);
571
+ const previousValue = first.entry.payload.previous_canary;
572
+ const terminalPrevious = requiredLast(entries).entry.payload.previous_canary;
573
+ if (!sameJson(previousValue, terminalPrevious))
574
+ throw new Error("terminal previous canary binding changed");
575
+ if (first.entry.run_kind === "12h") {
576
+ if (previousValue !== null)
577
+ throw new Error("12h canary must not depend on another canary");
578
+ return;
579
+ }
580
+ if (first.entry.run_kind !== "24h")
581
+ throw new Error("only 12h and 24h runs can certify");
582
+ const previousRef = parseCertificationReference(previousValue);
583
+ const previousMetrics = certifyTrustedRun(writer, previousRef.head).metrics;
584
+ if (previousMetrics.run_id !== previousRef.run_id
585
+ || previousMetrics.metrics_sha256 !== previousRef.metrics_sha256
586
+ || previousMetrics.run_kind !== "12h"
587
+ || previousMetrics.status !== "completed") {
588
+ throw new Error("24h run does not reference a certified terminal 12h canary");
589
+ }
590
+ if (!previousMetrics.ended_at || previousMetrics.ended_at > first.entry.recorded_at) {
591
+ throw new Error("24h run began before its 12h canary completed");
592
+ }
593
+ const currentProcesses = new Set(entries.map((entry) => entry.entry.process_instance_id));
594
+ if (previousMetrics.process_instance_ids.some((processId) => currentProcesses.has(processId))) {
595
+ throw new Error("24h run must use processes separate from its 12h canary");
596
+ }
597
+ if (previousMetrics.ticket_set_digest === first.entry.payload.ticket_set_digest) {
598
+ throw new Error("24h run must use a fresh ticket set after its 12h canary");
599
+ }
600
+ }
601
+ function resolvePreviousCanaryForStart(writer, input, authority) {
602
+ if (input.runKind === "12h") {
603
+ if (input.previousCanary !== null)
604
+ throw new Error("12h canary must not depend on another canary");
605
+ return null;
606
+ }
607
+ if (input.previousCanary === null)
608
+ throw new Error("24h run requires a certified 12h canary");
609
+ const previousRef = captureCertificationReference(input.previousCanary);
610
+ const metrics = certifyTrustedRun(writer, previousRef.head).metrics;
611
+ if (metrics.run_id !== previousRef.run_id
612
+ || metrics.metrics_sha256 !== previousRef.metrics_sha256
613
+ || metrics.run_kind !== "12h"
614
+ || metrics.status !== "completed") {
615
+ throw new Error("24h run requires a certified terminal 12h canary");
616
+ }
617
+ if (!metrics.ended_at || metrics.ended_at > authority.occurred_at) {
618
+ throw new Error("24h authority was issued before the 12h canary completed");
619
+ }
620
+ if (metrics.process_instance_ids.includes(currentProcessInstanceId())) {
621
+ throw new Error("24h run must use a process separate from its 12h canary");
622
+ }
623
+ if (metrics.ticket_set_digest === input.ticketSetDigest) {
624
+ throw new Error("24h run must use a fresh ticket set after its 12h canary");
625
+ }
626
+ return serializeCertificationReference(previousRef);
627
+ }
628
+ function trustedRecords(internal, runIdValue) {
629
+ const entries = internal.records(assertRunId(runIdValue));
630
+ if (entries.length === 0)
631
+ throw new Error("trusted run ledger does not exist");
632
+ if (entries.some((entry) => entry.entry.clock_class !== "live" || entry.entry.provenance_class !== "trusted-live")) {
633
+ throw new Error("trusted writer refuses an audit-only or synthetic run history");
634
+ }
635
+ return entries;
636
+ }
637
+ function validatePersistedAuthorities(resolver, entries) {
638
+ for (let index = 0; index < entries.length; index += 1) {
639
+ const stored = entries[index];
640
+ if (!stored)
641
+ throw new Error("trusted run authority entry is missing");
642
+ const entry = stored.entry;
643
+ if (entry.entry_kind === "outbox-lock-recovered") {
644
+ validateStoredRecovery(entry.payload.lock_recovery);
645
+ continue;
646
+ }
647
+ const storedAuthority = assertResolvedAuthority(entry.payload.authority);
648
+ const resolved = resolver.resolveStored(storedAuthority.reference);
649
+ if (!sameJson(storedAuthority, resolved))
650
+ throw new Error("persisted authority no longer matches immutable outbox evidence");
651
+ const previous = entries[index - 1];
652
+ const action = authorityAction(entry.entry_kind);
653
+ assertAuthorityForHistory(resolved, action, entries, previous?.ref ?? null, entry.operation_id);
654
+ if (previous)
655
+ assertAuthorityAfterHead(resolved, previous.entry);
656
+ assertAuthorityPrecedesEntry(resolved, entry);
657
+ }
658
+ }
659
+ function authorityAction(entryKind) {
660
+ if (entryKind === "run-started")
661
+ return "start";
662
+ if (entryKind === "heartbeat")
663
+ return "heartbeat";
664
+ if (entryKind === "process-restarted")
665
+ return "process-restart";
666
+ if (entryKind === "run-completed")
667
+ return "complete";
668
+ throw new Error(`trusted entry kind has no certification authority action: ${entryKind}`);
669
+ }
670
+ function assertAuthorityForHistory(authority, action, entries, expectedHead, operationId) {
671
+ const first = requiredFirst(entries);
672
+ assertAuthorityBinding(authority, {
673
+ action,
674
+ runId: first.entry.run_id,
675
+ operationId,
676
+ runKind: assertCertifyingRunKind(first.entry.run_kind),
677
+ policyDigest: first.entry.policy_sha256,
678
+ ticketSetDigest: assertSha256(first.entry.payload.ticket_set_digest, "trusted ticket set digest"),
679
+ servicePlanDigest: assertSha256(first.entry.payload.service_plan_digest, "trusted service plan digest"),
680
+ expectedHead,
681
+ previousCanary: first.entry.payload.previous_canary,
682
+ });
683
+ }
684
+ function assertAuthorityBinding(authority, expected) {
685
+ const expectedBinding = {
686
+ schema_version: 1,
687
+ action: expected.action,
688
+ completion_semantics: expected.action === "start"
689
+ ? "begin-run"
690
+ : expected.action === "heartbeat"
691
+ ? "append-heartbeat"
692
+ : expected.action === "process-restart"
693
+ ? "record-process-restart"
694
+ : "terminal-run-completed",
695
+ run_id: expected.runId,
696
+ operation_id: expected.operationId,
697
+ run_kind: expected.runKind,
698
+ policy_sha256: expected.policyDigest,
699
+ ticket_set_digest: expected.ticketSetDigest,
700
+ service_plan_digest: expected.servicePlanDigest,
701
+ expected_head: expected.expectedHead,
702
+ previous_canary: expected.previousCanary,
703
+ };
704
+ if (!sameJson(authority.binding, expectedBinding)) {
705
+ throw new Error("certification authority does not bind the exact canonical run transition");
706
+ }
707
+ }
708
+ function captureRecoveryEvidence(recovery) {
709
+ const captured = {
710
+ lockId: recovery.lock_id,
711
+ generationId: recovery.generation_id,
712
+ recoveredBy: recovery.recovered_by,
713
+ previousOwner: recovery.previousOwner,
714
+ archivedPath: recovery.archivedPath,
715
+ recoveryEvidencePath: recovery.recoveryEvidencePath,
716
+ };
717
+ const lockId = assertLockId(captured.lockId, "recovered lock id");
718
+ const generationId = assertOpaqueId(captured.generationId, "recovered lock generation id");
719
+ const recoveredBy = assertActorId(captured.recoveredBy, "lock recovery actor");
720
+ if (dirname(captured.recoveryEvidencePath) !== captured.archivedPath) {
721
+ throw new Error("lock recovery evidence escaped its immutable archive");
722
+ }
723
+ const bytes = readImmutableFile(captured.recoveryEvidencePath, MAX_ENTRY_BYTES);
724
+ const evidence = parseJson(bytes);
725
+ if (!isRecord(evidence)
726
+ || evidence.lock_id !== lockId
727
+ || evidence.generation_id !== generationId
728
+ || evidence.recovered_by !== recoveredBy) {
729
+ throw new Error("lock recovery evidence does not match its branded recovery result");
730
+ }
731
+ const previousProcess = captured.previousOwner?.process_instance_id ?? null;
732
+ if (!sameJson(evidence.previous_owner, captured.previousOwner)) {
733
+ throw new Error("lock recovery previous owner does not match immutable evidence");
734
+ }
735
+ return deepFreeze({
736
+ lock_id: lockId,
737
+ generation_id: generationId,
738
+ recovered_by: recoveredBy,
739
+ previous_process_instance_id: previousProcess,
740
+ recovery_evidence_path: captured.recoveryEvidencePath,
741
+ recovery_evidence_sha256: sha256(bytes),
742
+ });
743
+ }
744
+ function validateStoredRecovery(value) {
745
+ if (!isRecord(value))
746
+ throw new Error("trusted lock recovery payload must be an object");
747
+ assertExactKeys(value, [
748
+ "lock_id",
749
+ "generation_id",
750
+ "previous_process_instance_id",
751
+ "recovered_by",
752
+ "recovery_evidence_path",
753
+ "recovery_evidence_sha256",
754
+ ], "trusted lock recovery payload");
755
+ const lockId = assertLockId(value.lock_id, "recovered lock id");
756
+ const generationId = assertOpaqueId(value.generation_id, "recovered lock generation id");
757
+ const recoveredBy = assertActorId(value.recovered_by, "lock recovery actor");
758
+ const path = value.recovery_evidence_path;
759
+ if (typeof path !== "string")
760
+ throw new Error("invalid lock recovery evidence path");
761
+ const bytes = readImmutableFile(path, MAX_ENTRY_BYTES);
762
+ if (sha256(bytes) !== assertSha256(value.recovery_evidence_sha256, "lock recovery evidence digest")) {
763
+ throw new Error("lock recovery evidence digest changed");
764
+ }
765
+ const evidence = parseJson(bytes);
766
+ if (!isRecord(evidence)
767
+ || evidence.lock_id !== lockId
768
+ || evidence.generation_id !== generationId
769
+ || evidence.recovered_by !== recoveredBy) {
770
+ throw new Error("persisted lock recovery evidence does not match the trusted entry");
771
+ }
772
+ const previousProcess = isRecord(evidence.previous_owner) ? evidence.previous_owner.process_instance_id : null;
773
+ if (previousProcess !== value.previous_process_instance_id) {
774
+ throw new Error("persisted lock recovery previous process binding changed");
775
+ }
776
+ }
777
+ function makeEntry(input) {
778
+ return deepFreeze({
779
+ schema_version: 1,
780
+ entry_id: createStableId("audit", input.runId, input.operationId),
781
+ run_id: input.runId,
782
+ sequence: input.sequence,
783
+ entry_kind: input.entryKind,
784
+ run_kind: input.runKind,
785
+ operation_id: input.operationId,
786
+ operation_sha256: input.operationDigest,
787
+ policy_sha256: input.policyDigest,
788
+ clock_class: input.clock.clockClass,
789
+ provenance_class: input.provenance,
790
+ recorded_at: input.sampled.wallTime,
791
+ monotonic_ns: input.sampled.monotonicNs.toString(),
792
+ process_id: input.clock.processId,
793
+ process_instance_id: input.clock.processInstanceId,
794
+ boot_id: input.clock.bootId,
795
+ previous_entry_sha256: input.previousSha256,
796
+ payload: input.payload,
797
+ });
798
+ }
799
+ function writeEntry(entry, schema, root) {
800
+ const normalized = validateEntry(entry, schema);
801
+ const bytes = canonicalJson(normalized);
802
+ if (bytes.byteLength > MAX_ENTRY_BYTES)
803
+ throw new Error(`run ledger entry exceeds ${MAX_ENTRY_BYTES} bytes`);
804
+ const ref = referenceFor(normalized, bytes);
805
+ const path = join(root.path, ref.file_name);
806
+ assertFileInside(root, path);
807
+ const status = writeImmutableFile(path, bytes);
808
+ return { status, stored: readReference(ref, schema, root) };
809
+ }
810
+ function readReference(ref, schema, root) {
811
+ const normalizedRef = validateReference(ref);
812
+ const path = join(root.path, normalizedRef.file_name);
813
+ assertFileInside(root, path);
814
+ const bytes = readImmutableFile(path, MAX_ENTRY_BYTES);
815
+ root.assertUnchanged();
816
+ if (sha256(bytes) !== normalizedRef.sha256)
817
+ throw new Error("run ledger entry digest mismatch");
818
+ const entry = validateEntry(parseJson(bytes), schema);
819
+ if (entry.entry_id !== normalizedRef.entry_id
820
+ || entry.run_id !== normalizedRef.run_id
821
+ || entry.sequence !== normalizedRef.sequence) {
822
+ throw new Error("run ledger reference does not match its entry");
823
+ }
824
+ return deepFreeze({ entry, ref: normalizedRef, bytes });
825
+ }
826
+ function readRun(runId, schema, root) {
827
+ root.assertUnchanged();
828
+ const directoryEntries = readdirSync(root.path, { withFileTypes: true })
829
+ .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
830
+ root.assertUnchanged();
831
+ const entries = [];
832
+ for (const directoryEntry of directoryEntries) {
833
+ if (!directoryEntry.name.endsWith(".json"))
834
+ continue;
835
+ root.assertUnchanged();
836
+ if (!directoryEntry.isFile())
837
+ throw new Error(`unsafe run ledger directory entry: ${directoryEntry.name}`);
838
+ const entryId = directoryEntry.name.slice(0, -".json".length);
839
+ if (!ENTRY_ID_PATTERN.test(entryId) || `${entryId}.json` !== directoryEntry.name) {
840
+ throw new Error(`invalid run ledger filename: ${directoryEntry.name}`);
841
+ }
842
+ const path = join(root.path, directoryEntry.name);
843
+ assertFileInside(root, path);
844
+ const bytes = readImmutableFile(path, MAX_ENTRY_BYTES);
845
+ root.assertUnchanged();
846
+ const entry = validateEntry(parseJson(bytes), schema);
847
+ if (entry.entry_id !== entryId)
848
+ throw new Error("run ledger filename does not match entry id");
849
+ if (entry.run_id === runId)
850
+ entries.push(deepFreeze({ entry, ref: referenceFor(entry, bytes), bytes }));
851
+ }
852
+ root.assertUnchanged();
853
+ entries.sort((left, right) => left.entry.sequence - right.entry.sequence);
854
+ return deepFreeze(entries);
855
+ }
856
+ function listStoredRunIds(schema, root) {
857
+ root.assertUnchanged();
858
+ const runIds = new Set();
859
+ for (const entry of readdirSync(root.path, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
860
+ if (!entry.name.endsWith(".json"))
861
+ continue;
862
+ if (!entry.isFile())
863
+ throw new Error(`unsafe run ledger directory entry: ${entry.name}`);
864
+ const bytes = readImmutableFile(join(root.path, entry.name), MAX_ENTRY_BYTES);
865
+ const parsed = validateEntry(parseJson(bytes), schema);
866
+ if (`${parsed.entry_id}.json` !== entry.name)
867
+ throw new Error("run ledger filename does not match entry id");
868
+ runIds.add(parsed.run_id);
869
+ }
870
+ const result = [...runIds].sort((left, right) => left.localeCompare(right));
871
+ for (const runId of result)
872
+ validateRun(readRun(runId, schema, root));
873
+ root.assertUnchanged();
874
+ return Object.freeze(result);
875
+ }
876
+ function validateRun(entries) {
877
+ if (entries.length === 0)
878
+ return;
879
+ const first = requiredFirst(entries);
880
+ if (first.entry.sequence !== 0 || first.entry.entry_kind !== "run-started") {
881
+ throw new Error("run ledger must begin with run-started at sequence zero");
882
+ }
883
+ validateStartPayload(first.entry);
884
+ const operationIds = new Set();
885
+ for (let index = 0; index < entries.length; index += 1) {
886
+ const current = entries[index];
887
+ if (!current)
888
+ throw new Error("run ledger entry is missing");
889
+ const previous = entries[index - 1];
890
+ if (current.entry.sequence !== index)
891
+ throw new Error("run ledger sequence is not contiguous");
892
+ if (current.entry.run_id !== first.entry.run_id)
893
+ throw new Error("run ledger contains another run id");
894
+ if (current.entry.run_kind !== first.entry.run_kind)
895
+ throw new Error("run kind changed within ledger");
896
+ if (current.entry.policy_sha256 !== first.entry.policy_sha256)
897
+ throw new Error("run policy changed within ledger");
898
+ if (current.entry.clock_class !== first.entry.clock_class)
899
+ throw new Error("run clock class changed within ledger");
900
+ if (current.entry.provenance_class !== first.entry.provenance_class) {
901
+ throw new Error("run ledger provenance class changed within ledger");
902
+ }
903
+ if (operationIds.has(current.entry.operation_id))
904
+ throw new Error("run ledger operation ids must be unique");
905
+ operationIds.add(current.entry.operation_id);
906
+ if (current.entry.previous_entry_sha256 !== (previous?.ref.sha256 ?? null)) {
907
+ throw new Error("run ledger hash chain is broken");
908
+ }
909
+ if (previous) {
910
+ if (timestampEpoch(current.entry.recorded_at) < timestampEpoch(previous.entry.recorded_at)) {
911
+ throw new Error("run ledger wall clock moved backwards");
912
+ }
913
+ if (current.entry.boot_id === previous.entry.boot_id
914
+ && BigInt(current.entry.monotonic_ns) <= BigInt(previous.entry.monotonic_ns)) {
915
+ throw new Error("run ledger system monotonic clock did not advance");
916
+ }
917
+ if (isTerminal(previous.entry.entry_kind))
918
+ throw new Error("run ledger contains entries after a terminal entry");
919
+ }
920
+ const expectedOperation = operationSha256({
921
+ entryKind: current.entry.entry_kind,
922
+ runId: current.entry.run_id,
923
+ runKind: current.entry.run_kind,
924
+ operationId: current.entry.operation_id,
925
+ policyDigest: current.entry.policy_sha256,
926
+ clockClass: current.entry.clock_class,
927
+ provenance: current.entry.provenance_class,
928
+ payload: current.entry.payload,
929
+ });
930
+ if (current.entry.operation_sha256 !== expectedOperation)
931
+ throw new Error("run ledger operation digest is invalid");
932
+ validateTrustedPayload(current.entry);
933
+ }
934
+ const terminal = entries.at(-1);
935
+ if (terminal?.entry.provenance_class === "trusted-live" && terminal.entry.entry_kind === "run-completed") {
936
+ const payload = terminal.entry.payload;
937
+ const expectedKind = assertCertifyingRunKind(payload.expected_run_kind);
938
+ const minimum = minimumDuration(expectedKind);
939
+ if (payload.minimum_duration_ms !== minimum)
940
+ throw new Error("terminal minimum duration is not policy-derived");
941
+ const digest = certificationDigest(entries.slice(0, -1), toObservation(terminal), assertResolvedAuthority(payload.authority), expectedKind, minimum, payload.previous_canary);
942
+ if (payload.metrics_sha256 !== digest)
943
+ throw new Error("terminal certification metrics digest is invalid");
944
+ }
945
+ }
946
+ function validateEntry(value, schema) {
947
+ assertSchema(value, schema, "run ledger entry");
948
+ const entry = value;
949
+ assertRunId(entry.run_id);
950
+ if (!ENTRY_ID_PATTERN.test(entry.entry_id))
951
+ throw new Error("invalid run ledger entry id");
952
+ assertOpaqueId(entry.operation_id, "run ledger operation id");
953
+ assertOpaqueId(entry.process_instance_id, "run ledger process instance id");
954
+ assertOpaqueId(entry.boot_id, "run ledger boot id");
955
+ assertSha256(entry.operation_sha256, "run ledger operation digest");
956
+ assertSha256(entry.policy_sha256, "run ledger policy digest");
957
+ if (entry.previous_entry_sha256 !== null)
958
+ assertSha256(entry.previous_entry_sha256, "previous run entry digest");
959
+ if (entry.entry_id !== createStableId("audit", entry.run_id, entry.operation_id)) {
960
+ throw new Error("run ledger entry id does not match its operation identity");
961
+ }
962
+ assertTimestamp(entry.recorded_at);
963
+ if (!Number.isSafeInteger(entry.process_id) || entry.process_id < 0)
964
+ throw new Error("invalid run ledger process id");
965
+ if (!Number.isSafeInteger(entry.sequence) || entry.sequence < 0)
966
+ throw new Error("invalid run ledger sequence");
967
+ assertJsonValue(entry.payload, "run ledger payload");
968
+ if (entry.entry_kind === "run-started")
969
+ validateStartPayload(entry);
970
+ const expectedOperation = operationSha256({
971
+ entryKind: entry.entry_kind,
972
+ runId: entry.run_id,
973
+ runKind: entry.run_kind,
974
+ operationId: entry.operation_id,
975
+ policyDigest: entry.policy_sha256,
976
+ clockClass: entry.clock_class,
977
+ provenance: entry.provenance_class,
978
+ payload: entry.payload,
979
+ });
980
+ if (entry.operation_sha256 !== expectedOperation)
981
+ throw new Error("run ledger operation digest is invalid");
982
+ return deepFreeze(structuredClone(entry));
983
+ }
984
+ function validateStartPayload(entry) {
985
+ if (entry.provenance_class === "audit") {
986
+ assertExactKeys(entry.payload, ["service_plan_digest", "ticket_set_digest"], "audit run-started payload");
987
+ }
988
+ else {
989
+ if (entry.clock_class !== "live")
990
+ throw new Error("trusted provenance requires a live clock");
991
+ assertExactKeys(entry.payload, [
992
+ "authority",
993
+ "previous_canary",
994
+ "service_plan_digest",
995
+ "ticket_set_digest",
996
+ ], "trusted run-started payload");
997
+ assertResolvedAuthority(entry.payload.authority);
998
+ if (entry.run_kind === "12h" && entry.payload.previous_canary !== null) {
999
+ throw new Error("12h canary must not depend on another canary");
1000
+ }
1001
+ if (entry.run_kind === "24h")
1002
+ parseCertificationReference(entry.payload.previous_canary);
1003
+ if (entry.run_kind === "until-empty")
1004
+ throw new Error("until-empty runs cannot use trusted certification provenance");
1005
+ }
1006
+ assertSha256(entry.payload.ticket_set_digest, "run ticket set digest");
1007
+ assertSha256(entry.payload.service_plan_digest, "run service plan digest");
1008
+ }
1009
+ function validateTrustedPayload(entry) {
1010
+ if (entry.provenance_class !== "trusted-live")
1011
+ return;
1012
+ if (entry.clock_class !== "live")
1013
+ throw new Error("trusted provenance requires a live clock");
1014
+ switch (entry.entry_kind) {
1015
+ case "run-started":
1016
+ return;
1017
+ case "heartbeat":
1018
+ assertExactKeys(entry.payload, ["authority"], "trusted heartbeat payload");
1019
+ assertResolvedAuthority(entry.payload.authority);
1020
+ return;
1021
+ case "process-restarted":
1022
+ assertExactKeys(entry.payload, ["authority", "previous_process_instance_id"], "trusted restart payload");
1023
+ assertResolvedAuthority(entry.payload.authority);
1024
+ assertOpaqueId(entry.payload.previous_process_instance_id, "previous run process instance id");
1025
+ return;
1026
+ case "outbox-lock-recovered":
1027
+ assertExactKeys(entry.payload, ["lock_recovery"], "trusted lock recovery entry payload");
1028
+ if (!isRecord(entry.payload.lock_recovery))
1029
+ throw new Error("trusted lock recovery payload must be an object");
1030
+ return;
1031
+ case "run-completed":
1032
+ assertExactKeys(entry.payload, [
1033
+ "authority",
1034
+ "expected_run_kind",
1035
+ "metrics_sha256",
1036
+ "minimum_duration_ms",
1037
+ "previous_canary",
1038
+ ], "trusted run completion payload");
1039
+ assertResolvedAuthority(entry.payload.authority);
1040
+ assertCertifyingRunKind(entry.payload.expected_run_kind);
1041
+ assertSha256(entry.payload.metrics_sha256, "certification metrics digest");
1042
+ return;
1043
+ default:
1044
+ throw new Error(`entry kind cannot carry trusted certification provenance: ${entry.entry_kind}`);
1045
+ }
1046
+ }
1047
+ function deriveMetrics(entries, certifying) {
1048
+ const first = requiredFirst(entries);
1049
+ const last = requiredLast(entries);
1050
+ const observations = entries.map(toObservation);
1051
+ const timeline = analyzeTimeline(observations);
1052
+ const checkpoints = entries.filter((entry, index) => (index === 0 || entry.entry.entry_kind === "heartbeat" || index === entries.length - 1));
1053
+ let maxHeartbeatGapMs = 0;
1054
+ for (let index = 1; index < checkpoints.length; index += 1) {
1055
+ const previous = checkpoints[index - 1];
1056
+ const current = checkpoints[index];
1057
+ if (previous && current) {
1058
+ maxHeartbeatGapMs = Math.max(maxHeartbeatGapMs, timestampEpoch(current.entry.recorded_at) - timestampEpoch(previous.entry.recorded_at));
1059
+ }
1060
+ }
1061
+ const terminal = last.entry.entry_kind;
1062
+ const status = terminal === "run-completed" ? "completed" : terminal === "run-aborted" ? "aborted" : "running";
1063
+ const metricsDigest = certifying
1064
+ ? assertSha256(last.entry.payload.metrics_sha256, "certification metrics digest")
1065
+ : sha256(canonicalJson({
1066
+ clock_class: first.entry.clock_class,
1067
+ elapsed_ms: timeline.elapsedMs,
1068
+ entry_count: entries.length,
1069
+ head_sha256: last.ref.sha256,
1070
+ monotonic_elapsed_ms: timeline.monotonicElapsedMs,
1071
+ policy_sha256: first.entry.policy_sha256,
1072
+ restart_continuity_valid: timeline.restartContinuityValid,
1073
+ run_id: first.entry.run_id,
1074
+ run_kind: first.entry.run_kind,
1075
+ status,
1076
+ }));
1077
+ return deepFreeze({
1078
+ run_id: first.entry.run_id,
1079
+ run_kind: first.entry.run_kind,
1080
+ policy_sha256: first.entry.policy_sha256,
1081
+ clock_class: first.entry.clock_class,
1082
+ certifying,
1083
+ status,
1084
+ started_at: first.entry.recorded_at,
1085
+ ended_at: status === "running" ? null : last.entry.recorded_at,
1086
+ elapsed_ms: timeline.elapsedMs,
1087
+ monotonic_elapsed_ms: timeline.monotonicElapsedMs,
1088
+ entry_count: entries.length,
1089
+ heartbeat_count: countKind(entries, "heartbeat"),
1090
+ max_heartbeat_gap_ms: maxHeartbeatGapMs,
1091
+ recovery_count: countKind(entries, "lease-recovered")
1092
+ + countKind(entries, "controller-lock-recovered")
1093
+ + countKind(entries, "outbox-lock-recovered"),
1094
+ quota_park_count: countKind(entries, "quota-parked"),
1095
+ quota_resume_count: countKind(entries, "quota-resumed"),
1096
+ restart_count: timeline.restartCount,
1097
+ restart_continuity_valid: timeline.restartContinuityValid,
1098
+ metrics_sha256: metricsDigest,
1099
+ process_instance_ids: timeline.processInstanceIds,
1100
+ ticket_set_digest: assertSha256(first.entry.payload.ticket_set_digest, "run ticket set digest"),
1101
+ service_plan_digest: assertSha256(first.entry.payload.service_plan_digest, "run service plan digest"),
1102
+ head: last.ref,
1103
+ });
1104
+ }
1105
+ export function analyzeRunContinuity(samples) {
1106
+ const captured = samples.map((sample, sequence) => ({
1107
+ entry_id: `sample-${sequence}`,
1108
+ sequence,
1109
+ entry_kind: assertTimelineEntryKind(sample.entryKind),
1110
+ operation_id: `sample-${sequence}`,
1111
+ recorded_at: assertTimestamp(sample.recordedAt),
1112
+ monotonic_ns: assertMonotonicNs(sample.monotonicNs),
1113
+ process_instance_id: assertOpaqueId(sample.processInstanceId, "run continuity process instance id"),
1114
+ boot_id: assertOpaqueId(sample.bootId, "run continuity boot id"),
1115
+ }));
1116
+ return analyzeTimeline(captured);
1117
+ }
1118
+ function analyzeTimeline(entries) {
1119
+ const first = entries[0];
1120
+ const last = entries.at(-1);
1121
+ if (!first || !last)
1122
+ throw new Error("cannot analyze an empty run timeline");
1123
+ const processIds = [first.process_instance_id];
1124
+ const seen = new Set(processIds);
1125
+ let monotonicElapsedMs = 0;
1126
+ let restartCount = 0;
1127
+ let valid = true;
1128
+ for (let index = 1; index < entries.length; index += 1) {
1129
+ const previous = entries[index - 1];
1130
+ const current = entries[index];
1131
+ if (!previous || !current)
1132
+ continue;
1133
+ const changed = current.process_instance_id !== previous.process_instance_id;
1134
+ const delta = BigInt(current.monotonic_ns) - BigInt(previous.monotonic_ns);
1135
+ if (current.boot_id !== previous.boot_id || delta <= 0n)
1136
+ valid = false;
1137
+ if (changed) {
1138
+ restartCount += 1;
1139
+ const gap = timestampEpoch(current.recorded_at) - timestampEpoch(previous.recorded_at);
1140
+ if (current.entry_kind !== "process-restarted"
1141
+ || gap < 0
1142
+ || gap > MAX_RESTART_GAP_MS
1143
+ || seen.has(current.process_instance_id)) {
1144
+ valid = false;
1145
+ }
1146
+ seen.add(current.process_instance_id);
1147
+ processIds.push(current.process_instance_id);
1148
+ }
1149
+ else {
1150
+ if (current.entry_kind === "process-restarted")
1151
+ valid = false;
1152
+ }
1153
+ }
1154
+ if (entries.every((entry) => entry.boot_id === first.boot_id)) {
1155
+ const totalMonotonic = BigInt(last.monotonic_ns) - BigInt(first.monotonic_ns);
1156
+ if (totalMonotonic > 0n)
1157
+ monotonicElapsedMs = Number(totalMonotonic / 1000000n);
1158
+ }
1159
+ return deepFreeze({
1160
+ elapsedMs: timestampEpoch(last.recorded_at) - timestampEpoch(first.recorded_at),
1161
+ monotonicElapsedMs,
1162
+ restartCount,
1163
+ restartContinuityValid: valid,
1164
+ processInstanceIds: processIds,
1165
+ });
1166
+ }
1167
+ function certificationDigest(entriesBeforeTerminal, terminal, authority, expectedRunKind, minimumDurationMs, previousCanary) {
1168
+ const first = requiredFirst(entriesBeforeTerminal);
1169
+ const observations = [...entriesBeforeTerminal.map(toObservation), terminal];
1170
+ const timeline = analyzeTimeline(observations);
1171
+ return sha256(canonicalJson({
1172
+ authority,
1173
+ boot_ids: [...new Set(observations.map((entry) => entry.boot_id))],
1174
+ ended_at: terminal.recorded_at,
1175
+ entry_count: observations.length,
1176
+ expected_run_kind: expectedRunKind,
1177
+ heartbeat_count: entriesBeforeTerminal.filter((entry) => entry.entry.entry_kind === "heartbeat").length,
1178
+ monotonic_elapsed_ms: timeline.monotonicElapsedMs,
1179
+ minimum_duration_ms: minimumDurationMs,
1180
+ policy_sha256: first.entry.policy_sha256,
1181
+ previous_canary: previousCanary,
1182
+ process_instance_ids: timeline.processInstanceIds,
1183
+ restart_continuity_valid: timeline.restartContinuityValid,
1184
+ restart_count: timeline.restartCount,
1185
+ run_id: first.entry.run_id,
1186
+ service_plan_digest: first.entry.payload.service_plan_digest,
1187
+ started_at: first.entry.recorded_at,
1188
+ terminal_entry_id: terminal.entry_id,
1189
+ terminal_operation_id: terminal.operation_id,
1190
+ terminal_previous_entry_sha256: requiredLast(entriesBeforeTerminal).ref.sha256,
1191
+ ticket_set_digest: first.entry.payload.ticket_set_digest,
1192
+ wall_elapsed_ms: timeline.elapsedMs,
1193
+ }));
1194
+ }
1195
+ function prospectiveObservation(entries, operationId, entryKind, sampled, clock) {
1196
+ const first = requiredFirst(entries);
1197
+ return deepFreeze({
1198
+ entry_id: createStableId("audit", first.entry.run_id, operationId),
1199
+ sequence: requiredLast(entries).entry.sequence + 1,
1200
+ entry_kind: entryKind,
1201
+ operation_id: operationId,
1202
+ recorded_at: sampled.wallTime,
1203
+ monotonic_ns: sampled.monotonicNs.toString(),
1204
+ process_instance_id: clock.processInstanceId,
1205
+ boot_id: clock.bootId,
1206
+ });
1207
+ }
1208
+ function toObservation(stored) {
1209
+ const entry = stored.entry;
1210
+ return deepFreeze({
1211
+ entry_id: entry.entry_id,
1212
+ sequence: entry.sequence,
1213
+ entry_kind: entry.entry_kind,
1214
+ operation_id: entry.operation_id,
1215
+ recorded_at: entry.recorded_at,
1216
+ monotonic_ns: entry.monotonic_ns,
1217
+ process_instance_id: entry.process_instance_id,
1218
+ boot_id: entry.boot_id,
1219
+ });
1220
+ }
1221
+ function assertMinimumDuration(timeline, minimumMs) {
1222
+ if (!timeline.restartContinuityValid)
1223
+ throw new Error("run restart continuity is not certifiable");
1224
+ if (timeline.elapsedMs < minimumMs)
1225
+ throw new Error("run wall duration is below its certification minimum");
1226
+ if (timeline.monotonicElapsedMs < minimumMs) {
1227
+ throw new Error("run monotonic segment duration is below its certification minimum");
1228
+ }
1229
+ }
1230
+ function findReplay(entries, operationId, operationDigest, clockClass, provenance) {
1231
+ const existing = entries.find((entry) => entry.entry.operation_id === operationId);
1232
+ if (!existing)
1233
+ return undefined;
1234
+ if (existing.entry.operation_sha256 !== operationDigest)
1235
+ throw new RunLedgerConflictError(operationId);
1236
+ if (existing.entry.clock_class !== clockClass || existing.entry.provenance_class !== provenance) {
1237
+ throw new Error("run ledger operation cannot be replayed through another clock or provenance class");
1238
+ }
1239
+ return existing;
1240
+ }
1241
+ function operationSha256(input) {
1242
+ return sha256(canonicalJson({
1243
+ clock_class: input.clockClass,
1244
+ entry_kind: input.entryKind,
1245
+ operation_id: input.operationId,
1246
+ payload: input.payload,
1247
+ policy_sha256: input.policyDigest,
1248
+ provenance_class: input.provenance,
1249
+ run_id: input.runId,
1250
+ run_kind: input.runKind,
1251
+ }));
1252
+ }
1253
+ function referenceFor(entry, bytes) {
1254
+ return deepFreeze({
1255
+ entry_id: entry.entry_id,
1256
+ run_id: entry.run_id,
1257
+ sequence: entry.sequence,
1258
+ sha256: sha256(bytes),
1259
+ file_name: `${entry.entry_id}.json`,
1260
+ });
1261
+ }
1262
+ function captureReference(value) {
1263
+ const captured = {
1264
+ entry_id: value.entry_id,
1265
+ run_id: value.run_id,
1266
+ sequence: value.sequence,
1267
+ sha256: value.sha256,
1268
+ file_name: value.file_name,
1269
+ };
1270
+ return validateReference(captured);
1271
+ }
1272
+ function validateReference(value) {
1273
+ if (!isRecord(value))
1274
+ throw new Error("run ledger reference must be an object");
1275
+ const entryId = value.entry_id;
1276
+ const runId = value.run_id;
1277
+ if (typeof entryId !== "string" || !ENTRY_ID_PATTERN.test(entryId))
1278
+ throw new Error("invalid run ledger reference entry id");
1279
+ assertRunId(runId);
1280
+ if (!Number.isSafeInteger(value.sequence) || value.sequence < 0)
1281
+ throw new Error("invalid run ledger reference sequence");
1282
+ assertSha256(value.sha256, "run ledger reference digest");
1283
+ if (value.file_name !== `${entryId}.json`)
1284
+ throw new Error("invalid run ledger reference filename");
1285
+ return deepFreeze({ entry_id: entryId, run_id: runId, sequence: value.sequence, sha256: value.sha256, file_name: value.file_name });
1286
+ }
1287
+ function captureStartInput(input) {
1288
+ return {
1289
+ operationId: input.operationId,
1290
+ runKind: input.runKind,
1291
+ policyDigest: input.policyDigest,
1292
+ ticketSetDigest: input.ticketSetDigest,
1293
+ servicePlanDigest: input.servicePlanDigest,
1294
+ };
1295
+ }
1296
+ function captureStartLiveInput(input) {
1297
+ return {
1298
+ operationId: input.operationId,
1299
+ runKind: input.runKind,
1300
+ policyDigest: input.policyDigest,
1301
+ ticketSetDigest: input.ticketSetDigest,
1302
+ servicePlanDigest: input.servicePlanDigest,
1303
+ source: captureAuthorityReference(input.source),
1304
+ previousCanary: input.previousCanary,
1305
+ };
1306
+ }
1307
+ function captureAppendInput(input) {
1308
+ return {
1309
+ runId: input.runId,
1310
+ operationId: input.operationId,
1311
+ entryKind: input.entryKind,
1312
+ payload: input.payload,
1313
+ };
1314
+ }
1315
+ function captureAppendAtHeadInput(input) {
1316
+ return {
1317
+ ...captureAppendInput(input),
1318
+ expectedHeadSha256: assertSha256(input.expectedHeadSha256, "expected run head digest"),
1319
+ };
1320
+ }
1321
+ function captureLiveEventInput(input) {
1322
+ return {
1323
+ runId: assertRunId(input.runId),
1324
+ operationId: assertOpaqueId(input.operationId, "run entry operation id"),
1325
+ source: captureAuthorityReference(input.source),
1326
+ };
1327
+ }
1328
+ function captureCompleteInput(input) {
1329
+ return {
1330
+ ...captureLiveEventInput(input),
1331
+ expectedRunKind: assertCertifyingRunKind(input.expectedRunKind),
1332
+ };
1333
+ }
1334
+ function captureAuthorityReference(input) {
1335
+ assertRunCertificationAuthorityReference(input);
1336
+ return input;
1337
+ }
1338
+ function captureCertificationReference(input) {
1339
+ const captured = {
1340
+ run_id: input.run_id,
1341
+ head: captureReference(input.head),
1342
+ metrics_sha256: input.metrics_sha256,
1343
+ };
1344
+ assertRunId(captured.run_id);
1345
+ if (captured.head.run_id !== captured.run_id)
1346
+ throw new Error("canary reference head belongs to another run");
1347
+ assertSha256(captured.metrics_sha256, "canary metrics digest");
1348
+ return deepFreeze(captured);
1349
+ }
1350
+ function serializeCertificationReference(input) {
1351
+ return deepFreeze({
1352
+ head: {
1353
+ entry_id: input.head.entry_id,
1354
+ file_name: input.head.file_name,
1355
+ run_id: input.head.run_id,
1356
+ sequence: input.head.sequence,
1357
+ sha256: input.head.sha256,
1358
+ },
1359
+ metrics_sha256: input.metrics_sha256,
1360
+ run_id: input.run_id,
1361
+ });
1362
+ }
1363
+ function parseCertificationReference(value) {
1364
+ if (!isRecord(value) || !isRecord(value.head))
1365
+ throw new Error("invalid prior canary reference");
1366
+ assertExactKeys(value, ["head", "metrics_sha256", "run_id"], "prior canary reference");
1367
+ const head = value.head;
1368
+ assertExactKeys(head, ["entry_id", "file_name", "run_id", "sequence", "sha256"], "prior canary head reference");
1369
+ return captureCertificationReference({
1370
+ run_id: value.run_id,
1371
+ metrics_sha256: value.metrics_sha256,
1372
+ head: {
1373
+ entry_id: head.entry_id,
1374
+ file_name: head.file_name,
1375
+ run_id: head.run_id,
1376
+ sequence: head.sequence,
1377
+ sha256: head.sha256,
1378
+ },
1379
+ });
1380
+ }
1381
+ function assertResolvedAuthority(value) {
1382
+ if (!isRecord(value))
1383
+ throw new Error("trusted authority payload must be an object");
1384
+ assertExactKeys(value, [
1385
+ "authorized_by",
1386
+ "binding",
1387
+ "decision_id",
1388
+ "event_sha256",
1389
+ "occurred_at",
1390
+ "processed_at",
1391
+ "receipt_sha256",
1392
+ "reference",
1393
+ ], "trusted authority payload");
1394
+ if (!isRecord(value.reference) || !isRecord(value.binding))
1395
+ throw new Error("trusted authority references are invalid");
1396
+ assertExactKeys(value.reference, ["consumer_id", "event_id"], "trusted authority reference");
1397
+ if (value.reference.consumer_id !== "run-ledger-authority")
1398
+ throw new Error("invalid trusted authority consumer");
1399
+ const binding = value.binding;
1400
+ assertExactKeys(binding, [
1401
+ "action",
1402
+ "completion_semantics",
1403
+ "expected_head",
1404
+ "operation_id",
1405
+ "policy_sha256",
1406
+ "previous_canary",
1407
+ "run_id",
1408
+ "run_kind",
1409
+ "schema_version",
1410
+ "service_plan_digest",
1411
+ "ticket_set_digest",
1412
+ ], "trusted authority binding");
1413
+ const action = binding.action;
1414
+ if (action !== "start" && action !== "heartbeat" && action !== "process-restart" && action !== "complete") {
1415
+ throw new Error("invalid trusted authority action");
1416
+ }
1417
+ const expectedSemantics = action === "start"
1418
+ ? "begin-run"
1419
+ : action === "heartbeat"
1420
+ ? "append-heartbeat"
1421
+ : action === "process-restart"
1422
+ ? "record-process-restart"
1423
+ : "terminal-run-completed";
1424
+ if (binding.schema_version !== 1 || binding.completion_semantics !== expectedSemantics) {
1425
+ throw new Error("trusted authority completion semantics changed");
1426
+ }
1427
+ assertRunId(binding.run_id);
1428
+ assertOpaqueId(binding.operation_id, "authorized run operation id");
1429
+ assertCertifyingRunKind(binding.run_kind);
1430
+ assertSha256(binding.policy_sha256, "authorized run policy digest");
1431
+ assertSha256(binding.ticket_set_digest, "authorized ticket set digest");
1432
+ assertSha256(binding.service_plan_digest, "authorized service plan digest");
1433
+ if (binding.expected_head !== null) {
1434
+ if (!isRecord(binding.expected_head))
1435
+ throw new Error("invalid trusted authority expected head");
1436
+ validateReference(binding.expected_head);
1437
+ }
1438
+ if (binding.previous_canary !== null)
1439
+ parseCertificationReference(binding.previous_canary);
1440
+ assertOpaqueId(value.reference.event_id, "authority event id");
1441
+ assertOpaqueId(value.decision_id, "authority decision id");
1442
+ assertActorId(value.authorized_by, "authority actor");
1443
+ assertSha256(value.event_sha256, "authority event digest");
1444
+ assertSha256(value.receipt_sha256, "authority receipt digest");
1445
+ assertTimestamp(value.occurred_at);
1446
+ assertTimestamp(value.processed_at);
1447
+ return deepFreeze(structuredClone(value));
1448
+ }
1449
+ function assertAuthorityAfterHead(authority, head) {
1450
+ if (timestampEpoch(authority.occurred_at) < timestampEpoch(head.recorded_at)) {
1451
+ throw new Error("live transition authority predates the canonical trusted head");
1452
+ }
1453
+ }
1454
+ function assertAuthorityPrecedesEntry(authority, entry) {
1455
+ if (timestampEpoch(authority.processed_at) > timestampEpoch(entry.recorded_at)) {
1456
+ throw new Error("live transition was recorded before its authority receipt");
1457
+ }
1458
+ }
1459
+ function countKind(entries, kind) {
1460
+ return entries.filter((entry) => entry.entry.entry_kind === kind).length;
1461
+ }
1462
+ function result(status, stored) {
1463
+ return deepFreeze({ status, ref: stored.ref, entry: stored.entry });
1464
+ }
1465
+ function requiredFirst(entries) {
1466
+ const first = entries[0];
1467
+ if (!first)
1468
+ throw new Error("run ledger does not exist");
1469
+ return first;
1470
+ }
1471
+ function requiredLast(entries) {
1472
+ const last = entries.at(-1);
1473
+ if (!last)
1474
+ throw new Error("run ledger does not exist");
1475
+ return last;
1476
+ }
1477
+ function runLockId(runId) {
1478
+ return assertLockId(`run-${assertRunId(runId)}`, "run ledger lock id");
1479
+ }
1480
+ function minimumDuration(kind) {
1481
+ return kind === "12h" ? TWELVE_HOURS_MS : TWENTY_FOUR_HOURS_MS;
1482
+ }
1483
+ function assertRunId(value) {
1484
+ if (typeof value !== "string" || !RUN_ID_PATTERN.test(value))
1485
+ throw new Error("invalid run id");
1486
+ return value;
1487
+ }
1488
+ function assertRunKind(value) {
1489
+ if (value !== "12h" && value !== "24h" && value !== "until-empty")
1490
+ throw new Error("invalid run kind");
1491
+ return value;
1492
+ }
1493
+ function assertCertifyingRunKind(value) {
1494
+ if (value !== "12h" && value !== "24h")
1495
+ throw new Error("invalid certifying run kind");
1496
+ return value;
1497
+ }
1498
+ function assertAppendEntryKind(value) {
1499
+ const kinds = [
1500
+ "heartbeat",
1501
+ "session-started",
1502
+ "session-stopped",
1503
+ "session-stalled",
1504
+ "process-restarted",
1505
+ "ticket-transition",
1506
+ "quota-parked",
1507
+ "quota-resumed",
1508
+ "lease-recovered",
1509
+ "controller-lock-recovered",
1510
+ "outbox-lock-recovered",
1511
+ "live-call-observed",
1512
+ "run-completed",
1513
+ "run-aborted",
1514
+ ];
1515
+ if (!kinds.includes(value))
1516
+ throw new Error("invalid append run entry kind");
1517
+ return value;
1518
+ }
1519
+ function assertTimelineEntryKind(value) {
1520
+ return value === "run-started" ? value : assertAppendEntryKind(value);
1521
+ }
1522
+ function assertMonotonicNs(value) {
1523
+ if (typeof value !== "string" || !/^(?:0|[1-9][0-9]*)$/.test(value)) {
1524
+ throw new Error("invalid run continuity monotonic time");
1525
+ }
1526
+ return value;
1527
+ }
1528
+ function isTerminal(kind) {
1529
+ return kind === "run-completed" || kind === "run-aborted";
1530
+ }
1531
+ function sameReference(left, right) {
1532
+ return left.entry_id === right.entry_id
1533
+ && left.run_id === right.run_id
1534
+ && left.sequence === right.sequence
1535
+ && left.sha256 === right.sha256
1536
+ && left.file_name === right.file_name;
1537
+ }
1538
+ function sampleClock(clock) {
1539
+ const sample = clock.sample();
1540
+ if (!isRecord(sample))
1541
+ throw new Error("run ledger clock sample must be an object");
1542
+ const wallTime = assertTimestamp(sample.wallTime);
1543
+ if (typeof sample.monotonicNs !== "bigint" || sample.monotonicNs < 0n) {
1544
+ throw new Error("run ledger monotonic clock must be a non-negative bigint");
1545
+ }
1546
+ return { wallTime, monotonicNs: sample.monotonicNs };
1547
+ }
1548
+ function assertClockAdvances(previous, sampled, clock) {
1549
+ if (timestampEpoch(sampled.wallTime) < timestampEpoch(previous.recorded_at)) {
1550
+ throw new Error("run ledger wall clock moved backwards");
1551
+ }
1552
+ if (previous.boot_id === clock.bootId && sampled.monotonicNs <= BigInt(previous.monotonic_ns)) {
1553
+ throw new Error("run ledger system monotonic clock did not advance");
1554
+ }
1555
+ }
1556
+ function assertExactKeys(value, expected, label) {
1557
+ const actual = Object.keys(value).sort();
1558
+ const sortedExpected = [...expected].sort();
1559
+ if (actual.length !== sortedExpected.length || actual.some((key, index) => key !== sortedExpected[index])) {
1560
+ throw new Error(`${label} has invalid fields`);
1561
+ }
1562
+ }
1563
+ function assertSha256(value, label) {
1564
+ if (typeof value !== "string" || !SHA256_PATTERN.test(value))
1565
+ throw new Error(`invalid ${label}`);
1566
+ return value;
1567
+ }
1568
+ function assertTimestamp(value) {
1569
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) {
1570
+ throw new Error("invalid run ledger timestamp");
1571
+ }
1572
+ const epoch = Date.parse(value);
1573
+ if (!Number.isFinite(epoch) || new Date(epoch).toISOString() !== value)
1574
+ throw new Error("invalid run ledger timestamp");
1575
+ return value;
1576
+ }
1577
+ function timestampEpoch(value) {
1578
+ assertTimestamp(value);
1579
+ return Date.parse(value);
1580
+ }
1581
+ function canonicalJson(value) {
1582
+ return Buffer.from(`${JSON.stringify(canonicalizeJson(value), null, 2)}\n`, "utf8");
1583
+ }
1584
+ function canonicalizeObject(value, label) {
1585
+ const canonical = canonicalizeJson(value, label);
1586
+ if (!isRecord(canonical) || Array.isArray(canonical))
1587
+ throw new Error(`${label} must be an object`);
1588
+ return canonical;
1589
+ }
1590
+ function canonicalizeJson(value, label = "JSON value", seen = new Set(), depth = 0) {
1591
+ if (depth > 32)
1592
+ throw new Error(`${label} exceeds maximum nesting depth`);
1593
+ if (value === null || typeof value === "string" || typeof value === "boolean")
1594
+ return value;
1595
+ if (typeof value === "number") {
1596
+ if (!Number.isFinite(value))
1597
+ throw new Error(`${label} contains a non-finite number`);
1598
+ return value;
1599
+ }
1600
+ if (Array.isArray(value)) {
1601
+ if (seen.has(value))
1602
+ throw new Error(`${label} contains a cycle`);
1603
+ seen.add(value);
1604
+ const result = value.map((item) => canonicalizeJson(item, label, seen, depth + 1));
1605
+ seen.delete(value);
1606
+ return result;
1607
+ }
1608
+ if (!isRecord(value))
1609
+ throw new Error(`${label} contains a non-JSON value`);
1610
+ if (seen.has(value))
1611
+ throw new Error(`${label} contains a cycle`);
1612
+ seen.add(value);
1613
+ const result = {};
1614
+ for (const key of Object.keys(value).sort()) {
1615
+ const child = value[key];
1616
+ if (child === undefined)
1617
+ throw new Error(`${label} contains undefined`);
1618
+ result[key] = canonicalizeJson(child, label, seen, depth + 1);
1619
+ }
1620
+ seen.delete(value);
1621
+ return result;
1622
+ }
1623
+ function assertJsonValue(value, label) {
1624
+ canonicalizeJson(value, label);
1625
+ }
1626
+ function parseJson(bytes) {
1627
+ try {
1628
+ return JSON.parse(bytes.toString("utf8"));
1629
+ }
1630
+ catch {
1631
+ throw new Error("run ledger entry contains invalid JSON");
1632
+ }
1633
+ }
1634
+ function sha256(bytes) {
1635
+ return createHash("sha256").update(bytes).digest("hex");
1636
+ }
1637
+ function sameJson(left, right) {
1638
+ try {
1639
+ return canonicalJson(left).equals(canonicalJson(right));
1640
+ }
1641
+ catch {
1642
+ return false;
1643
+ }
1644
+ }
1645
+ function deepFreeze(value, seen = new Set()) {
1646
+ if (typeof value !== "object" || value === null || seen.has(value))
1647
+ return value;
1648
+ if (ArrayBuffer.isView(value))
1649
+ return value;
1650
+ seen.add(value);
1651
+ for (const child of Object.values(value))
1652
+ deepFreeze(child, seen);
1653
+ return Object.freeze(value);
1654
+ }
1655
+ //# sourceMappingURL=run-ledger.js.map