@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,229 @@
1
+ // The capability profile a run carries (docs/decisions/0026-capability-profiles-and-request-routing.md):
2
+ // the machine class its tools execute on, the identity it acts as, and the
3
+ // minutes it may run. A preset declares one; the run's EFFECTIVE profile is
4
+ // what the pipeline hands the factory, the ledger and the runner — never the
5
+ // preset's fields read again downstream. Pure and leaf: type-only imports, so
6
+ // the node-free record contract and the state Worker can name the shape.
7
+ import type { AgentDef, Identity, MachineClass } from "../agents/registry.js";
8
+
9
+ export type { Identity, MachineClass };
10
+
11
+ /** Where a cap on the profile came from — named on a clip and on a refusal.
12
+ * `parent` is the wall clock a spawning run had left when it started a child
13
+ * (docs/reference/specs/routing-and-config.md item 20): a boundary on the
14
+ * minutes axis alone, never on the identity or the class. */
15
+ export type BoundaryScope = "defaults" | "channel" | "user" | "directive" | "parent";
16
+ export const BOUNDARY_SCOPES: readonly BoundaryScope[] = ["defaults", "channel", "user", "directive", "parent"];
17
+
18
+ /** The clip's source as the card and the config block name it: a scope's cap
19
+ * is `<scope> boundary`, the caller's own `budget:` directive is `budget
20
+ * directive`, a spawning run's remaining wall clock is `parent run's budget`
21
+ * — one wording for every surface that says what clipped a run. */
22
+ export function clipSourceLabel(scope: BoundaryScope): string {
23
+ switch (scope) {
24
+ case "directive":
25
+ return "budget directive";
26
+ case "parent":
27
+ return "parent run's budget";
28
+ default:
29
+ return `${scope} boundary`;
30
+ }
31
+ }
32
+
33
+ /** What one run may have: the three axes, and — when a boundary clipped the
34
+ * budget — the scope that did. Identity and class are never clipped: a
35
+ * profile above a cap on those axes is refused before any executor exists. */
36
+ export interface RunProfile {
37
+ machine: MachineClass;
38
+ identity: Identity;
39
+ /** The wall-clock budget in minutes: the preset's, or the tightest cap. */
40
+ minutes: number;
41
+ /** The scope whose boundary clipped `minutes` below the preset's own; absent
42
+ * when the preset's declared budget stands. */
43
+ boundedBy?: BoundaryScope;
44
+ }
45
+
46
+ /** The identity order `none < read < write`: a boundary's `maxIdentity` admits
47
+ * every identity at or below it. Typed against the union, so a new identity
48
+ * fails to compile until it has a rung. */
49
+ export const IDENTITY_ORDER: Record<Identity, number> = { none: 0, read: 1, write: 2 };
50
+
51
+ /** Whether `identity` is at or under `cap` on the order. */
52
+ export function identityWithin(identity: Identity, cap: Identity): boolean {
53
+ return IDENTITY_ORDER[identity] <= IDENTITY_ORDER[cap];
54
+ }
55
+
56
+ /** The profile a preset declares — what an effective profile is when no
57
+ * boundary on the path caps anything (record 0026's invariant: a
58
+ * configuration with no boundaries runs exactly the preset's declared profile). */
59
+ export function declaredProfile(preset: Pick<AgentDef, "machine" | "identity" | "maxMinutes">): RunProfile {
60
+ return { machine: preset.machine, identity: preset.identity, minutes: preset.maxMinutes };
61
+ }
62
+
63
+ /** The preset with its budget replaced by the effective profile's — the def
64
+ * the runner is handed, since its deadline, wrap-up warning and budget label
65
+ * read `maxMinutes` (the same clipped copy the ship pipeline hands its child
66
+ * rounds). Always a copy: the shared `AgentDef` is never mutated. */
67
+ export function budgetedAgent(agent: AgentDef, profile: RunProfile): AgentDef {
68
+ return { ...agent, maxMinutes: profile.minutes };
69
+ }
70
+
71
+ // ---- boundaries: a scope caps, never grants -----------------------------------
72
+
73
+ /** A cap on the three axes that any scope may set (`defaults`, `channels.<id>`,
74
+ * `users.<id>`; docs/reference/specs/routing-and-config.md item 2). An absent
75
+ * axis caps nothing. A boundary never grants: it is not a fourth grants axis,
76
+ * and the policy table's one question (who may run a preset) is unchanged. */
77
+ export interface Boundary {
78
+ /** The most a run may have, in minutes; at least 2 (the bash tool keeps a 60 s reserve). */
79
+ maxMinutes?: number;
80
+ /** The highest identity a run may act as, on `none < read < write`. */
81
+ maxIdentity?: Identity;
82
+ /** The machine classes a run may execute on; a preset's class must be in every layer's set. */
83
+ machines?: MachineClass[];
84
+ }
85
+
86
+ /** One layer's boundary with the scope it came from, in resolution order:
87
+ * `defaults`, then `channel`, then `user`. */
88
+ export interface ScopedBoundary {
89
+ scope: BoundaryScope;
90
+ boundary: Boundary;
91
+ }
92
+
93
+ /** The intersection of every boundary on a request's path, each axis with
94
+ * the scope that set the value that won — so a clip or a refusal can name
95
+ * it. Unlike every other scope setting, which the most specific layer
96
+ * replaces whole, boundaries intersect: the smallest budget, the lowest
97
+ * identity, the classes every layer allows. */
98
+ export interface EffectiveBoundary {
99
+ maxMinutes?: { value: number; scope: BoundaryScope };
100
+ maxIdentity?: { value: Identity; scope: BoundaryScope };
101
+ machines?: {
102
+ /** The classes every layer allows, in canonical class order. */
103
+ value: MachineClass[];
104
+ /** Each layer's own list, so a refusal can name the scopes that exclude a class. */
105
+ by: Array<{ scope: BoundaryScope; machines: MachineClass[] }>;
106
+ };
107
+ }
108
+
109
+ /** Canonical class order for the intersected list (typed against the union
110
+ * like the identity order, so a new class fails to compile until placed). */
111
+ const MACHINE_ORDER: Record<MachineClass, number> = { none: 0, blank: 1, "repo-cold": 2, "repo-resident": 3 };
112
+
113
+ /**
114
+ * Intersect the boundaries on a request's path. On a tie the first layer
115
+ * named keeps the attribution (the least specific scope, in the order the
116
+ * caller passes). Undefined when no layer caps any axis: the absence of a
117
+ * boundary is today's behaviour, and the caller can tell it apart from an
118
+ * empty cap.
119
+ */
120
+ export function intersectBoundaries(layers: readonly ScopedBoundary[]): EffectiveBoundary | undefined {
121
+ const out: EffectiveBoundary = {};
122
+ for (const { scope, boundary } of layers) {
123
+ if (boundary.maxMinutes !== undefined && (!out.maxMinutes || boundary.maxMinutes < out.maxMinutes.value)) {
124
+ out.maxMinutes = { value: boundary.maxMinutes, scope };
125
+ }
126
+ if (
127
+ boundary.maxIdentity !== undefined &&
128
+ (!out.maxIdentity || !identityWithin(out.maxIdentity.value, boundary.maxIdentity))
129
+ ) {
130
+ out.maxIdentity = { value: boundary.maxIdentity, scope };
131
+ }
132
+ if (boundary.machines !== undefined) {
133
+ const allowed = boundary.machines;
134
+ const value = (out.machines ? out.machines.value : allowed)
135
+ .filter((m) => allowed.includes(m))
136
+ .sort((a, b) => MACHINE_ORDER[a] - MACHINE_ORDER[b]);
137
+ out.machines = { value, by: [...(out.machines?.by ?? []), { scope, machines: [...allowed] }] };
138
+ }
139
+ }
140
+ return out.maxMinutes || out.maxIdentity || out.machines ? out : undefined;
141
+ }
142
+
143
+ /**
144
+ * The boundaries on a child's path with its parent's remaining wall clock as
145
+ * one more layer (docs/reference/specs/routing-and-config.md item 20): the
146
+ * whole minutes the parent has left cap the child's minutes, attributed to
147
+ * `parent`, when that is tighter than every cap already on the path. The
148
+ * identity and the class are untouched — a parent hands a child time, never a
149
+ * credential or a machine. The same object comes back when the parent's clock
150
+ * is not the tightest cap, so a caller can tell "nothing changed" apart.
151
+ */
152
+ export function boundedByParent(
153
+ boundary: EffectiveBoundary | undefined,
154
+ parentRemainingMs: number,
155
+ ): EffectiveBoundary | undefined {
156
+ const minutes = Math.floor(parentRemainingMs / 60_000);
157
+ if (boundary?.maxMinutes && boundary.maxMinutes.value <= minutes) return boundary;
158
+ return { ...boundary, maxMinutes: { value: minutes, scope: "parent" } };
159
+ }
160
+
161
+ /** Why a profile was refused: the axis, what the preset needs, what the
162
+ * boundary allows, and the scope(s) that set it — everything the refusal
163
+ * reply names. */
164
+ export type ProfileRefusal =
165
+ | { axis: "identity"; needs: Identity; cap: Identity; scope: BoundaryScope }
166
+ | { axis: "machine"; needs: MachineClass; allowed: MachineClass[]; scopes: BoundaryScope[] };
167
+
168
+ /** The effective profile, or the refusal the authorize stage turns into a
169
+ * named reply. Resolution is pure; the gate judges. */
170
+ export type ProfileResolution = { kind: "profile"; profile: RunProfile } | { kind: "refused"; refusal: ProfileRefusal };
171
+
172
+ /**
173
+ * The effective profile: preset ∩ directives ∩ boundary, with the rule per
174
+ * axis (docs/decisions/0026-capability-profiles-and-request-routing.md). The
175
+ * budget CLIPS — the smallest of the preset's minutes, the caller's own
176
+ * `budget` directive and the boundary's cap, `boundedBy` naming whichever
177
+ * narrowed it (a directive at or above the preset changes nothing) — because a
178
+ * shorter run still ends in the runner's forced write-up. Identity and machine
179
+ * class REFUSE when the preset's is above the cap or outside the set, because
180
+ * a preset that needs to push cannot do its job with a read token. Identity is
181
+ * judged before the class. A directive never widens anything.
182
+ */
183
+ export function effectiveProfile(
184
+ preset: Pick<AgentDef, "machine" | "identity" | "maxMinutes">,
185
+ directives: { budget?: number },
186
+ boundary: EffectiveBoundary | undefined,
187
+ ): ProfileResolution {
188
+ if (boundary?.maxIdentity && !identityWithin(preset.identity, boundary.maxIdentity.value)) {
189
+ return {
190
+ kind: "refused",
191
+ refusal: {
192
+ axis: "identity",
193
+ needs: preset.identity,
194
+ cap: boundary.maxIdentity.value,
195
+ scope: boundary.maxIdentity.scope,
196
+ },
197
+ };
198
+ }
199
+ if (boundary?.machines && !boundary.machines.value.includes(preset.machine)) {
200
+ return {
201
+ kind: "refused",
202
+ refusal: {
203
+ axis: "machine",
204
+ needs: preset.machine,
205
+ allowed: boundary.machines.value,
206
+ scopes: boundary.machines.by.filter((b) => !b.machines.includes(preset.machine)).map((b) => b.scope),
207
+ },
208
+ };
209
+ }
210
+ let minutes = preset.maxMinutes;
211
+ let boundedBy: BoundaryScope | undefined;
212
+ if (directives.budget !== undefined && directives.budget < minutes) {
213
+ minutes = directives.budget;
214
+ boundedBy = "directive";
215
+ }
216
+ if (boundary?.maxMinutes && boundary.maxMinutes.value < minutes) {
217
+ minutes = boundary.maxMinutes.value;
218
+ boundedBy = boundary.maxMinutes.scope;
219
+ }
220
+ return {
221
+ kind: "profile",
222
+ profile: {
223
+ machine: preset.machine,
224
+ identity: preset.identity,
225
+ minutes,
226
+ ...(boundedBy !== undefined ? { boundedBy } : {}),
227
+ },
228
+ };
229
+ }
@@ -175,6 +175,21 @@ export const POLICY: readonly Rule[] = [
175
175
  // ── schedules ────────────────────────────────────────────────────────────
176
176
  // Only the schedule shim's actor fires a schedule.
177
177
  { action: "schedule:fire", resource: "command", actorKinds: ["schedule"], when: [] },
178
+
179
+ // ── coordinator ──────────────────────────────────────────────────────────
180
+ // A coordinator step (`spawn`, `read-record`, `pr-check`, the instance
181
+ // creation): a machine identity holding the grant — the `coordinator`
182
+ // ingress bearer, granted `coordinator:step` by name — and never a person,
183
+ // an admin's `all` included: the steps are a program's, and the child a
184
+ // spawn starts is authorized as the requesting user the parent record
185
+ // names, not as whoever holds this grant.
186
+ { action: "coordinator:step", resource: "command", actorKinds: ["service"], when: [grant("coordinator:step")] },
187
+ // The plan runner's merge (docs/decisions/0031-the-coordinator-runs-a-plan-not-a-pull-request.md,
188
+ // "The merge grant"): its own action, held by the same bearer by name and by
189
+ // no person — what decides a merge is the branch (a plan branch the runner
190
+ // itself opened) and the guards, never the requester. Withdrawing the grant
191
+ // returns every merge to a person.
192
+ { action: "plan:merge", resource: "command", actorKinds: ["service"], when: [grant("plan:merge")] },
178
193
  ];
179
194
 
180
195
  export const ACTOR_KINDS: readonly ActorKind[] = ["user", "service", "schedule", "agent"];
@@ -0,0 +1,239 @@
1
+ // The ship coordinator's contract (docs/decisions/0029-durable-objects-store-workflows-schedule.md,
2
+ // docs/decisions/0031-the-coordinator-runs-a-plan-not-a-pull-request.md;
3
+ // docs/reference/specs/run-history.md items 47–48; docs/reference/specs/http-ingress.md
4
+ // item 9): what the state Worker, the bot and the bot's shim Worker agree on
5
+ // about a coordinator instance and its children. Node-free — imported by all
6
+ // three by relative path, the way runRecord.ts is — so the shapes agree by
7
+ // construction: the event a finished child sends, the key a spawn carries, the
8
+ // parent record the spawn route reads the requester from, and the names the
9
+ // routes decide on.
10
+ //
11
+ // A coordinator is a Workflow instance in the shim Worker whose children are
12
+ // ordinary `dispatch()` runs as the requesting user. It holds no credential of
13
+ // its own: every step is a call into the bot with the `coordinator` bearer,
14
+ // whose actor holds `coordinator:step` — the one grant that admits the steps.
15
+
16
+ /** The ingress subject the coordinator presents (`SWITCHBOARD_INGRESS_TOKENS`
17
+ * entry `{ subject: "coordinator" }` → the actor `http:coordinator`). A name,
18
+ * not authority: what admits a step is the grant below on that actor. */
19
+ export const COORDINATOR_IDENTITY = "coordinator";
20
+ /** The policy action every coordinator step is decided on (authorization.md item 2). */
21
+ export const COORDINATOR_STEP_ACTION = "coordinator:step";
22
+ /** The plan runner's merge, its own action on the same bearer (record 0031's merge grant): a
23
+ * plan branch's pull request is merged by the runner only under it; withdrawn, every merge is a person's. */
24
+ export const PLAN_MERGE_ACTION = "plan:merge";
25
+ /** Where the bot answers the steps: `POST <prefix><step>` on the container, forwarded by the shim like every `/admin/*` path. */
26
+ export const COORDINATOR_STEP_PATH_PREFIX = "/admin/coordinator/";
27
+
28
+ /** A Workflow instance id: the platform's own alphabet, at most 100 characters. */
29
+ export const INSTANCE_ID_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,99}$/;
30
+ /** A step name: `<unit>/<round>/<kind>` and its kin — no colon, which separates it from the instance in the key. */
31
+ export const STEP_NAME_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_./-]{0,119}$/;
32
+ /** `<parentInstanceId>:<step>` — the idempotency key a spawn carries and the child's claim stores. */
33
+ export const IDEMPOTENCY_KEY_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,99}:[A-Za-z0-9_][A-Za-z0-9_./-]{0,119}$/;
34
+
35
+ export function idempotencyKeyFor(parentInstanceId: string, step: string): string {
36
+ return `${parentInstanceId}:${step}`;
37
+ }
38
+
39
+ /** The event a child's terminal record sends its parent: the type carries the
40
+ * run id, so each `waitForEvent` matches its own child and a duplicate is
41
+ * buffered harmlessly. */
42
+ export const RUN_FINISHED_EVENT_PREFIX = "run finished:";
43
+ export function runFinishedEventType(runId: string): string {
44
+ return `${RUN_FINISHED_EVENT_PREFIX}${runId}`;
45
+ }
46
+
47
+ /** What the event carries — ids, a status and a clock; the parent confirms
48
+ * through `read-record` before it acts, so nothing more rides here. */
49
+ export interface RunFinishedPayload {
50
+ runId: string;
51
+ status: string;
52
+ finishedAt: number;
53
+ parentInstanceId: string;
54
+ }
55
+
56
+ /** What a coordinator's spawn stamps on the child's every row: the instance
57
+ * the child belongs to and the key the spawn carried. */
58
+ export interface CoordinatorTag {
59
+ parentInstanceId: string;
60
+ idempotencyKey: string;
61
+ }
62
+
63
+ /** The tag as the two flat record fields, or nothing — so a row, a summary
64
+ * and a record spread the same thing and a run with no coordinator carries no key. */
65
+ export function coordinatorFields(tag: CoordinatorTag | undefined): {
66
+ parentInstanceId?: string;
67
+ idempotencyKey?: string;
68
+ } {
69
+ return tag ? { parentInstanceId: tag.parentInstanceId, idempotencyKey: tag.idempotencyKey } : {};
70
+ }
71
+
72
+ /** The parent ship record: what the bot writes at an instance's creation and
73
+ * the spawn route reads the requester, channel and thread from — so a step
74
+ * never takes an actor from its caller. Ids only, never the task text. The
75
+ * units the instance runs are rows of their own (`CoordinatorUnit`), so this
76
+ * row stays the instance's identity and its two surfaces: the card the bot
77
+ * redraws and the run record it writes at the end. */
78
+ export interface CoordinatorInstance {
79
+ id: string;
80
+ kind: "ship";
81
+ /** The requesting user (platform-namespaced), whose grants every child is authorized under. */
82
+ userId: string;
83
+ userName?: string;
84
+ channelId: string;
85
+ channelName?: string;
86
+ /** The requesting thread: where the card lives and where a task-string
87
+ * instance's one unit runs; a plan's units each open a thread of their own. */
88
+ threadKey: string;
89
+ sourceUrl?: string;
90
+ /** `owner/name`, and the head branch the pipeline works on (a task string's
91
+ * deterministic ship branch; for a plan, the first unit's — each unit row names its own). */
92
+ repo: string;
93
+ branch: string;
94
+ /** The pull request's base branch, when the creator knew it. */
95
+ base?: string;
96
+ /** Epoch ms. */
97
+ createdAt: number;
98
+ /** The plan the instance runs, when it runs one: its id (the file's name) and its path in the repository. */
99
+ plan?: { id: string; path: string };
100
+ /** The pipeline's caps as the profile gate clipped them: the rounds cap and the wall clock per unit. */
101
+ caps?: { maxRounds: number; maxMinutes: number };
102
+ /** The status card in the requesting thread, when the channel has one — what
103
+ * the bot redraws from the coordinator's round events (`StatusHandle.handle`). */
104
+ card?: { channel: string; ts: string };
105
+ /** The run id the bot writes the parent's record under when the instance ends, and the card's label. */
106
+ runId?: string;
107
+ label?: string;
108
+ /** Which attempt of the plan this instance runs (a re-issue after an earlier
109
+ * attempt ended reruns the units not merged under `plan-<plan-id>-<attempt>`);
110
+ * absent for the first. */
111
+ attempt?: number;
112
+ }
113
+
114
+ /** One unit of the plan an instance runs (a task string is a plan of one unit,
115
+ * `task`): its branch, the units it waits on, and — as the runner reaches it —
116
+ * its thread, its pull request, the round boundaries the card drew and how it
117
+ * ended. One row a person can read for "what happened to this unit". */
118
+ export interface CoordinatorUnit {
119
+ instanceId: string;
120
+ /** `U<n>` as the plan spells it, or `task`. */
121
+ unit: string;
122
+ slug: string;
123
+ title?: string;
124
+ /** `plan/<plan-id>/<unit-slug>`, or the task string's ship branch. */
125
+ branch: string;
126
+ dependsOn: string[];
127
+ /** The unit's thread, once opened; a task's is the requesting thread from the start. */
128
+ threadKey?: string;
129
+ sourceUrl?: string;
130
+ /** The unit's board issue in the repository, when one titled by the unit id exists — the handoff's destination. */
131
+ issue?: number;
132
+ pr?: { number: number; url: string };
133
+ /** The round boundaries the coordinator reported, oldest first (the `ship_round` vocabulary). */
134
+ rounds: Array<{ index: number; agent: string; outcome: string; at: number }>;
135
+ /** How the unit ended: the ending's kind and the thread's report, when it has. */
136
+ ending?: { kind: string; report: string; at: number };
137
+ startedAt?: number;
138
+ }
139
+
140
+ const REPO_SLUG = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
141
+ const MAX_TEXT = 512;
142
+ /** A unit's report — the loop's words for how it ended, with a cap report's findings — is longer than a name. */
143
+ const MAX_REPORT = 20_000;
144
+ const MAX_ROUNDS = 200;
145
+
146
+ const isText = (v: unknown, max = MAX_TEXT): v is string => typeof v === "string" && v.length > 0 && v.length <= max;
147
+ const isOptionalText = (v: unknown): boolean => v === undefined || isText(v);
148
+ const isFinite = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v);
149
+ const isObject = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null;
150
+ const isPr = (v: unknown): boolean => isObject(v) && isFinite(v.number) && isText(v.url, 2048);
151
+
152
+ /** Structural check on a record from outside the process (a Worker response, an HTTP body). */
153
+ export function isCoordinatorInstance(v: unknown): v is CoordinatorInstance {
154
+ if (!isObject(v)) return false;
155
+ const r = v;
156
+ if (typeof r.id !== "string" || !INSTANCE_ID_PATTERN.test(r.id)) return false;
157
+ if (r.kind !== "ship") return false;
158
+ if (!isText(r.userId) || !isText(r.channelId) || !isText(r.threadKey)) return false;
159
+ if (!isOptionalText(r.userName) || !isOptionalText(r.channelName) || !isOptionalText(r.sourceUrl)) return false;
160
+ if (typeof r.repo !== "string" || !REPO_SLUG.test(r.repo)) return false;
161
+ if (!isText(r.branch) || !isOptionalText(r.base)) return false;
162
+ if (!isFinite(r.createdAt)) return false;
163
+ if (r.plan !== undefined && !(isObject(r.plan) && isText(r.plan.id) && isText(r.plan.path, 1024))) return false;
164
+ if (r.caps !== undefined && !(isObject(r.caps) && isFinite(r.caps.maxRounds) && isFinite(r.caps.maxMinutes)))
165
+ return false;
166
+ if (r.card !== undefined && !(isObject(r.card) && isText(r.card.channel) && isText(r.card.ts))) return false;
167
+ if (!isOptionalText(r.runId) || !isOptionalText(r.label)) return false;
168
+ if (r.attempt !== undefined && !(Number.isInteger(r.attempt) && (r.attempt as number) >= 2)) return false;
169
+ return true;
170
+ }
171
+
172
+ /** Structural check on a unit row from outside the process. */
173
+ export function isCoordinatorUnit(v: unknown): v is CoordinatorUnit {
174
+ if (!isObject(v)) return false;
175
+ const r = v;
176
+ if (typeof r.instanceId !== "string" || !INSTANCE_ID_PATTERN.test(r.instanceId)) return false;
177
+ if (!isText(r.unit, 32) || !isText(r.slug) || !isText(r.branch) || !isOptionalText(r.title)) return false;
178
+ if (!Array.isArray(r.dependsOn) || !r.dependsOn.every((d) => isText(d, 32))) return false;
179
+ if (!isOptionalText(r.threadKey) || !isOptionalText(r.sourceUrl)) return false;
180
+ if (r.issue !== undefined && !isFinite(r.issue)) return false;
181
+ if (r.pr !== undefined && !isPr(r.pr)) return false;
182
+ if (
183
+ !Array.isArray(r.rounds) ||
184
+ r.rounds.length > MAX_ROUNDS ||
185
+ !r.rounds.every((x) => isObject(x) && isFinite(x.index) && isText(x.agent) && isText(x.outcome) && isFinite(x.at))
186
+ )
187
+ return false;
188
+ if (
189
+ r.ending !== undefined &&
190
+ !(isObject(r.ending) && isText(r.ending.kind) && isText(r.ending.report, MAX_REPORT) && isFinite(r.ending.at))
191
+ )
192
+ return false;
193
+ if (r.startedAt !== undefined && !isFinite(r.startedAt)) return false;
194
+ return true;
195
+ }
196
+
197
+ /** The slice of a Workflow binding the send needs (`Workflow.get` →
198
+ * `WorkflowInstance.sendEvent`), so the state Worker's handler is testable
199
+ * with a double and the contract names no platform type. */
200
+ export interface WorkflowInstanceSender {
201
+ sendEvent(event: { type: string; payload: unknown }): Promise<void>;
202
+ }
203
+ export interface WorkflowSender {
204
+ get(id: string): Promise<WorkflowInstanceSender>;
205
+ }
206
+
207
+ /** How a send ended. `none`: the record names no instance. `no-binding`: the
208
+ * Worker has no coordinator binding. `failed`: the engine refused (the
209
+ * instance ended, or is unknown) — swallowed, never thrown: the commit stands,
210
+ * and the parent's `waitForEvent` timeout falls back to `read-record`. */
211
+ export type RunFinishedSend =
212
+ | { kind: "sent"; instance: string; type: string }
213
+ | { kind: "none" }
214
+ | { kind: "no-binding"; instance: string }
215
+ | { kind: "failed"; instance: string; type: string; reason: string };
216
+
217
+ /** The one send per committed terminal record (run-history item 47). */
218
+ export async function sendRunFinished(
219
+ workflow: WorkflowSender | undefined,
220
+ record: { id: string; status: string; finishedAt: number; parentInstanceId?: string },
221
+ ): Promise<RunFinishedSend> {
222
+ const instance = record.parentInstanceId;
223
+ if (instance === undefined) return { kind: "none" };
224
+ if (!workflow) return { kind: "no-binding", instance };
225
+ const type = runFinishedEventType(record.id);
226
+ const payload: RunFinishedPayload = {
227
+ runId: record.id,
228
+ status: record.status,
229
+ finishedAt: record.finishedAt,
230
+ parentInstanceId: instance,
231
+ };
232
+ try {
233
+ const handle = await workflow.get(instance);
234
+ await handle.sendEvent({ type, payload });
235
+ return { kind: "sent", instance, type };
236
+ } catch (err) {
237
+ return { kind: "failed", instance, type, reason: err instanceof Error ? err.message : String(err) };
238
+ }
239
+ }