@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.
- package/dist/assets/Dockerfile +6 -3
- package/dist/assets/config/config.example.yaml +56 -4
- package/dist/assets/deploy/cloudflare/coordinator.ts +86 -0
- package/dist/assets/deploy/cloudflare/shared.ts +9 -0
- package/dist/assets/deploy/cloudflare/worker.ts +142 -6
- package/dist/assets/deploy/cloudflare/wrangler.template.jsonc +9 -0
- package/dist/assets/deploy/cloudflare-memory/worker.ts +381 -7
- package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +22 -2
- package/dist/assets/deploy/cloudflare-resident/Dockerfile +89 -3
- package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +81 -10
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +4 -1
- package/dist/assets/deploy/secrets.manifest.json +1 -1
- package/dist/assets/package-lock.json +291 -97
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +517 -0
- package/dist/assets/src/config/profile.ts +229 -0
- package/dist/assets/src/core/authz/policy.ts +15 -0
- package/dist/assets/src/core/coordinator/contract.ts +239 -0
- package/dist/assets/src/core/coordinator/driver.ts +501 -0
- package/dist/assets/src/core/coordinator/instancesRoute.ts +181 -0
- package/dist/assets/src/core/delivery.ts +829 -0
- package/dist/assets/src/core/deliverySnapshotStore.ts +260 -0
- package/dist/assets/src/core/reviewVerdict.ts +296 -0
- package/dist/assets/src/core/reviewedHead.ts +77 -0
- package/dist/assets/src/core/runEvents.ts +4 -2
- package/dist/assets/src/core/runLedger/decisions.ts +2 -1
- package/dist/assets/src/core/runLedger/types.ts +23 -1
- package/dist/assets/src/core/runRecord.ts +100 -0
- package/dist/assets/src/core/ship/contract.ts +502 -0
- package/dist/assets/src/core/ship/coordinator.ts +1039 -0
- package/dist/assets/src/core/workerError.ts +12 -0
- package/dist/assets/web/dist/.vite/manifest.json +2770 -70
- package/dist/assets/web/dist/assets/AppShell-lYVcj-k7.js +1 -0
- package/dist/assets/web/dist/assets/{CostsPage-XQ-fQzdz.js → CostsPage-DGSE48l1.js} +1 -1
- package/dist/assets/web/dist/assets/DeliveryPage-CvlWP7Eq.js +1 -0
- package/dist/assets/web/dist/assets/NotFoundPage-BzWS4Ca1.js +1 -0
- package/dist/assets/web/dist/assets/ResidentDetailPage-D3P21yeI.js +1 -0
- package/dist/assets/web/dist/assets/ResidentsIndexPage-DOYqnZ1q.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-OmvrvPXY.js +12 -0
- package/dist/assets/web/dist/assets/RunRoutePage-f6TSNPeW.css +1 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-DWbSQtL4.js +1 -0
- package/dist/assets/web/dist/assets/RunsTabs-DEICQ4FY.js +1 -0
- package/dist/assets/web/dist/assets/ScheduledPage-CPKfJ4mR.js +1 -0
- package/dist/assets/web/dist/assets/StatusDot-COr8jTyM.js +1 -0
- package/dist/assets/web/dist/assets/Tooltip-fOqTZkNT.js +1 -0
- package/dist/assets/web/dist/assets/abap-CLvhMVsD.js +1 -0
- package/dist/assets/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
- package/dist/assets/web/dist/assets/ada-C5qYipkI.js +1 -0
- package/dist/assets/web/dist/assets/ahk-CA9gdOdF.js +1 -0
- package/dist/assets/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
- package/dist/assets/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
- package/dist/assets/web/dist/assets/angular-html-Cw130Zyi.js +1 -0
- package/dist/assets/web/dist/assets/angular-ts-DY1m4C2s.js +1 -0
- package/dist/assets/web/dist/assets/apache-U0d_L8uA.js +1 -0
- package/dist/assets/web/dist/assets/apex-KFGFyKwG.js +1 -0
- package/dist/assets/web/dist/assets/apl-W6Ty7v05.js +1 -0
- package/dist/assets/web/dist/assets/applescript-CCn79oCD.js +1 -0
- package/dist/assets/web/dist/assets/ara-4CJ0cIlV.js +1 -0
- package/dist/assets/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
- package/dist/assets/web/dist/assets/asm-Cmm7eHzH.js +1 -0
- package/dist/assets/web/dist/assets/astro-DlLVHwVk.js +1 -0
- package/dist/assets/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
- package/dist/assets/web/dist/assets/awk-BWXHIvNe.js +1 -0
- package/dist/assets/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
- package/dist/assets/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
- package/dist/assets/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
- package/dist/assets/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
- package/dist/assets/web/dist/assets/bat-qdxAdBWQ.js +1 -0
- package/dist/assets/web/dist/assets/beancount-D-usSTwE.js +1 -0
- package/dist/assets/web/dist/assets/berry-DKpUyyne.js +1 -0
- package/dist/assets/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
- package/dist/assets/web/dist/assets/bicep-CUHmPFLl.js +1 -0
- package/dist/assets/web/dist/assets/bird2-CYIUgYqB.js +1 -0
- package/dist/assets/web/dist/assets/blade-Bco3tDh4.js +1 -0
- package/dist/assets/web/dist/assets/bsl-_VjlB9mv.js +1 -0
- package/dist/assets/web/dist/assets/c-1LaFY_cj.js +1 -0
- package/dist/assets/web/dist/assets/c3-D0apG41V.js +1 -0
- package/dist/assets/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
- package/dist/assets/web/dist/assets/cairo-DLTphjLi.js +1 -0
- package/dist/assets/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
- package/dist/assets/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
- package/dist/assets/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
- package/dist/assets/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
- package/dist/assets/web/dist/assets/chapel-B-OvrOL5.js +1 -0
- package/dist/assets/web/dist/assets/clarity-CtfBBspg.js +1 -0
- package/dist/assets/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
- package/dist/assets/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
- package/dist/assets/web/dist/assets/cobol-Ch3EPlHu.js +1 -0
- package/dist/assets/web/dist/assets/codeowners-C8r90Shi.js +1 -0
- package/dist/assets/web/dist/assets/codeql-oeQT6MSM.js +1 -0
- package/dist/assets/web/dist/assets/coffee-hZuITnto.js +1 -0
- package/dist/assets/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
- package/dist/assets/web/dist/assets/coq-DuBRPxYg.js +1 -0
- package/dist/assets/web/dist/assets/cpp-B8iRVo8m.js +1 -0
- package/dist/assets/web/dist/assets/crystal-DKveI7lr.js +1 -0
- package/dist/assets/web/dist/assets/csharp-oqKa8noW.js +1 -0
- package/dist/assets/web/dist/assets/css-_2_CD-rr.js +1 -0
- package/dist/assets/web/dist/assets/csv-Dx-8-gkx.js +1 -0
- package/dist/assets/web/dist/assets/cue-CE9AQfxI.js +1 -0
- package/dist/assets/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
- package/dist/assets/web/dist/assets/d-qD-0Kul2.js +1 -0
- package/dist/assets/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
- package/dist/assets/web/dist/assets/dart-CnvKMtbv.js +1 -0
- package/dist/assets/web/dist/assets/dax-BkyTk9wS.js +1 -0
- package/dist/assets/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
- package/dist/assets/web/dist/assets/diff-woXpYk--.js +1 -0
- package/dist/assets/web/dist/assets/dist-BVjAWgkb.js +1547 -0
- package/dist/assets/web/dist/assets/docker-IyjqRm3v.js +1 -0
- package/dist/assets/web/dist/assets/dotenv-_5a1GRtc.js +1 -0
- package/dist/assets/web/dist/assets/dracula-BHWKrbxM.js +1 -0
- package/dist/assets/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
- package/dist/assets/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
- package/dist/assets/web/dist/assets/edge-BenMDPct.js +1 -0
- package/dist/assets/web/dist/assets/elixir-CBbdEqG6.js +1 -0
- package/dist/assets/web/dist/assets/elm-DMub_2lY.js +1 -0
- package/dist/assets/web/dist/assets/emacs-lisp-c_oH4hRZ.js +1 -0
- package/dist/assets/web/dist/assets/erb-CI9He_Up.js +1 -0
- package/dist/assets/web/dist/assets/erlang-Cphh6RMH.js +1 -0
- package/dist/assets/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
- package/dist/assets/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
- package/dist/assets/web/dist/assets/{favicon-CFfbl2AI.js → favicon-CWPcvWvp.js} +1 -1
- package/dist/assets/web/dist/assets/fennel-DQxkIbk2.js +1 -0
- package/dist/assets/web/dist/assets/fish-BJitypiv.js +1 -0
- package/dist/assets/web/dist/assets/fluent-C03EYrpw.js +1 -0
- package/dist/assets/web/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
- package/dist/assets/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
- package/dist/assets/web/dist/assets/fsharp-D13ZGOAj.js +1 -0
- package/dist/assets/web/dist/assets/gdresource-xMC7jr7x.js +1 -0
- package/dist/assets/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
- package/dist/assets/web/dist/assets/gdshader-CBce3t8t.js +1 -0
- package/dist/assets/web/dist/assets/genie-CV2tkWYe.js +1 -0
- package/dist/assets/web/dist/assets/gherkin-DExj1W_8.js +1 -0
- package/dist/assets/web/dist/assets/git-commit-BSykSTBG.js +1 -0
- package/dist/assets/web/dist/assets/git-rebase-BBfhnyF8.js +1 -0
- package/dist/assets/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
- package/dist/assets/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
- package/dist/assets/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
- package/dist/assets/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
- package/dist/assets/web/dist/assets/github-light-EUqPIrTm.js +1 -0
- package/dist/assets/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
- package/dist/assets/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
- package/dist/assets/web/dist/assets/gleam-CSRkHgEL.js +1 -0
- package/dist/assets/web/dist/assets/glimmer-js-CR9Fxrau.js +1 -0
- package/dist/assets/web/dist/assets/glimmer-ts-CwFwX_XS.js +1 -0
- package/dist/assets/web/dist/assets/glsl-Daiodp1r.js +1 -0
- package/dist/assets/web/dist/assets/gn-ilITqXS6.js +1 -0
- package/dist/assets/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
- package/dist/assets/web/dist/assets/go-rLFTqkRN.js +1 -0
- package/dist/assets/web/dist/assets/graphql-DZvrsEkB.js +1 -0
- package/dist/assets/web/dist/assets/groovy-CacY0gHj.js +1 -0
- package/dist/assets/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
- package/dist/assets/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
- package/dist/assets/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
- package/dist/assets/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
- package/dist/assets/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
- package/dist/assets/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
- package/dist/assets/web/dist/assets/hack-BaXOzdg7.js +1 -0
- package/dist/assets/web/dist/assets/haml-CMskq_VA.js +1 -0
- package/dist/assets/web/dist/assets/handlebars-Dhw_f7jD.js +1 -0
- package/dist/assets/web/dist/assets/haskell-D8IpX4py.js +1 -0
- package/dist/assets/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
- package/dist/assets/web/dist/assets/hcl-Dh228itO.js +1 -0
- package/dist/assets/web/dist/assets/hjson-CxZEssPk.js +1 -0
- package/dist/assets/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
- package/dist/assets/web/dist/assets/horizon-CE9ld1lL.js +1 -0
- package/dist/assets/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
- package/dist/assets/web/dist/assets/houston-CsvMBhTu.js +1 -0
- package/dist/assets/web/dist/assets/html-C9k99z0z.js +1 -0
- package/dist/assets/web/dist/assets/html-derivative-VckPItXN.js +1 -0
- package/dist/assets/web/dist/assets/http-DXU_3h9v.js +1 -0
- package/dist/assets/web/dist/assets/hurl-KU24JNdp.js +1 -0
- package/dist/assets/web/dist/assets/hxml-Dcm88srW.js +1 -0
- package/dist/assets/web/dist/assets/hy-CZbG8q4J.js +1 -0
- package/dist/assets/web/dist/assets/imba-DsUTQ-LC.js +1 -0
- package/dist/assets/web/dist/assets/indexFormat-B-pd4r7Y.js +1 -0
- package/dist/assets/web/dist/assets/ini-B5eOa1yu.js +1 -0
- package/dist/assets/web/dist/assets/java-BbZjTNgf.js +1 -0
- package/dist/assets/web/dist/assets/javascript-DXCdqcZZ.js +1 -0
- package/dist/assets/web/dist/assets/jinja-pDlsjTVp.js +1 -0
- package/dist/assets/web/dist/assets/jison-DsXDk66e.js +1 -0
- package/dist/assets/web/dist/assets/json-tCxXfRgG.js +1 -0
- package/dist/assets/web/dist/assets/json5-BR5RXkoi.js +1 -0
- package/dist/assets/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
- package/dist/assets/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
- package/dist/assets/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
- package/dist/assets/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
- package/dist/assets/web/dist/assets/jsx-B0ZYkCmi.js +1 -0
- package/dist/assets/web/dist/assets/julia-BLzzeI6x.js +1 -0
- package/dist/assets/web/dist/assets/just-DvD2_60c.js +1 -0
- package/dist/assets/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
- package/dist/assets/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
- package/dist/assets/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
- package/dist/assets/web/dist/assets/kdl-CsD5j6eV.js +1 -0
- package/dist/assets/web/dist/assets/kotlin-DhhofPvG.js +1 -0
- package/dist/assets/web/dist/assets/kusto-C7mF5XQf.js +1 -0
- package/dist/assets/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
- package/dist/assets/web/dist/assets/latex-511zn3h7.js +1 -0
- package/dist/assets/web/dist/assets/lean-CewbzKMR.js +1 -0
- package/dist/assets/web/dist/assets/less-DVTAwKKz.js +1 -0
- package/dist/assets/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
- package/dist/assets/web/dist/assets/liquid-BD0BkMox.js +1 -0
- package/dist/assets/web/dist/assets/llvm-DqHOMS-s.js +1 -0
- package/dist/assets/web/dist/assets/log-BNLmms1o.js +1 -0
- package/dist/assets/web/dist/assets/logo-Cluzi2Zq.js +1 -0
- package/dist/assets/web/dist/assets/lua-NFicKm6N.js +1 -0
- package/dist/assets/web/dist/assets/luau-C2pBCSgt.js +1 -0
- package/dist/assets/web/dist/assets/main-CuENKPdD.css +1 -0
- package/dist/assets/web/dist/assets/main-DZbJaqUb.js +28 -0
- package/dist/assets/web/dist/assets/make-Dixweg8N.js +1 -0
- package/dist/assets/web/dist/assets/markdown-BYOwaDjH.js +1 -0
- package/dist/assets/web/dist/assets/marko-1-G_nEXK.js +1 -0
- package/dist/assets/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
- package/dist/assets/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
- package/dist/assets/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
- package/dist/assets/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
- package/dist/assets/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
- package/dist/assets/web/dist/assets/matlab-D7qyCx1q.js +1 -0
- package/dist/assets/web/dist/assets/mdc-DUe3AQLp.js +1 -0
- package/dist/assets/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
- package/dist/assets/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
- package/dist/assets/web/dist/assets/min-dark-BSWPekZh.js +1 -0
- package/dist/assets/web/dist/assets/min-light-DDpmG2fV.js +1 -0
- package/dist/assets/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
- package/dist/assets/web/dist/assets/mojo-fg40_ntQ.js +1 -0
- package/dist/assets/web/dist/assets/monokai-B_bV9PQ9.js +1 -0
- package/dist/assets/web/dist/assets/moonbit-DgjMoLWr.js +1 -0
- package/dist/assets/web/dist/assets/move-BYnGhZoM.js +1 -0
- package/dist/assets/web/dist/assets/narrat-_X_XdTYD.js +1 -0
- package/dist/assets/web/dist/assets/nextflow-BJtWHP5T.js +1 -0
- package/dist/assets/web/dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
- package/dist/assets/web/dist/assets/nginx-Dg379_bA.js +1 -0
- package/dist/assets/web/dist/assets/night-owl-DhmEMT88.js +1 -0
- package/dist/assets/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
- package/dist/assets/web/dist/assets/nim-BnFq97ZO.js +1 -0
- package/dist/assets/web/dist/assets/nix-IvuFDN5E.js +1 -0
- package/dist/assets/web/dist/assets/nord-Cb4Vim4T.js +1 -0
- package/dist/assets/web/dist/assets/nsis-CjNLYCLg.js +1 -0
- package/dist/assets/web/dist/assets/nushell-DHYfO66s.js +1 -0
- package/dist/assets/web/dist/assets/objective-c-D1A_Heim.js +1 -0
- package/dist/assets/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
- package/dist/assets/web/dist/assets/ocaml-O90oeIOV.js +1 -0
- package/dist/assets/web/dist/assets/odin-B1RWQWA5.js +1 -0
- package/dist/assets/web/dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
- package/dist/assets/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
- package/dist/assets/web/dist/assets/openscad-BUDT5pXO.js +1 -0
- package/dist/assets/web/dist/assets/org-F0uiwGvq.js +1 -0
- package/dist/assets/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
- package/dist/assets/web/dist/assets/perl-B0euczkl.js +1 -0
- package/dist/assets/web/dist/assets/php-DkL3k_n7.js +1 -0
- package/dist/assets/web/dist/assets/pierre-dark-CpLgRqie.js +1 -0
- package/dist/assets/web/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
- package/dist/assets/web/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
- package/dist/assets/web/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
- package/dist/assets/web/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
- package/dist/assets/web/dist/assets/pierre-light-CoaEpmwp.js +1 -0
- package/dist/assets/web/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
- package/dist/assets/web/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
- package/dist/assets/web/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
- package/dist/assets/web/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
- package/dist/assets/web/dist/assets/pkl-ot-7Btpt.js +1 -0
- package/dist/assets/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
- package/dist/assets/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
- package/dist/assets/web/dist/assets/po-BiJDBrnU.js +1 -0
- package/dist/assets/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
- package/dist/assets/web/dist/assets/polar-C7UOKdEL.js +1 -0
- package/dist/assets/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
- package/dist/assets/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
- package/dist/assets/web/dist/assets/powershell-CCSX75yM.js +1 -0
- package/dist/assets/web/dist/assets/prisma-DMgNhnYS.js +1 -0
- package/dist/assets/web/dist/assets/prolog-iXnhIJG7.js +1 -0
- package/dist/assets/web/dist/assets/proto-DB4EqR-F.js +1 -0
- package/dist/assets/web/dist/assets/pug-L_OjjZP4.js +1 -0
- package/dist/assets/web/dist/assets/puppet-CDv2pdJW.js +1 -0
- package/dist/assets/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
- package/dist/assets/web/dist/assets/python-gzcpVVnB.js +1 -0
- package/dist/assets/web/dist/assets/qml-BMjB00Zz.js +1 -0
- package/dist/assets/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
- package/dist/assets/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
- package/dist/assets/web/dist/assets/r-DoeLdnqR.js +1 -0
- package/dist/assets/web/dist/assets/racket-DcIDlBhZ.js +1 -0
- package/dist/assets/web/dist/assets/raku-B3gFvitq.js +1 -0
- package/dist/assets/web/dist/assets/razor-BzYNkAWy.js +1 -0
- package/dist/assets/web/dist/assets/rbs-C0MpOrj4.js +1 -0
- package/dist/assets/web/dist/assets/red-DX1BQOs8.js +1 -0
- package/dist/assets/web/dist/assets/reg-CRGYupPL.js +1 -0
- package/dist/assets/web/dist/assets/regexp-CPmElMk3.js +1 -0
- package/dist/assets/web/dist/assets/rel-BtDbiS_P.js +1 -0
- package/dist/assets/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
- package/dist/assets/web/dist/assets/ron-VUp2lXgN.js +1 -0
- package/dist/assets/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
- package/dist/assets/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
- package/dist/assets/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
- package/dist/assets/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
- package/dist/assets/web/dist/assets/rst-DrsjgfI7.js +1 -0
- package/dist/assets/web/dist/assets/ruby-cB42ppvx.js +1 -0
- package/dist/assets/web/dist/assets/rust-Cfkwpbl8.js +1 -0
- package/dist/assets/web/dist/assets/sas-R5Y3NM_I.js +1 -0
- package/dist/assets/web/dist/assets/sass-DXrisJhu.js +1 -0
- package/dist/assets/web/dist/assets/scala-BiFt_7mu.js +1 -0
- package/dist/assets/web/dist/assets/scheme-DQCgrYNe.js +1 -0
- package/dist/assets/web/dist/assets/scss-C-zWxV9s.js +1 -0
- package/dist/assets/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
- package/dist/assets/web/dist/assets/seed-DeKSHrt2.js +6 -0
- package/dist/assets/web/dist/assets/shaderlab-TOUzSsQk.js +1 -0
- package/dist/assets/web/dist/assets/shellscript-oJF96aAU.js +1 -0
- package/dist/assets/web/dist/assets/shellsession-CznECKyi.js +1 -0
- package/dist/assets/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
- package/dist/assets/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
- package/dist/assets/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
- package/dist/assets/web/dist/assets/smithy-Dgaz7aur.js +1 -0
- package/dist/assets/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
- package/dist/assets/web/dist/assets/solarized-dark-UdsmMDCL.js +1 -0
- package/dist/assets/web/dist/assets/solarized-light-C1IUL_tW.js +1 -0
- package/dist/assets/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
- package/dist/assets/web/dist/assets/soy-wjHLSRag.js +1 -0
- package/dist/assets/web/dist/assets/sparql-D_iOobhT.js +1 -0
- package/dist/assets/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
- package/dist/assets/web/dist/assets/sql-C_BM8IOW.js +1 -0
- package/dist/assets/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
- package/dist/assets/web/dist/assets/stata-D31HO8aQ.js +1 -0
- package/dist/assets/web/dist/assets/stylus-B6D30XZt.js +1 -0
- package/dist/assets/web/dist/assets/surrealql-CKCLyplA.js +1 -0
- package/dist/assets/web/dist/assets/svelte-3geWk2iu.js +1 -0
- package/dist/assets/web/dist/assets/swift-CyEgAFGc.js +1 -0
- package/dist/assets/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
- package/dist/assets/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
- package/dist/assets/web/dist/assets/systemd-BxMlprV5.js +1 -0
- package/dist/assets/web/dist/assets/talonscript-CohzipZa.js +1 -0
- package/dist/assets/web/dist/assets/tasl-DMoTqEGO.js +1 -0
- package/dist/assets/web/dist/assets/tcl-CZd0xW_V.js +1 -0
- package/dist/assets/web/dist/assets/templ-DiJr_hTD.js +1 -0
- package/dist/assets/web/dist/assets/terraform-DswuEJGm.js +1 -0
- package/dist/assets/web/dist/assets/tex-BEJGWyeF.js +1 -0
- package/dist/assets/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
- package/dist/assets/web/dist/assets/toml-CcmNWLt0.js +1 -0
- package/dist/assets/web/dist/assets/ts-tags-C8Mzdpxx.js +1 -0
- package/dist/assets/web/dist/assets/tsv-sltzmVWM.js +1 -0
- package/dist/assets/web/dist/assets/tsx-UQiSm_p3.js +1 -0
- package/dist/assets/web/dist/assets/turtle-ByJddavk.js +1 -0
- package/dist/assets/web/dist/assets/twig-_Pvzkeo9.js +1 -0
- package/dist/assets/web/dist/assets/typescript-BWMkINKK.js +1 -0
- package/dist/assets/web/dist/assets/typespec-BRdr0IET.js +1 -0
- package/dist/assets/web/dist/assets/typst-Bdg9m-e7.js +1 -0
- package/dist/assets/web/dist/assets/v-DBj4K9d_.js +1 -0
- package/dist/assets/web/dist/assets/vala-zf12oZj6.js +1 -0
- package/dist/assets/web/dist/assets/vb-DOwzaHZ0.js +1 -0
- package/dist/assets/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
- package/dist/assets/web/dist/assets/vesper-DdrHHSXu.js +1 -0
- package/dist/assets/web/dist/assets/vhdl-BroJfC0k.js +1 -0
- package/dist/assets/web/dist/assets/viml-DvXPmvsu.js +1 -0
- package/dist/assets/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
- package/dist/assets/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
- package/dist/assets/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
- package/dist/assets/web/dist/assets/vue-CDZrrAZ8.js +1 -0
- package/dist/assets/web/dist/assets/vue-html-tAlpNhfN.js +1 -0
- package/dist/assets/web/dist/assets/vue-vine-C27UF_rh.js +1 -0
- package/dist/assets/web/dist/assets/vyper-CgoNMtux.js +1 -0
- package/dist/assets/web/dist/assets/wallClock-61NpVkY6.js +1 -0
- package/dist/assets/web/dist/assets/wasm-BnjxR4X6.js +1 -0
- package/dist/assets/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
- package/dist/assets/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
- package/dist/assets/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
- package/dist/assets/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
- package/dist/assets/web/dist/assets/wit-DdvCle-K.js +1 -0
- package/dist/assets/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
- package/dist/assets/web/dist/assets/xml-CxeDr9Zh.js +1 -0
- package/dist/assets/web/dist/assets/xsl-Bpi0uUnM.js +1 -0
- package/dist/assets/web/dist/assets/yaml-BtUgZ1GN.js +1 -0
- package/dist/assets/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
- package/dist/assets/web/dist/assets/zig-CMLA9XwU.js +1 -0
- package/dist/cli.js +6936 -3347
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/AppShell-Bw3-_TTE.js +0 -1
- package/dist/assets/web/dist/assets/DeliveryPage-BRwcQyr7.js +0 -1
- package/dist/assets/web/dist/assets/NotFoundPage-RjH-9dPy.js +0 -1
- package/dist/assets/web/dist/assets/ResidentDetailPage-BRy5wkv9.js +0 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-DAEVLN8j.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-B1KHmkZ9.js +0 -126
- package/dist/assets/web/dist/assets/RunRoutePage-CdbrbqU2.css +0 -1
- package/dist/assets/web/dist/assets/RunsIndexPage-3hUWFpFV.js +0 -1
- package/dist/assets/web/dist/assets/RunsTabs-Qn_5TVJU.js +0 -1
- package/dist/assets/web/dist/assets/ScheduledPage-DysVvVm1.js +0 -1
- package/dist/assets/web/dist/assets/StatusDot-Bug2a6T6.js +0 -1
- package/dist/assets/web/dist/assets/Tooltip-C2eEUbwn.js +0 -1
- package/dist/assets/web/dist/assets/main-0uhp-taL.js +0 -28
- package/dist/assets/web/dist/assets/main-Xickfv8V.css +0 -1
- package/dist/assets/web/dist/assets/seed-B8mQEhhw.js +0 -6
- 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
|
+
}
|