@coreplane/switchboard 1.204.0 → 1.206.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 (389) hide show
  1. package/dist/assets/Dockerfile +6 -3
  2. package/dist/assets/config/config.example.yaml +56 -4
  3. package/dist/assets/deploy/cloudflare/coordinator.ts +86 -0
  4. package/dist/assets/deploy/cloudflare/shared.ts +9 -0
  5. package/dist/assets/deploy/cloudflare/worker.ts +142 -6
  6. package/dist/assets/deploy/cloudflare/wrangler.template.jsonc +9 -0
  7. package/dist/assets/deploy/cloudflare-memory/worker.ts +381 -7
  8. package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +22 -2
  9. package/dist/assets/deploy/cloudflare-resident/Dockerfile +89 -3
  10. package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +81 -10
  11. package/dist/assets/deploy/cloudflare-sandbox/worker.ts +4 -1
  12. package/dist/assets/deploy/secrets.manifest.json +1 -1
  13. package/dist/assets/package-lock.json +291 -97
  14. package/dist/assets/package.json +1 -1
  15. package/dist/assets/source.json +3 -3
  16. package/dist/assets/src/agents/registry.ts +517 -0
  17. package/dist/assets/src/config/profile.ts +229 -0
  18. package/dist/assets/src/core/authz/policy.ts +15 -0
  19. package/dist/assets/src/core/coordinator/contract.ts +239 -0
  20. package/dist/assets/src/core/coordinator/driver.ts +501 -0
  21. package/dist/assets/src/core/coordinator/instancesRoute.ts +181 -0
  22. package/dist/assets/src/core/delivery.ts +829 -0
  23. package/dist/assets/src/core/deliverySnapshotStore.ts +260 -0
  24. package/dist/assets/src/core/reviewVerdict.ts +296 -0
  25. package/dist/assets/src/core/reviewedHead.ts +77 -0
  26. package/dist/assets/src/core/runEvents.ts +4 -2
  27. package/dist/assets/src/core/runLedger/decisions.ts +2 -1
  28. package/dist/assets/src/core/runLedger/types.ts +23 -1
  29. package/dist/assets/src/core/runRecord.ts +100 -0
  30. package/dist/assets/src/core/ship/contract.ts +502 -0
  31. package/dist/assets/src/core/ship/coordinator.ts +1039 -0
  32. package/dist/assets/src/core/workerError.ts +12 -0
  33. package/dist/assets/web/dist/.vite/manifest.json +2770 -70
  34. package/dist/assets/web/dist/assets/AppShell-lYVcj-k7.js +1 -0
  35. package/dist/assets/web/dist/assets/{CostsPage-XQ-fQzdz.js → CostsPage-DGSE48l1.js} +1 -1
  36. package/dist/assets/web/dist/assets/DeliveryPage-CvlWP7Eq.js +1 -0
  37. package/dist/assets/web/dist/assets/NotFoundPage-BzWS4Ca1.js +1 -0
  38. package/dist/assets/web/dist/assets/ResidentDetailPage-D3P21yeI.js +1 -0
  39. package/dist/assets/web/dist/assets/ResidentsIndexPage-DOYqnZ1q.js +1 -0
  40. package/dist/assets/web/dist/assets/RunRoutePage-OmvrvPXY.js +12 -0
  41. package/dist/assets/web/dist/assets/RunRoutePage-f6TSNPeW.css +1 -0
  42. package/dist/assets/web/dist/assets/RunsIndexPage-DWbSQtL4.js +1 -0
  43. package/dist/assets/web/dist/assets/RunsTabs-DEICQ4FY.js +1 -0
  44. package/dist/assets/web/dist/assets/ScheduledPage-CPKfJ4mR.js +1 -0
  45. package/dist/assets/web/dist/assets/StatusDot-COr8jTyM.js +1 -0
  46. package/dist/assets/web/dist/assets/Tooltip-fOqTZkNT.js +1 -0
  47. package/dist/assets/web/dist/assets/abap-CLvhMVsD.js +1 -0
  48. package/dist/assets/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
  49. package/dist/assets/web/dist/assets/ada-C5qYipkI.js +1 -0
  50. package/dist/assets/web/dist/assets/ahk-CA9gdOdF.js +1 -0
  51. package/dist/assets/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
  52. package/dist/assets/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  53. package/dist/assets/web/dist/assets/angular-html-Cw130Zyi.js +1 -0
  54. package/dist/assets/web/dist/assets/angular-ts-DY1m4C2s.js +1 -0
  55. package/dist/assets/web/dist/assets/apache-U0d_L8uA.js +1 -0
  56. package/dist/assets/web/dist/assets/apex-KFGFyKwG.js +1 -0
  57. package/dist/assets/web/dist/assets/apl-W6Ty7v05.js +1 -0
  58. package/dist/assets/web/dist/assets/applescript-CCn79oCD.js +1 -0
  59. package/dist/assets/web/dist/assets/ara-4CJ0cIlV.js +1 -0
  60. package/dist/assets/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
  61. package/dist/assets/web/dist/assets/asm-Cmm7eHzH.js +1 -0
  62. package/dist/assets/web/dist/assets/astro-DlLVHwVk.js +1 -0
  63. package/dist/assets/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  64. package/dist/assets/web/dist/assets/awk-BWXHIvNe.js +1 -0
  65. package/dist/assets/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  66. package/dist/assets/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  67. package/dist/assets/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  68. package/dist/assets/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  69. package/dist/assets/web/dist/assets/bat-qdxAdBWQ.js +1 -0
  70. package/dist/assets/web/dist/assets/beancount-D-usSTwE.js +1 -0
  71. package/dist/assets/web/dist/assets/berry-DKpUyyne.js +1 -0
  72. package/dist/assets/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  73. package/dist/assets/web/dist/assets/bicep-CUHmPFLl.js +1 -0
  74. package/dist/assets/web/dist/assets/bird2-CYIUgYqB.js +1 -0
  75. package/dist/assets/web/dist/assets/blade-Bco3tDh4.js +1 -0
  76. package/dist/assets/web/dist/assets/bsl-_VjlB9mv.js +1 -0
  77. package/dist/assets/web/dist/assets/c-1LaFY_cj.js +1 -0
  78. package/dist/assets/web/dist/assets/c3-D0apG41V.js +1 -0
  79. package/dist/assets/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
  80. package/dist/assets/web/dist/assets/cairo-DLTphjLi.js +1 -0
  81. package/dist/assets/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  82. package/dist/assets/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  83. package/dist/assets/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  84. package/dist/assets/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  85. package/dist/assets/web/dist/assets/chapel-B-OvrOL5.js +1 -0
  86. package/dist/assets/web/dist/assets/clarity-CtfBBspg.js +1 -0
  87. package/dist/assets/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
  88. package/dist/assets/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
  89. package/dist/assets/web/dist/assets/cobol-Ch3EPlHu.js +1 -0
  90. package/dist/assets/web/dist/assets/codeowners-C8r90Shi.js +1 -0
  91. package/dist/assets/web/dist/assets/codeql-oeQT6MSM.js +1 -0
  92. package/dist/assets/web/dist/assets/coffee-hZuITnto.js +1 -0
  93. package/dist/assets/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  94. package/dist/assets/web/dist/assets/coq-DuBRPxYg.js +1 -0
  95. package/dist/assets/web/dist/assets/cpp-B8iRVo8m.js +1 -0
  96. package/dist/assets/web/dist/assets/crystal-DKveI7lr.js +1 -0
  97. package/dist/assets/web/dist/assets/csharp-oqKa8noW.js +1 -0
  98. package/dist/assets/web/dist/assets/css-_2_CD-rr.js +1 -0
  99. package/dist/assets/web/dist/assets/csv-Dx-8-gkx.js +1 -0
  100. package/dist/assets/web/dist/assets/cue-CE9AQfxI.js +1 -0
  101. package/dist/assets/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
  102. package/dist/assets/web/dist/assets/d-qD-0Kul2.js +1 -0
  103. package/dist/assets/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  104. package/dist/assets/web/dist/assets/dart-CnvKMtbv.js +1 -0
  105. package/dist/assets/web/dist/assets/dax-BkyTk9wS.js +1 -0
  106. package/dist/assets/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
  107. package/dist/assets/web/dist/assets/diff-woXpYk--.js +1 -0
  108. package/dist/assets/web/dist/assets/dist-BVjAWgkb.js +1547 -0
  109. package/dist/assets/web/dist/assets/docker-IyjqRm3v.js +1 -0
  110. package/dist/assets/web/dist/assets/dotenv-_5a1GRtc.js +1 -0
  111. package/dist/assets/web/dist/assets/dracula-BHWKrbxM.js +1 -0
  112. package/dist/assets/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  113. package/dist/assets/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  114. package/dist/assets/web/dist/assets/edge-BenMDPct.js +1 -0
  115. package/dist/assets/web/dist/assets/elixir-CBbdEqG6.js +1 -0
  116. package/dist/assets/web/dist/assets/elm-DMub_2lY.js +1 -0
  117. package/dist/assets/web/dist/assets/emacs-lisp-c_oH4hRZ.js +1 -0
  118. package/dist/assets/web/dist/assets/erb-CI9He_Up.js +1 -0
  119. package/dist/assets/web/dist/assets/erlang-Cphh6RMH.js +1 -0
  120. package/dist/assets/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  121. package/dist/assets/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  122. package/dist/assets/web/dist/assets/{favicon-CFfbl2AI.js → favicon-CWPcvWvp.js} +1 -1
  123. package/dist/assets/web/dist/assets/fennel-DQxkIbk2.js +1 -0
  124. package/dist/assets/web/dist/assets/fish-BJitypiv.js +1 -0
  125. package/dist/assets/web/dist/assets/fluent-C03EYrpw.js +1 -0
  126. package/dist/assets/web/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  127. package/dist/assets/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  128. package/dist/assets/web/dist/assets/fsharp-D13ZGOAj.js +1 -0
  129. package/dist/assets/web/dist/assets/gdresource-xMC7jr7x.js +1 -0
  130. package/dist/assets/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
  131. package/dist/assets/web/dist/assets/gdshader-CBce3t8t.js +1 -0
  132. package/dist/assets/web/dist/assets/genie-CV2tkWYe.js +1 -0
  133. package/dist/assets/web/dist/assets/gherkin-DExj1W_8.js +1 -0
  134. package/dist/assets/web/dist/assets/git-commit-BSykSTBG.js +1 -0
  135. package/dist/assets/web/dist/assets/git-rebase-BBfhnyF8.js +1 -0
  136. package/dist/assets/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
  137. package/dist/assets/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  138. package/dist/assets/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  139. package/dist/assets/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  140. package/dist/assets/web/dist/assets/github-light-EUqPIrTm.js +1 -0
  141. package/dist/assets/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
  142. package/dist/assets/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  143. package/dist/assets/web/dist/assets/gleam-CSRkHgEL.js +1 -0
  144. package/dist/assets/web/dist/assets/glimmer-js-CR9Fxrau.js +1 -0
  145. package/dist/assets/web/dist/assets/glimmer-ts-CwFwX_XS.js +1 -0
  146. package/dist/assets/web/dist/assets/glsl-Daiodp1r.js +1 -0
  147. package/dist/assets/web/dist/assets/gn-ilITqXS6.js +1 -0
  148. package/dist/assets/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
  149. package/dist/assets/web/dist/assets/go-rLFTqkRN.js +1 -0
  150. package/dist/assets/web/dist/assets/graphql-DZvrsEkB.js +1 -0
  151. package/dist/assets/web/dist/assets/groovy-CacY0gHj.js +1 -0
  152. package/dist/assets/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  153. package/dist/assets/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  154. package/dist/assets/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  155. package/dist/assets/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  156. package/dist/assets/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  157. package/dist/assets/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  158. package/dist/assets/web/dist/assets/hack-BaXOzdg7.js +1 -0
  159. package/dist/assets/web/dist/assets/haml-CMskq_VA.js +1 -0
  160. package/dist/assets/web/dist/assets/handlebars-Dhw_f7jD.js +1 -0
  161. package/dist/assets/web/dist/assets/haskell-D8IpX4py.js +1 -0
  162. package/dist/assets/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
  163. package/dist/assets/web/dist/assets/hcl-Dh228itO.js +1 -0
  164. package/dist/assets/web/dist/assets/hjson-CxZEssPk.js +1 -0
  165. package/dist/assets/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  166. package/dist/assets/web/dist/assets/horizon-CE9ld1lL.js +1 -0
  167. package/dist/assets/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  168. package/dist/assets/web/dist/assets/houston-CsvMBhTu.js +1 -0
  169. package/dist/assets/web/dist/assets/html-C9k99z0z.js +1 -0
  170. package/dist/assets/web/dist/assets/html-derivative-VckPItXN.js +1 -0
  171. package/dist/assets/web/dist/assets/http-DXU_3h9v.js +1 -0
  172. package/dist/assets/web/dist/assets/hurl-KU24JNdp.js +1 -0
  173. package/dist/assets/web/dist/assets/hxml-Dcm88srW.js +1 -0
  174. package/dist/assets/web/dist/assets/hy-CZbG8q4J.js +1 -0
  175. package/dist/assets/web/dist/assets/imba-DsUTQ-LC.js +1 -0
  176. package/dist/assets/web/dist/assets/indexFormat-B-pd4r7Y.js +1 -0
  177. package/dist/assets/web/dist/assets/ini-B5eOa1yu.js +1 -0
  178. package/dist/assets/web/dist/assets/java-BbZjTNgf.js +1 -0
  179. package/dist/assets/web/dist/assets/javascript-DXCdqcZZ.js +1 -0
  180. package/dist/assets/web/dist/assets/jinja-pDlsjTVp.js +1 -0
  181. package/dist/assets/web/dist/assets/jison-DsXDk66e.js +1 -0
  182. package/dist/assets/web/dist/assets/json-tCxXfRgG.js +1 -0
  183. package/dist/assets/web/dist/assets/json5-BR5RXkoi.js +1 -0
  184. package/dist/assets/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  185. package/dist/assets/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  186. package/dist/assets/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  187. package/dist/assets/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
  188. package/dist/assets/web/dist/assets/jsx-B0ZYkCmi.js +1 -0
  189. package/dist/assets/web/dist/assets/julia-BLzzeI6x.js +1 -0
  190. package/dist/assets/web/dist/assets/just-DvD2_60c.js +1 -0
  191. package/dist/assets/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  192. package/dist/assets/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  193. package/dist/assets/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
  194. package/dist/assets/web/dist/assets/kdl-CsD5j6eV.js +1 -0
  195. package/dist/assets/web/dist/assets/kotlin-DhhofPvG.js +1 -0
  196. package/dist/assets/web/dist/assets/kusto-C7mF5XQf.js +1 -0
  197. package/dist/assets/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
  198. package/dist/assets/web/dist/assets/latex-511zn3h7.js +1 -0
  199. package/dist/assets/web/dist/assets/lean-CewbzKMR.js +1 -0
  200. package/dist/assets/web/dist/assets/less-DVTAwKKz.js +1 -0
  201. package/dist/assets/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
  202. package/dist/assets/web/dist/assets/liquid-BD0BkMox.js +1 -0
  203. package/dist/assets/web/dist/assets/llvm-DqHOMS-s.js +1 -0
  204. package/dist/assets/web/dist/assets/log-BNLmms1o.js +1 -0
  205. package/dist/assets/web/dist/assets/logo-Cluzi2Zq.js +1 -0
  206. package/dist/assets/web/dist/assets/lua-NFicKm6N.js +1 -0
  207. package/dist/assets/web/dist/assets/luau-C2pBCSgt.js +1 -0
  208. package/dist/assets/web/dist/assets/main-CuENKPdD.css +1 -0
  209. package/dist/assets/web/dist/assets/main-DZbJaqUb.js +28 -0
  210. package/dist/assets/web/dist/assets/make-Dixweg8N.js +1 -0
  211. package/dist/assets/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  212. package/dist/assets/web/dist/assets/marko-1-G_nEXK.js +1 -0
  213. package/dist/assets/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  214. package/dist/assets/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  215. package/dist/assets/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  216. package/dist/assets/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  217. package/dist/assets/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  218. package/dist/assets/web/dist/assets/matlab-D7qyCx1q.js +1 -0
  219. package/dist/assets/web/dist/assets/mdc-DUe3AQLp.js +1 -0
  220. package/dist/assets/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
  221. package/dist/assets/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
  222. package/dist/assets/web/dist/assets/min-dark-BSWPekZh.js +1 -0
  223. package/dist/assets/web/dist/assets/min-light-DDpmG2fV.js +1 -0
  224. package/dist/assets/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  225. package/dist/assets/web/dist/assets/mojo-fg40_ntQ.js +1 -0
  226. package/dist/assets/web/dist/assets/monokai-B_bV9PQ9.js +1 -0
  227. package/dist/assets/web/dist/assets/moonbit-DgjMoLWr.js +1 -0
  228. package/dist/assets/web/dist/assets/move-BYnGhZoM.js +1 -0
  229. package/dist/assets/web/dist/assets/narrat-_X_XdTYD.js +1 -0
  230. package/dist/assets/web/dist/assets/nextflow-BJtWHP5T.js +1 -0
  231. package/dist/assets/web/dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
  232. package/dist/assets/web/dist/assets/nginx-Dg379_bA.js +1 -0
  233. package/dist/assets/web/dist/assets/night-owl-DhmEMT88.js +1 -0
  234. package/dist/assets/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  235. package/dist/assets/web/dist/assets/nim-BnFq97ZO.js +1 -0
  236. package/dist/assets/web/dist/assets/nix-IvuFDN5E.js +1 -0
  237. package/dist/assets/web/dist/assets/nord-Cb4Vim4T.js +1 -0
  238. package/dist/assets/web/dist/assets/nsis-CjNLYCLg.js +1 -0
  239. package/dist/assets/web/dist/assets/nushell-DHYfO66s.js +1 -0
  240. package/dist/assets/web/dist/assets/objective-c-D1A_Heim.js +1 -0
  241. package/dist/assets/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  242. package/dist/assets/web/dist/assets/ocaml-O90oeIOV.js +1 -0
  243. package/dist/assets/web/dist/assets/odin-B1RWQWA5.js +1 -0
  244. package/dist/assets/web/dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
  245. package/dist/assets/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
  246. package/dist/assets/web/dist/assets/openscad-BUDT5pXO.js +1 -0
  247. package/dist/assets/web/dist/assets/org-F0uiwGvq.js +1 -0
  248. package/dist/assets/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
  249. package/dist/assets/web/dist/assets/perl-B0euczkl.js +1 -0
  250. package/dist/assets/web/dist/assets/php-DkL3k_n7.js +1 -0
  251. package/dist/assets/web/dist/assets/pierre-dark-CpLgRqie.js +1 -0
  252. package/dist/assets/web/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  253. package/dist/assets/web/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  254. package/dist/assets/web/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  255. package/dist/assets/web/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  256. package/dist/assets/web/dist/assets/pierre-light-CoaEpmwp.js +1 -0
  257. package/dist/assets/web/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  258. package/dist/assets/web/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  259. package/dist/assets/web/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  260. package/dist/assets/web/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  261. package/dist/assets/web/dist/assets/pkl-ot-7Btpt.js +1 -0
  262. package/dist/assets/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
  263. package/dist/assets/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
  264. package/dist/assets/web/dist/assets/po-BiJDBrnU.js +1 -0
  265. package/dist/assets/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
  266. package/dist/assets/web/dist/assets/polar-C7UOKdEL.js +1 -0
  267. package/dist/assets/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
  268. package/dist/assets/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
  269. package/dist/assets/web/dist/assets/powershell-CCSX75yM.js +1 -0
  270. package/dist/assets/web/dist/assets/prisma-DMgNhnYS.js +1 -0
  271. package/dist/assets/web/dist/assets/prolog-iXnhIJG7.js +1 -0
  272. package/dist/assets/web/dist/assets/proto-DB4EqR-F.js +1 -0
  273. package/dist/assets/web/dist/assets/pug-L_OjjZP4.js +1 -0
  274. package/dist/assets/web/dist/assets/puppet-CDv2pdJW.js +1 -0
  275. package/dist/assets/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
  276. package/dist/assets/web/dist/assets/python-gzcpVVnB.js +1 -0
  277. package/dist/assets/web/dist/assets/qml-BMjB00Zz.js +1 -0
  278. package/dist/assets/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
  279. package/dist/assets/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
  280. package/dist/assets/web/dist/assets/r-DoeLdnqR.js +1 -0
  281. package/dist/assets/web/dist/assets/racket-DcIDlBhZ.js +1 -0
  282. package/dist/assets/web/dist/assets/raku-B3gFvitq.js +1 -0
  283. package/dist/assets/web/dist/assets/razor-BzYNkAWy.js +1 -0
  284. package/dist/assets/web/dist/assets/rbs-C0MpOrj4.js +1 -0
  285. package/dist/assets/web/dist/assets/red-DX1BQOs8.js +1 -0
  286. package/dist/assets/web/dist/assets/reg-CRGYupPL.js +1 -0
  287. package/dist/assets/web/dist/assets/regexp-CPmElMk3.js +1 -0
  288. package/dist/assets/web/dist/assets/rel-BtDbiS_P.js +1 -0
  289. package/dist/assets/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
  290. package/dist/assets/web/dist/assets/ron-VUp2lXgN.js +1 -0
  291. package/dist/assets/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
  292. package/dist/assets/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  293. package/dist/assets/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  294. package/dist/assets/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
  295. package/dist/assets/web/dist/assets/rst-DrsjgfI7.js +1 -0
  296. package/dist/assets/web/dist/assets/ruby-cB42ppvx.js +1 -0
  297. package/dist/assets/web/dist/assets/rust-Cfkwpbl8.js +1 -0
  298. package/dist/assets/web/dist/assets/sas-R5Y3NM_I.js +1 -0
  299. package/dist/assets/web/dist/assets/sass-DXrisJhu.js +1 -0
  300. package/dist/assets/web/dist/assets/scala-BiFt_7mu.js +1 -0
  301. package/dist/assets/web/dist/assets/scheme-DQCgrYNe.js +1 -0
  302. package/dist/assets/web/dist/assets/scss-C-zWxV9s.js +1 -0
  303. package/dist/assets/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
  304. package/dist/assets/web/dist/assets/seed-DeKSHrt2.js +6 -0
  305. package/dist/assets/web/dist/assets/shaderlab-TOUzSsQk.js +1 -0
  306. package/dist/assets/web/dist/assets/shellscript-oJF96aAU.js +1 -0
  307. package/dist/assets/web/dist/assets/shellsession-CznECKyi.js +1 -0
  308. package/dist/assets/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
  309. package/dist/assets/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  310. package/dist/assets/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
  311. package/dist/assets/web/dist/assets/smithy-Dgaz7aur.js +1 -0
  312. package/dist/assets/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  313. package/dist/assets/web/dist/assets/solarized-dark-UdsmMDCL.js +1 -0
  314. package/dist/assets/web/dist/assets/solarized-light-C1IUL_tW.js +1 -0
  315. package/dist/assets/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
  316. package/dist/assets/web/dist/assets/soy-wjHLSRag.js +1 -0
  317. package/dist/assets/web/dist/assets/sparql-D_iOobhT.js +1 -0
  318. package/dist/assets/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
  319. package/dist/assets/web/dist/assets/sql-C_BM8IOW.js +1 -0
  320. package/dist/assets/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  321. package/dist/assets/web/dist/assets/stata-D31HO8aQ.js +1 -0
  322. package/dist/assets/web/dist/assets/stylus-B6D30XZt.js +1 -0
  323. package/dist/assets/web/dist/assets/surrealql-CKCLyplA.js +1 -0
  324. package/dist/assets/web/dist/assets/svelte-3geWk2iu.js +1 -0
  325. package/dist/assets/web/dist/assets/swift-CyEgAFGc.js +1 -0
  326. package/dist/assets/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  327. package/dist/assets/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
  328. package/dist/assets/web/dist/assets/systemd-BxMlprV5.js +1 -0
  329. package/dist/assets/web/dist/assets/talonscript-CohzipZa.js +1 -0
  330. package/dist/assets/web/dist/assets/tasl-DMoTqEGO.js +1 -0
  331. package/dist/assets/web/dist/assets/tcl-CZd0xW_V.js +1 -0
  332. package/dist/assets/web/dist/assets/templ-DiJr_hTD.js +1 -0
  333. package/dist/assets/web/dist/assets/terraform-DswuEJGm.js +1 -0
  334. package/dist/assets/web/dist/assets/tex-BEJGWyeF.js +1 -0
  335. package/dist/assets/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  336. package/dist/assets/web/dist/assets/toml-CcmNWLt0.js +1 -0
  337. package/dist/assets/web/dist/assets/ts-tags-C8Mzdpxx.js +1 -0
  338. package/dist/assets/web/dist/assets/tsv-sltzmVWM.js +1 -0
  339. package/dist/assets/web/dist/assets/tsx-UQiSm_p3.js +1 -0
  340. package/dist/assets/web/dist/assets/turtle-ByJddavk.js +1 -0
  341. package/dist/assets/web/dist/assets/twig-_Pvzkeo9.js +1 -0
  342. package/dist/assets/web/dist/assets/typescript-BWMkINKK.js +1 -0
  343. package/dist/assets/web/dist/assets/typespec-BRdr0IET.js +1 -0
  344. package/dist/assets/web/dist/assets/typst-Bdg9m-e7.js +1 -0
  345. package/dist/assets/web/dist/assets/v-DBj4K9d_.js +1 -0
  346. package/dist/assets/web/dist/assets/vala-zf12oZj6.js +1 -0
  347. package/dist/assets/web/dist/assets/vb-DOwzaHZ0.js +1 -0
  348. package/dist/assets/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
  349. package/dist/assets/web/dist/assets/vesper-DdrHHSXu.js +1 -0
  350. package/dist/assets/web/dist/assets/vhdl-BroJfC0k.js +1 -0
  351. package/dist/assets/web/dist/assets/viml-DvXPmvsu.js +1 -0
  352. package/dist/assets/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  353. package/dist/assets/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  354. package/dist/assets/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  355. package/dist/assets/web/dist/assets/vue-CDZrrAZ8.js +1 -0
  356. package/dist/assets/web/dist/assets/vue-html-tAlpNhfN.js +1 -0
  357. package/dist/assets/web/dist/assets/vue-vine-C27UF_rh.js +1 -0
  358. package/dist/assets/web/dist/assets/vyper-CgoNMtux.js +1 -0
  359. package/dist/assets/web/dist/assets/wallClock-61NpVkY6.js +1 -0
  360. package/dist/assets/web/dist/assets/wasm-BnjxR4X6.js +1 -0
  361. package/dist/assets/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
  362. package/dist/assets/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
  363. package/dist/assets/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
  364. package/dist/assets/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
  365. package/dist/assets/web/dist/assets/wit-DdvCle-K.js +1 -0
  366. package/dist/assets/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
  367. package/dist/assets/web/dist/assets/xml-CxeDr9Zh.js +1 -0
  368. package/dist/assets/web/dist/assets/xsl-Bpi0uUnM.js +1 -0
  369. package/dist/assets/web/dist/assets/yaml-BtUgZ1GN.js +1 -0
  370. package/dist/assets/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
  371. package/dist/assets/web/dist/assets/zig-CMLA9XwU.js +1 -0
  372. package/dist/cli.js +6936 -3347
  373. package/package.json +1 -1
  374. package/dist/assets/web/dist/assets/AppShell-Bw3-_TTE.js +0 -1
  375. package/dist/assets/web/dist/assets/DeliveryPage-BRwcQyr7.js +0 -1
  376. package/dist/assets/web/dist/assets/NotFoundPage-RjH-9dPy.js +0 -1
  377. package/dist/assets/web/dist/assets/ResidentDetailPage-BRy5wkv9.js +0 -1
  378. package/dist/assets/web/dist/assets/ResidentsIndexPage-DAEVLN8j.js +0 -1
  379. package/dist/assets/web/dist/assets/RunRoutePage-B1KHmkZ9.js +0 -126
  380. package/dist/assets/web/dist/assets/RunRoutePage-CdbrbqU2.css +0 -1
  381. package/dist/assets/web/dist/assets/RunsIndexPage-3hUWFpFV.js +0 -1
  382. package/dist/assets/web/dist/assets/RunsTabs-Qn_5TVJU.js +0 -1
  383. package/dist/assets/web/dist/assets/ScheduledPage-DysVvVm1.js +0 -1
  384. package/dist/assets/web/dist/assets/StatusDot-Bug2a6T6.js +0 -1
  385. package/dist/assets/web/dist/assets/Tooltip-C2eEUbwn.js +0 -1
  386. package/dist/assets/web/dist/assets/main-0uhp-taL.js +0 -28
  387. package/dist/assets/web/dist/assets/main-Xickfv8V.css +0 -1
  388. package/dist/assets/web/dist/assets/seed-B8mQEhhw.js +0 -6
  389. package/dist/assets/web/dist/assets/wallClock-DI4HIEN5.js +0 -1
@@ -0,0 +1,260 @@
1
+ import { REPO_SLUG, type DeliveryRange, type PullRequestFacts } from "./delivery.js";
2
+ import { errorSuffix } from "./workerError.js";
3
+
4
+ // Where a repository's delivery snapshot lives (docs/reference/specs/delivery.md item 10):
5
+ // the merged pull requests' facts over the snapshot window, as GitHub gave
6
+ // them, and when they were read. Two implementations of one seam (AGENTS.md
7
+ // invariant 2):
8
+ //
9
+ // - `WorkerDeliverySnapshotStore` — the production choice — an HTTPS client
10
+ // to the `DeliveryDO` on the state Worker (deploy/cloudflare-memory/, one
11
+ // SQLite Durable Object, the same MEMORY_TOKEN bearer as memory, run history
12
+ // and the schedule store), so a bot restart keeps the snapshot (invariant 6)
13
+ // and the first request after one is served, not computed.
14
+ // - `InMemoryDeliverySnapshotStore` — tests, and the process without a state
15
+ // Worker: the snapshot lives as long as the process does.
16
+ //
17
+ // Node-free on purpose: the state Worker imports `isDeliverySnapshot` by
18
+ // relative path so the bot and the Worker validate ONE shape.
19
+ //
20
+ // A snapshot is written whole once — the first read of a repository — and
21
+ // patched by every refresh after: the rows the refresh re-read, the rows that
22
+ // aged out of the window, the new meta. A busy repository's window is many MB
23
+ // and its hourly change a few rows, so the write follows the change.
24
+ //
25
+ // Route contract (JSON in/out, bearer = the Worker's MEMORY_TOKEN):
26
+ // POST /delivery/get {repo} → {snapshot: DeliverySnapshot | null}
27
+ // POST /delivery/put {snapshot} → {ok: true, prs} replace the repository's snapshot whole
28
+ // POST /delivery/merge {patch} → {ok: true, prs} | 404 apply a refresh; 404 with no snapshot to merge into
29
+
30
+ /** What a snapshot says about itself: everything but the rows. */
31
+ export interface DeliverySnapshotMeta {
32
+ /** `owner/name`. */
33
+ repo: string;
34
+ /** ISO 8601 — when the read from GitHub began. */
35
+ snapshotAt: string;
36
+ /** The window the facts were read for: Monday-start weeks ending on the snapshot day. */
37
+ range: DeliveryRange;
38
+ /** The read stopped at its page cap before the window's start — the newest pull requests only. */
39
+ truncated: boolean;
40
+ /** ISO 8601 — every pull request merged at or after this instant is in `prs`: the window's
41
+ * start on a complete read, the oldest update a capped listing reached otherwise. */
42
+ completeFrom: string;
43
+ }
44
+
45
+ /** One repository's facts as a source assembled them, and when. */
46
+ export interface DeliverySnapshot extends DeliverySnapshotMeta {
47
+ /** Every pull request merged inside the window that the reads reached, in number order. */
48
+ prs: PullRequestFacts[];
49
+ }
50
+
51
+ /** What one refresh changes in a stored snapshot: the new meta, the rows the read re-read — they
52
+ * replace the stored ones by number — and the numbers of the rows that aged out of the window. */
53
+ export interface DeliverySnapshotPatch extends DeliverySnapshotMeta {
54
+ upsert: PullRequestFacts[];
55
+ drop: number[];
56
+ }
57
+
58
+ const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
59
+ const isInstant = (v: unknown): v is string => typeof v === "string" && Number.isFinite(Date.parse(v));
60
+
61
+ /** The fields the arithmetic reads, present and of the right kind; the arrays' rows are the source's. */
62
+ export function isPullRequestFacts(v: unknown): v is PullRequestFacts {
63
+ if (typeof v !== "object" || v === null) return false;
64
+ const p = v as Record<string, unknown>;
65
+ return (
66
+ typeof p.number === "number" &&
67
+ Number.isFinite(p.number) &&
68
+ typeof p.title === "string" &&
69
+ typeof p.author === "string" &&
70
+ isInstant(p.createdAt) &&
71
+ isInstant(p.mergedAt) &&
72
+ (p.updatedAt === undefined || isInstant(p.updatedAt)) &&
73
+ (p.firstHeadSha === undefined || typeof p.firstHeadSha === "string") &&
74
+ Array.isArray(p.ci) &&
75
+ Array.isArray(p.reviews) &&
76
+ Array.isArray(p.pushes) &&
77
+ (p.issue === undefined || (typeof p.issue === "object" && p.issue !== null))
78
+ );
79
+ }
80
+
81
+ /** The fields every snapshot document carries, present and of the right kind. */
82
+ function hasSnapshotMeta(s: Record<string, unknown>): boolean {
83
+ if (typeof s.repo !== "string" || !REPO_SLUG.test(s.repo)) return false;
84
+ if (!isInstant(s.snapshotAt) || !isInstant(s.completeFrom)) return false;
85
+ if (typeof s.truncated !== "boolean") return false;
86
+ const range = s.range as Record<string, unknown> | null | undefined;
87
+ return (
88
+ typeof range === "object" &&
89
+ range !== null &&
90
+ typeof range.since === "string" &&
91
+ ISO_DAY.test(range.since) &&
92
+ typeof range.until === "string" &&
93
+ ISO_DAY.test(range.until) &&
94
+ typeof range.weeks === "number" &&
95
+ Number.isFinite(range.weeks)
96
+ );
97
+ }
98
+
99
+ export function isDeliverySnapshot(v: unknown): v is DeliverySnapshot {
100
+ if (typeof v !== "object" || v === null) return false;
101
+ const s = v as Record<string, unknown>;
102
+ return hasSnapshotMeta(s) && Array.isArray(s.prs) && s.prs.every(isPullRequestFacts);
103
+ }
104
+
105
+ export function isDeliverySnapshotPatch(v: unknown): v is DeliverySnapshotPatch {
106
+ if (typeof v !== "object" || v === null) return false;
107
+ const { upsert, drop, ...meta } = v as Record<string, unknown>;
108
+ return (
109
+ hasSnapshotMeta(meta) &&
110
+ Array.isArray(upsert) &&
111
+ upsert.every(isPullRequestFacts) &&
112
+ Array.isArray(drop) &&
113
+ drop.every((n) => typeof n === "number" && Number.isFinite(n))
114
+ );
115
+ }
116
+
117
+ /** The snapshot a patch leaves behind when applied to `stored`: rows by number, in number order. */
118
+ export function applyPatch(stored: DeliverySnapshot, patch: DeliverySnapshotPatch): DeliverySnapshot {
119
+ const { upsert, drop, ...meta } = patch;
120
+ const rows = new Map(stored.prs.map((p) => [p.number, p]));
121
+ for (const p of upsert) rows.set(p.number, p);
122
+ for (const n of drop) rows.delete(n);
123
+ return { ...meta, prs: [...rows.values()].sort((a, b) => a.number - b.number) };
124
+ }
125
+
126
+ export interface DeliverySnapshotStore {
127
+ /** The repository's stored snapshot, or undefined when none was ever stored. Throws on failure. */
128
+ get(repo: string): Promise<DeliverySnapshot | undefined>;
129
+ /** Replace the repository's snapshot whole. Throws on failure; callers warn, never crash. */
130
+ put(snapshot: DeliverySnapshot): Promise<void>;
131
+ /** Apply a refresh to the repository's stored snapshot. False when nothing is stored to merge
132
+ * into — the caller then writes the snapshot whole. Throws on failure. */
133
+ merge(patch: DeliverySnapshotPatch): Promise<boolean>;
134
+ }
135
+
136
+ export class InMemoryDeliverySnapshotStore implements DeliverySnapshotStore {
137
+ private readonly byRepo = new Map<string, DeliverySnapshot>();
138
+
139
+ async get(repo: string): Promise<DeliverySnapshot | undefined> {
140
+ const s = this.byRepo.get(repo);
141
+ return s ? structuredClone(s) : undefined;
142
+ }
143
+
144
+ async put(snapshot: DeliverySnapshot): Promise<void> {
145
+ this.byRepo.set(snapshot.repo, structuredClone(snapshot));
146
+ }
147
+
148
+ async merge(patch: DeliverySnapshotPatch): Promise<boolean> {
149
+ const stored = this.byRepo.get(patch.repo);
150
+ if (!stored) return false;
151
+ this.byRepo.set(patch.repo, applyPatch(stored, structuredClone(patch)));
152
+ return true;
153
+ }
154
+ }
155
+
156
+ /** Per-request ceiling: a snapshot of a busy repository is a few MB each way. */
157
+ export const SNAPSHOT_WORKER_TIMEOUT_MS = 30_000;
158
+
159
+ export interface WorkerDeliverySnapshotStoreOptions {
160
+ /** Base URL of the state Worker. */
161
+ baseUrl: string;
162
+ /** Bearer secret (MEMORY_TOKEN on the Worker). */
163
+ token: string;
164
+ /** Injectable for tests; defaults to global fetch. */
165
+ fetch?: typeof fetch;
166
+ }
167
+
168
+ export class WorkerDeliverySnapshotStore implements DeliverySnapshotStore {
169
+ private readonly baseUrl: string;
170
+ private readonly fetchImpl: typeof fetch;
171
+
172
+ constructor(private readonly opts: WorkerDeliverySnapshotStoreOptions) {
173
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
174
+ this.fetchImpl = opts.fetch ?? fetch;
175
+ }
176
+
177
+ async get(repo: string): Promise<DeliverySnapshot | undefined> {
178
+ const res = await this.post("/delivery/get", { repo });
179
+ if (!res.ok) throw new Error(`state Worker /delivery/get HTTP ${res.status}${await errorSuffix(res)}`);
180
+ const data = (await res.json().catch(() => ({}))) as Record<string, unknown>;
181
+ if (data.snapshot === null || data.snapshot === undefined) return undefined;
182
+ if (!isDeliverySnapshot(data.snapshot))
183
+ throw new Error(`state Worker /delivery/get: the answer for ${repo} is not a delivery snapshot`);
184
+ return data.snapshot;
185
+ }
186
+
187
+ async put(snapshot: DeliverySnapshot): Promise<void> {
188
+ const res = await this.post("/delivery/put", { snapshot });
189
+ if (!res.ok) throw new Error(`state Worker /delivery/put HTTP ${res.status}${await errorSuffix(res)}`);
190
+ }
191
+
192
+ async merge(patch: DeliverySnapshotPatch): Promise<boolean> {
193
+ const res = await this.post("/delivery/merge", { patch });
194
+ // Nothing stored to merge into — or a Worker from before the route: either way the caller writes whole.
195
+ if (res.status === 404) return false;
196
+ if (!res.ok) throw new Error(`state Worker /delivery/merge HTTP ${res.status}${await errorSuffix(res)}`);
197
+ return true;
198
+ }
199
+
200
+ private post(path: string, body: unknown): Promise<Response> {
201
+ return this.fetchImpl(`${this.baseUrl}${path}`, {
202
+ method: "POST",
203
+ headers: { "content-type": "application/json", authorization: `Bearer ${this.opts.token}` },
204
+ body: JSON.stringify(body),
205
+ signal: AbortSignal.timeout(SNAPSHOT_WORKER_TIMEOUT_MS),
206
+ });
207
+ }
208
+ }
209
+
210
+ /** A `{ baseUrl, tokenEnv? }` reference to the state Worker, as every `*.worker` config block spells it. */
211
+ export interface StateWorkerRef {
212
+ baseUrl: string;
213
+ /** Env var holding the bearer; default MEMORY_TOKEN. */
214
+ tokenEnv?: string;
215
+ }
216
+
217
+ /** The config blocks that may name the state Worker — one Worker, referenced from each capability that uses it. */
218
+ export interface StateWorkerBlocks {
219
+ runtimeOverrides?: { worker?: StateWorkerRef };
220
+ runHistory?: { worker?: StateWorkerRef };
221
+ schedules?: { worker?: StateWorkerRef };
222
+ memory?: { worker?: StateWorkerRef };
223
+ }
224
+
225
+ const DEFAULT_TOKEN_ENV = "MEMORY_TOKEN";
226
+
227
+ /** The slice of the process's `Secrets` the builder reads (src/secrets.ts; named
228
+ * structurally so this module stays free of the bot's Node-only imports for the Worker). */
229
+ export interface SecretReader {
230
+ named(name: string): { reveal(): string } | undefined;
231
+ }
232
+
233
+ /** Process-startup store selection (src/index.ts, the CLI): the Worker store
234
+ * behind whichever `*.worker` block names the state Worker — the snapshot is
235
+ * a cache, so it earns no block of its own — else the in-memory store with a
236
+ * warning naming what a restart then costs. Pure w.r.t. the environment. */
237
+ export function buildDeliverySnapshotStore(
238
+ config: StateWorkerBlocks,
239
+ secrets: SecretReader,
240
+ warn: (message: string) => void,
241
+ ): DeliverySnapshotStore {
242
+ const worker = [config.runtimeOverrides, config.runHistory, config.schedules, config.memory].find(
243
+ (block) => block?.worker?.baseUrl,
244
+ )?.worker;
245
+ if (!worker) {
246
+ warn(
247
+ "delivery snapshots are kept in memory — a restart loses them and the first request after one reads GitHub live. Name the state Worker (runHistory.worker, runtimeOverrides.worker, schedules.worker or memory.worker) to keep them on it.",
248
+ );
249
+ return new InMemoryDeliverySnapshotStore();
250
+ }
251
+ const tokenEnv = worker.tokenEnv ?? DEFAULT_TOKEN_ENV;
252
+ const token = secrets.named(tokenEnv);
253
+ if (!token) {
254
+ warn(
255
+ `the state Worker is configured but ${tokenEnv} is unset — delivery snapshots are kept in memory and a restart loses them. Set ${tokenEnv} to the state Worker's bearer.`,
256
+ );
257
+ return new InMemoryDeliverySnapshotStore();
258
+ }
259
+ return new WorkerDeliverySnapshotStore({ baseUrl: worker.baseUrl, token: token.reveal() });
260
+ }
@@ -0,0 +1,296 @@
1
+ // Deterministic review verdict → GitHub comment body.
2
+ //
3
+ // Downstream automation (a repository's opt-in `auto-approve-review-lgtm.yml`
4
+ // workflow) approves a PR when the review App's review body STARTS WITH the
5
+ // exact token `LGTM:`. That token must therefore never depend on how the model happens to
6
+ // phrase its opening line. The model states its judgement through the
7
+ // structured `submit_verdict` tool (src/tools/workspace.ts); this module turns
8
+ // that structured value into the first line of the posted body:
9
+ //
10
+ // approve → "LGTM: <summary>"
11
+ // request_changes → "Changes requested: <summary>"
12
+ // (no verdict) → "No verdict submitted — not approving." (fail-closed)
13
+ //
14
+ // The model's prose follows after a blank line. Whatever the prose says, only
15
+ // an explicit `approve` verdict can produce a body that begins with "LGTM".
16
+ //
17
+ // The verdict also carries typed findings (docs/reference/specs/agent-ship.md item 6):
18
+ // one compact entry per issue, rendered as a list under the verdict line, so
19
+ // a fix round can reference each finding by its stable id and answer it with
20
+ // a typed disposition (`parseDispositionsInput` below). Findings validate
21
+ // fail-closed PER finding — a malformed finding drops with a note, a
22
+ // malformed array drops the whole field — and an `approve` carrying a
23
+ // self-declared `blocking` finding is downgraded to `request_changes`, so the
24
+ // posted body can never begin with `LGTM:` over a defect the reviewer itself
25
+ // called blocking.
26
+
27
+ import { normalizeHead } from "./reviewedHead.js";
28
+ import { redactSecrets } from "./redact.js";
29
+
30
+ export type ReviewVerdictKind = "approve" | "request_changes";
31
+
32
+ export const FINDING_SEVERITIES = ["blocking", "major", "minor", "nit"] as const;
33
+ export type FindingSeverity = (typeof FINDING_SEVERITIES)[number];
34
+
35
+ export interface Finding {
36
+ /** Stable id the reviewer assigned in order ("F1", "F2", …) — dispositions
37
+ * reference findings by this id. */
38
+ id: string;
39
+ severity: FindingSeverity;
40
+ /** Repo-relative file the finding points at. */
41
+ file: string;
42
+ /** Optional 1-based line. An unusable value is dropped like a malformed
43
+ * `head` — the finding stands without it. */
44
+ line?: number;
45
+ /** One line naming the issue; the full explanation lives in the prose. */
46
+ title: string;
47
+ }
48
+
49
+ export interface ReviewVerdict {
50
+ verdict: ReviewVerdictKind;
51
+ /** One line: why. Newlines are collapsed so the token line stays one line. */
52
+ summary: string;
53
+ /** The commit the agent says it reviewed (`git rev-parse HEAD` in its
54
+ * checkout), 7–40 lowercase hex. The dispatcher's reviewed-head guard
55
+ * (reviewedHead.ts) compares it to the PR head when the workspace HEAD
56
+ * could not be observed directly. Absent when not supplied or malformed. */
57
+ head?: string;
58
+ /** Typed findings enumerated through `submit_verdict`. Present only when
59
+ * the input carried a findings array (possibly empty after drops); absent
60
+ * when no array was supplied or the array itself was malformed. */
61
+ findings?: Finding[];
62
+ /** Parse notes naming what was dropped from `findings` (ids/indices with
63
+ * reasons) — surfaced in the tool ack so the model can resubmit, never
64
+ * rendered into the posted body. */
65
+ droppedFindings?: string[];
66
+ }
67
+
68
+ export const LGTM_TOKEN = "LGTM:";
69
+ export const CHANGES_TOKEN = "Changes requested:";
70
+ export const NO_VERDICT_LINE = "No verdict submitted — not approving.";
71
+
72
+ /** Parse an arbitrary tool input into a verdict, or null when it is not one. */
73
+ export function parseVerdictInput(input: Record<string, unknown>): ReviewVerdict | null {
74
+ const verdict = input.verdict;
75
+ if (verdict !== "approve" && verdict !== "request_changes") return null;
76
+ const summary = typeof input.summary === "string" ? oneLine(input.summary) : "";
77
+ const head = normalizeHead(input.head);
78
+ const out: ReviewVerdict = { verdict, summary };
79
+ if (head) out.head = head;
80
+ if (input.findings !== undefined) {
81
+ const parsed = parseFindings(input.findings);
82
+ if (parsed.findings) out.findings = parsed.findings;
83
+ if (parsed.dropped.length) out.droppedFindings = parsed.dropped;
84
+ // Verdict/severity consistency, fail-closed: an approve carrying a
85
+ // blocking finding downgrades so the posted body can never start with
86
+ // `LGTM:` over a self-declared blocking defect. Keyed on the RAW entries'
87
+ // declared severity, not the validated survivors — a blocking entry that
88
+ // itself failed validation and dropped still poisons the approve.
89
+ if (out.verdict === "approve" && parsed.blocking.length > 0) {
90
+ out.verdict = "request_changes";
91
+ out.summary = oneLine(
92
+ `${out.summary} [downgraded from approve: blocking finding${parsed.blocking.length === 1 ? "" : "s"} ${parsed.blocking.join(", ")}]`,
93
+ );
94
+ }
95
+ }
96
+ return out;
97
+ }
98
+
99
+ /** Fail-closed per finding: each malformed entry drops with a note naming its
100
+ * index (and id when readable); a non-array drops the whole field.
101
+ * `blocking` labels every raw entry that declared severity "blocking" —
102
+ * valid or not — for the approve downgrade above. */
103
+ function parseFindings(value: unknown): { findings?: Finding[]; dropped: string[]; blocking: string[] } {
104
+ if (!Array.isArray(value)) {
105
+ return { dropped: ["findings: dropped — not an array"], blocking: [] };
106
+ }
107
+ const findings: Finding[] = [];
108
+ const dropped: string[] = [];
109
+ const blocking: string[] = [];
110
+ value.forEach((raw, i) => {
111
+ const parsed = parseFinding(raw);
112
+ if (parsed.finding) {
113
+ findings.push(parsed.finding);
114
+ } else {
115
+ dropped.push(`findings[${i}]${parsed.id ? ` (${parsed.id})` : ""}: dropped — ${parsed.reason}`);
116
+ }
117
+ const label = parsed.finding?.id ?? parsed.id ?? `findings[${i}]`;
118
+ const r = typeof raw === "object" && raw !== null ? (raw as Record<string, unknown>) : undefined;
119
+ if (r?.severity === "blocking") blocking.push(label);
120
+ });
121
+ return { findings, dropped, blocking };
122
+ }
123
+
124
+ function parseFinding(
125
+ raw: unknown,
126
+ ): { finding: Finding; id?: never; reason?: never } | { finding?: never; id?: string; reason: string } {
127
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return { reason: "not an object" };
128
+ const r = raw as Record<string, unknown>;
129
+ const id = typeof r.id === "string" ? oneLine(r.id) : "";
130
+ if (!id) return { reason: "missing or empty id" };
131
+ const severity = r.severity;
132
+ if (!(FINDING_SEVERITIES as readonly unknown[]).includes(severity)) {
133
+ return { id, reason: `invalid severity ${JSON.stringify(r.severity)} (expected ${FINDING_SEVERITIES.join("|")})` };
134
+ }
135
+ const file = typeof r.file === "string" ? oneLine(r.file) : "";
136
+ if (!file) return { id, reason: "missing or empty file" };
137
+ const title = typeof r.title === "string" ? oneLine(r.title) : "";
138
+ if (!title) return { id, reason: "missing or empty title" };
139
+ const finding: Finding = { id, severity: severity as FindingSeverity, file, title };
140
+ // Like `head`: an unusable optional locator is dropped, the finding stands —
141
+ // dropping the whole finding over a bad line would also drop the severity
142
+ // that the approve→request_changes downgrade keys on.
143
+ if (typeof r.line === "number" && Number.isInteger(r.line) && r.line >= 1) finding.line = r.line;
144
+ return { finding };
145
+ }
146
+
147
+ function oneLine(s: string): string {
148
+ return s.replace(/\s*\n+\s*/g, " ").trim();
149
+ }
150
+
151
+ /** The exact first line of the posted body for a verdict (or its absence). */
152
+ export function verdictLine(verdict: ReviewVerdict | undefined): string {
153
+ if (!verdict) return NO_VERDICT_LINE;
154
+ const token = verdict.verdict === "approve" ? LGTM_TOKEN : CHANGES_TOKEN;
155
+ const summary = oneLine(verdict.summary);
156
+ return summary ? `${token} ${summary}` : token;
157
+ }
158
+
159
+ /** One compact finding line — `[severity] id file[:line] — title` — shared by
160
+ * the posted body's list (bulleted below) and ship's synthesized child turns. */
161
+ export function formatFinding(f: Finding): string {
162
+ const location = f.line !== undefined ? `${f.file}:${f.line}` : f.file;
163
+ return `[${f.severity}] ${f.id} ${location} — ${f.title}`;
164
+ }
165
+
166
+ /**
167
+ * Build the body posted to GitHub: the deterministic verdict line, the
168
+ * compact findings list (when present), a blank line, then the model's
169
+ * review text. Never starts with "LGTM" unless the verdict is `approve`.
170
+ */
171
+ export function buildReviewPostBody(answer: string, verdict: ReviewVerdict | undefined): string {
172
+ const head = [verdictLine(verdict), ...(verdict?.findings ?? []).map((f) => `- ${formatFinding(f)}`)];
173
+ return `${head.join("\n")}\n\n${answer.trim()}`;
174
+ }
175
+
176
+ // --- Dispositions (the coding side's answer to findings) -------------------
177
+ //
178
+ // A fix round records one disposition per finding through the
179
+ // `submit_dispositions` tool; the ship orchestrator keeps the last valid set
180
+ // and splits a cap report into declined (disposition recorded) vs unaddressed
181
+ // (none). Validation mirrors the findings above: a malformed entry drops with
182
+ // a note; a non-array input rejects the whole call.
183
+
184
+ export type DispositionKind = "fixed" | "declined";
185
+
186
+ export interface FindingDisposition {
187
+ /** The stable finding id from the review verdict this disposition answers. */
188
+ findingId: string;
189
+ disposition: DispositionKind;
190
+ /** One line: what was done, or why the finding was declined. */
191
+ note: string;
192
+ }
193
+
194
+ /**
195
+ * Parse a `submit_dispositions` input. Null when `dispositions` is not an
196
+ * array (the call is rejected, nothing recorded); otherwise each malformed
197
+ * entry drops with a note naming its index (and findingId when readable),
198
+ * mirroring the per-finding validation above. A missing note is tolerated as
199
+ * "" (the summary convention), never a dropped entry.
200
+ */
201
+ export function parseDispositionsInput(
202
+ input: Record<string, unknown>,
203
+ ): { dispositions: FindingDisposition[]; dropped: string[] } | null {
204
+ const value = input.dispositions;
205
+ if (!Array.isArray(value)) return null;
206
+ const dispositions: FindingDisposition[] = [];
207
+ const dropped: string[] = [];
208
+ value.forEach((raw, i) => {
209
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
210
+ dropped.push(`dispositions[${i}]: dropped — not an object`);
211
+ return;
212
+ }
213
+ const r = raw as Record<string, unknown>;
214
+ const findingId = typeof r.findingId === "string" ? oneLine(r.findingId) : "";
215
+ if (!findingId) {
216
+ dropped.push(`dispositions[${i}]: dropped — missing or empty findingId`);
217
+ return;
218
+ }
219
+ const disposition = r.disposition;
220
+ if (disposition !== "fixed" && disposition !== "declined") {
221
+ dropped.push(
222
+ `dispositions[${i}] (${findingId}): dropped — invalid disposition ${JSON.stringify(r.disposition)} (expected fixed|declined)`,
223
+ );
224
+ return;
225
+ }
226
+ const note = typeof r.note === "string" ? oneLine(r.note) : "";
227
+ dispositions.push({ findingId, disposition, note });
228
+ });
229
+ return { dispositions, dropped };
230
+ }
231
+
232
+ // ---- the stored shapes (docs/reference/specs/run-history.md items 2 and 3) ---------------------------
233
+
234
+ const VERDICT_KINDS: readonly string[] = ["approve", "request_changes"];
235
+ const DISPOSITION_KINDS: readonly string[] = ["fixed", "declined"];
236
+
237
+ const isRecordLike = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null;
238
+
239
+ function isFindingShape(v: unknown): v is Finding {
240
+ if (!isRecordLike(v)) return false;
241
+ return (
242
+ typeof v.id === "string" &&
243
+ (FINDING_SEVERITIES as readonly string[]).includes(v.severity as string) &&
244
+ typeof v.file === "string" &&
245
+ typeof v.title === "string" &&
246
+ (v.line === undefined || typeof v.line === "number")
247
+ );
248
+ }
249
+
250
+ /** Structural check on a verdict read back from a stored record: the kind, a
251
+ * string summary, an optional string head, findings each with an id, a known
252
+ * severity, a file and a title. Shape only, no bounds — what a record carries
253
+ * was validated by `parseVerdictInput` on the way in and redacted since. */
254
+ export function isReviewVerdictShape(v: unknown): v is ReviewVerdict {
255
+ if (!isRecordLike(v)) return false;
256
+ if (!VERDICT_KINDS.includes(v.verdict as string) || typeof v.summary !== "string") return false;
257
+ if (v.head !== undefined && typeof v.head !== "string") return false;
258
+ if (v.findings !== undefined && !(Array.isArray(v.findings) && v.findings.every(isFindingShape))) return false;
259
+ return true;
260
+ }
261
+
262
+ /** Structural check on a disposition set read back from a stored record. */
263
+ export function isFindingDispositionsShape(v: unknown): v is FindingDisposition[] {
264
+ return (
265
+ Array.isArray(v) &&
266
+ v.every(
267
+ (d) =>
268
+ isRecordLike(d) &&
269
+ typeof d.findingId === "string" &&
270
+ DISPOSITION_KINDS.includes(d.disposition as string) &&
271
+ typeof d.note === "string",
272
+ )
273
+ );
274
+ }
275
+
276
+ /** Every string leaf of the verdict through the redaction seam — the summary,
277
+ * each finding's file and title — the input untouched: what the one record
278
+ * assembly writes. `droppedFindings` are parse notes for the model and never
279
+ * ride a record. */
280
+ export function redactVerdict(v: ReviewVerdict, redact: (s: string) => string = redactSecrets): ReviewVerdict {
281
+ return {
282
+ verdict: v.verdict,
283
+ summary: redact(v.summary),
284
+ ...(v.head !== undefined ? { head: v.head } : {}),
285
+ ...(v.findings !== undefined
286
+ ? { findings: v.findings.map((f) => ({ ...f, file: redact(f.file), title: redact(f.title) })) }
287
+ : {}),
288
+ };
289
+ }
290
+
291
+ export function redactDispositions(
292
+ dispositions: readonly FindingDisposition[],
293
+ redact: (s: string) => string = redactSecrets,
294
+ ): FindingDisposition[] {
295
+ return dispositions.map((d) => ({ findingId: d.findingId, disposition: d.disposition, note: redact(d.note) }));
296
+ }
@@ -0,0 +1,77 @@
1
+ // Reviewed-head guard for the review post-step (docs/reference/specs/agent-review.md item 8).
2
+ //
3
+ // A review may only be posted to a PR when the commit the agent ACTUALLY
4
+ // reviewed is the PR head resolved for the run (`RepoContext.headSha`). The
5
+ // failure this guards against: the agent's own worktree is on the right
6
+ // branch, but it fetches and checks out another PR's branch because the PR
7
+ // body references it, reviews that, calls `submit_verdict approve`, and the
8
+ // deterministic post-step puts `LGTM:` on the PR under review — which an
9
+ // auto-approve workflow then approves. Every layer has done its job except the
10
+ // one that asks "is this review OF this PR?".
11
+ //
12
+ // Two sources for the reviewed head, in authority order:
13
+ // observed — `git rev-parse HEAD` run by the dispatcher in the run's
14
+ // workspace AFTER the model finished and BEFORE the workspace is
15
+ // released (a resident worktree; authoritative). Absent when the
16
+ // cwd is not a git repo (the cold sandbox's workspace root — the
17
+ // clone lives in a subdirectory the agent chose).
18
+ // reported — the `head` the agent passed to `submit_verdict` (it is told to
19
+ // run `git rev-parse HEAD` in the checkout it reviewed). Consulted
20
+ // only when nothing was observed; an observed mismatch is never
21
+ // rescued by a matching report.
22
+ //
23
+ // Fail-closed: unknown PR head, or no head from either source → no post.
24
+
25
+ export type HeadCheck = { ok: true; head: string; source: "observed" | "reported" } | { ok: false; reason: string };
26
+
27
+ const FULL_SHA = /^[0-9a-f]{40}$/;
28
+ const SHA_PREFIX = /^[0-9a-f]{7,40}$/;
29
+
30
+ /** Normalize a reported/observed head: trimmed, lowercased, 7–40 hex; else undefined. */
31
+ export function normalizeHead(value: unknown): string | undefined {
32
+ if (typeof value !== "string") return undefined;
33
+ const h = value.trim().toLowerCase();
34
+ return SHA_PREFIX.test(h) ? h : undefined;
35
+ }
36
+
37
+ /** The 40-hex commit from `git rev-parse HEAD` output, or undefined when the
38
+ * command failed (not a git repo, git missing) or printed nothing usable. */
39
+ export function parseRevParseOutput(output: string): string | undefined {
40
+ for (const line of output.split(/\r?\n/)) {
41
+ const t = line.trim().toLowerCase();
42
+ if (FULL_SHA.test(t)) return t;
43
+ }
44
+ return undefined;
45
+ }
46
+
47
+ /** Two normalized heads name the same commit when one is a ≥7-hex prefix of
48
+ * the other (the dispatcher's pre-run attach check reuses this). */
49
+ export function sameCommit(a: string, b: string): boolean {
50
+ const n = Math.min(a.length, b.length);
51
+ return n >= 7 && a.slice(0, n) === b.slice(0, n);
52
+ }
53
+
54
+ /**
55
+ * Decide whether the reviewed head is the PR head. `expected` is the PR head
56
+ * from repo resolution; `observed` the dispatcher's own `git rev-parse HEAD`
57
+ * (40-hex or undefined); `reported` whatever the agent passed to submit_verdict.
58
+ */
59
+ export function checkReviewedHead(input: { expected?: string; observed?: string; reported?: string }): HeadCheck {
60
+ const expected = normalizeHead(input.expected);
61
+ if (!expected) return { ok: false, reason: "PR head unknown at resolution time — cannot verify what was reviewed" };
62
+ const observed = normalizeHead(input.observed);
63
+ if (observed) {
64
+ return sameCommit(observed, expected)
65
+ ? { ok: true, head: observed, source: "observed" }
66
+ : { ok: false, reason: `reviewed head ${observed.slice(0, 7)} is not the PR head ${expected.slice(0, 7)}` };
67
+ }
68
+ const reported = normalizeHead(input.reported);
69
+ if (!reported)
70
+ return {
71
+ ok: false,
72
+ reason: "reviewed head unknown — the workspace HEAD could not be read and no head was reported with the verdict",
73
+ };
74
+ return sameCommit(reported, expected)
75
+ ? { ok: true, head: reported, source: "reported" }
76
+ : { ok: false, reason: `reviewed head ${reported.slice(0, 7)} is not the PR head ${expected.slice(0, 7)}` };
77
+ }
@@ -319,8 +319,10 @@ export type RunEvent =
319
319
  text: string;
320
320
  /** Where the request came from, for the Request block: the channel and
321
321
  * user display names and a link back to the triggering message —
322
- * whatever the adapter supplied (all optional). */
323
- source?: { url?: string; channel?: string; user?: string };
322
+ * whatever the adapter supplied (all optional) — and, for a follow-up a
323
+ * run sent rather than a person (a parent's `send_to_run`), that run's
324
+ * id (docs/reference/specs/agent-conductor.md item 8). */
325
+ source?: { url?: string; channel?: string; user?: string; run?: string };
324
326
  seq?: number;
325
327
  at?: number;
326
328
  }
@@ -9,7 +9,7 @@ import type { ClaimResult, FenceResult, LivePhase } from "./types.js";
9
9
  * after a lost response), anything else is refused with what the steer
10
10
  * message needs. */
11
11
  export function decideClaim(
12
- existing: { runId: string; agent?: string; startedAt: number; ownerGen: string } | undefined,
12
+ existing: { runId: string; agent?: string; startedAt: number; ownerGen: string; idempotencyKey?: string } | undefined,
13
13
  req: { runId: string; gen: string },
14
14
  ): ClaimResult {
15
15
  if (!existing) return { ok: true };
@@ -21,6 +21,7 @@ export function decideClaim(
21
21
  runId: existing.runId,
22
22
  ...(existing.agent !== undefined ? { agent: existing.agent } : {}),
23
23
  startedAt: existing.startedAt,
24
+ ...(existing.idempotencyKey !== undefined ? { idempotencyKey: existing.idempotencyKey } : {}),
24
25
  },
25
26
  };
26
27
  }