@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
|
@@ -10,6 +10,13 @@ import {
|
|
|
10
10
|
} from "../../src/core/memory/engine.ts";
|
|
11
11
|
import { tokenize } from "../../src/core/memory/scorer.ts";
|
|
12
12
|
import { FIRING_DETAIL_MAX, isScheduleFiring, type ScheduleFiring } from "../../src/core/schedules.ts";
|
|
13
|
+
import { REPO_SLUG } from "../../src/core/delivery.ts";
|
|
14
|
+
import {
|
|
15
|
+
isDeliverySnapshot,
|
|
16
|
+
isDeliverySnapshotPatch,
|
|
17
|
+
type DeliverySnapshot,
|
|
18
|
+
type DeliverySnapshotPatch,
|
|
19
|
+
} from "../../src/core/deliverySnapshotStore.ts";
|
|
13
20
|
import {
|
|
14
21
|
applyRetention,
|
|
15
22
|
clampRetentionPolicy,
|
|
@@ -41,6 +48,16 @@ import {
|
|
|
41
48
|
reclaimPhase,
|
|
42
49
|
selectReclaim,
|
|
43
50
|
} from "../../src/core/runLedger/decisions.ts";
|
|
51
|
+
import {
|
|
52
|
+
IDEMPOTENCY_KEY_PATTERN,
|
|
53
|
+
INSTANCE_ID_PATTERN,
|
|
54
|
+
isCoordinatorInstance,
|
|
55
|
+
isCoordinatorUnit,
|
|
56
|
+
sendRunFinished,
|
|
57
|
+
type CoordinatorInstance,
|
|
58
|
+
type CoordinatorUnit,
|
|
59
|
+
type RunFinishedSend,
|
|
60
|
+
} from "../../src/core/coordinator/contract.ts";
|
|
44
61
|
import {
|
|
45
62
|
GEN_PATTERN,
|
|
46
63
|
type ClaimRequest,
|
|
@@ -127,9 +144,20 @@ export interface Env {
|
|
|
127
144
|
CONFIG: DurableObjectNamespace<ConfigDO>;
|
|
128
145
|
/** Live-run transcripts (run-history item 32): one RunTranscriptDO per live run, named by run id. */
|
|
129
146
|
RUN_TRANSCRIPTS: DurableObjectNamespace<RunTranscriptDO>;
|
|
147
|
+
/** Delivery snapshots (delivery item 10): ONE DeliveryDO (named "delivery"), one snapshot per repository. */
|
|
148
|
+
DELIVERY: DurableObjectNamespace<DeliveryDO>;
|
|
149
|
+
/** The ship coordinator Workflow in the bot's shim Worker (run-history item
|
|
150
|
+
* 47): where `RunHistoryDO.finish` sends `run finished:<runId>` for a record
|
|
151
|
+
* carrying `parentInstanceId`. Optional: this Worker deploys without it (the
|
|
152
|
+
* binding is a cross-script one, and the class must exist on the bot before
|
|
153
|
+
* the state Worker may name it), and a finish then commits with no event. */
|
|
154
|
+
SHIP_COORDINATOR?: Workflow;
|
|
130
155
|
MEMORY_TOKEN?: string;
|
|
131
156
|
}
|
|
132
157
|
|
|
158
|
+
/** The single DeliveryDO's name — every repository's snapshot lives in one object. */
|
|
159
|
+
const DELIVERY_OBJECT = "delivery";
|
|
160
|
+
|
|
133
161
|
/** The single ConfigDO's name. */
|
|
134
162
|
const CONFIG_OBJECT = "config";
|
|
135
163
|
/** A config document key: short, lowercase, like `overrides`. */
|
|
@@ -142,6 +170,8 @@ const SCHEDULES_OBJECT = "schedules";
|
|
|
142
170
|
const MAX_LIMIT = 50;
|
|
143
171
|
/** Candidates per write batch (the reflection pass emits ≤6). */
|
|
144
172
|
const MAX_BATCH = 50;
|
|
173
|
+
/** Unit rows one put may carry: a plan has tens of units, never hundreds. */
|
|
174
|
+
const MAX_UNITS_PER_PUT = 200;
|
|
145
175
|
const MAX_TEXT_CHARS = 4000;
|
|
146
176
|
const MAX_KEYWORDS = 20;
|
|
147
177
|
const MAX_KEYWORD_CHARS = 64;
|
|
@@ -700,6 +730,172 @@ function parseStored<T>(text: string, guard: (v: unknown) => v is T): T | null {
|
|
|
700
730
|
}
|
|
701
731
|
}
|
|
702
732
|
|
|
733
|
+
// ---------------------------------------------------------------------------
|
|
734
|
+
// Durable Object: delivery snapshots, one per repository
|
|
735
|
+
// ---------------------------------------------------------------------------
|
|
736
|
+
|
|
737
|
+
// The delivery page's snapshot (docs/reference/specs/delivery.md item 10): the
|
|
738
|
+
// merged pull requests' facts over the snapshot window, as GitHub gave them,
|
|
739
|
+
// and when they were read. A busy repository's window is many MB — the review
|
|
740
|
+
// bodies and every workflow run of every branch — so a snapshot is stored as
|
|
741
|
+
// one row per pull request under a meta row, never as one JSON value (the
|
|
742
|
+
// per-row limit is 2 MB). A put replaces the repository's snapshot whole, in
|
|
743
|
+
// one transaction — the first read; a merge applies a refresh — the rows it
|
|
744
|
+
// re-read replace theirs by number, the rows that aged out go, the meta is
|
|
745
|
+
// replaced — so the hourly write is the change, not the window; a get
|
|
746
|
+
// reassembles the snapshot in pull request number order.
|
|
747
|
+
|
|
748
|
+
/** One pull request's facts may not exceed a fraction of the row limit; the fence names the pull request. */
|
|
749
|
+
const MAX_PULL_REQUEST_FACTS_BYTES = 1024 * 1024;
|
|
750
|
+
|
|
751
|
+
export class DeliveryDO extends DurableObject<Env> {
|
|
752
|
+
private readonly sql: SqlStorage;
|
|
753
|
+
|
|
754
|
+
constructor(ctx: DurableObjectState, env: Env) {
|
|
755
|
+
super(ctx, env);
|
|
756
|
+
this.sql = ctx.storage.sql;
|
|
757
|
+
this.sql.exec(`
|
|
758
|
+
CREATE TABLE IF NOT EXISTS snapshots (
|
|
759
|
+
repo TEXT PRIMARY KEY,
|
|
760
|
+
snapshot_at TEXT NOT NULL,
|
|
761
|
+
meta TEXT NOT NULL
|
|
762
|
+
);
|
|
763
|
+
CREATE TABLE IF NOT EXISTS pull_requests (
|
|
764
|
+
repo TEXT NOT NULL,
|
|
765
|
+
number INTEGER NOT NULL,
|
|
766
|
+
facts TEXT NOT NULL,
|
|
767
|
+
PRIMARY KEY (repo, number)
|
|
768
|
+
);
|
|
769
|
+
`);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/** Replace the repository's snapshot: the meta row and one row per pull request, in one transaction. */
|
|
773
|
+
async put(snapshot: DeliverySnapshot): Promise<number> {
|
|
774
|
+
const { prs, ...meta } = snapshot;
|
|
775
|
+
this.ctx.storage.transactionSync(() => {
|
|
776
|
+
this.sql.exec(`DELETE FROM pull_requests WHERE repo = ?`, snapshot.repo);
|
|
777
|
+
for (const pr of prs) {
|
|
778
|
+
this.sql.exec(
|
|
779
|
+
`INSERT OR REPLACE INTO pull_requests (repo, number, facts) VALUES (?, ?, ?)`,
|
|
780
|
+
snapshot.repo,
|
|
781
|
+
pr.number,
|
|
782
|
+
JSON.stringify(pr),
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
this.sql.exec(
|
|
786
|
+
`INSERT OR REPLACE INTO snapshots (repo, snapshot_at, meta) VALUES (?, ?, ?)`,
|
|
787
|
+
snapshot.repo,
|
|
788
|
+
snapshot.snapshotAt,
|
|
789
|
+
JSON.stringify(meta),
|
|
790
|
+
);
|
|
791
|
+
});
|
|
792
|
+
return prs.length;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/** Apply a refresh to the repository's snapshot in one transaction; the rows now stored, or null
|
|
796
|
+
* when the repository has no snapshot to merge into (a partial snapshot would claim a
|
|
797
|
+
* completeness it lacks — the caller writes whole instead). */
|
|
798
|
+
async merge(patch: DeliverySnapshotPatch): Promise<number | null> {
|
|
799
|
+
const { upsert, drop, ...meta } = patch;
|
|
800
|
+
return this.ctx.storage.transactionSync(() => {
|
|
801
|
+
const stored = this.sql.exec(`SELECT 1 FROM snapshots WHERE repo = ?`, patch.repo).toArray().length > 0;
|
|
802
|
+
if (!stored) return null;
|
|
803
|
+
for (const pr of upsert) {
|
|
804
|
+
this.sql.exec(
|
|
805
|
+
`INSERT OR REPLACE INTO pull_requests (repo, number, facts) VALUES (?, ?, ?)`,
|
|
806
|
+
patch.repo,
|
|
807
|
+
pr.number,
|
|
808
|
+
JSON.stringify(pr),
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
for (const number of drop) {
|
|
812
|
+
this.sql.exec(`DELETE FROM pull_requests WHERE repo = ? AND number = ?`, patch.repo, number);
|
|
813
|
+
}
|
|
814
|
+
this.sql.exec(
|
|
815
|
+
`INSERT OR REPLACE INTO snapshots (repo, snapshot_at, meta) VALUES (?, ?, ?)`,
|
|
816
|
+
patch.repo,
|
|
817
|
+
patch.snapshotAt,
|
|
818
|
+
JSON.stringify(meta),
|
|
819
|
+
);
|
|
820
|
+
const count = this.sql
|
|
821
|
+
.exec<{ n: number }>(`SELECT COUNT(*) AS n FROM pull_requests WHERE repo = ?`, patch.repo)
|
|
822
|
+
.toArray()[0];
|
|
823
|
+
return count?.n ?? 0;
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/** The repository's snapshot, pull requests in number order; null when none was stored. */
|
|
828
|
+
async get(repo: string): Promise<DeliverySnapshot | null> {
|
|
829
|
+
const row = this.sql.exec<{ meta: string }>(`SELECT meta FROM snapshots WHERE repo = ?`, repo).toArray()[0];
|
|
830
|
+
if (!row) return null;
|
|
831
|
+
const prs = this.sql
|
|
832
|
+
.exec<{ facts: string }>(`SELECT facts FROM pull_requests WHERE repo = ? ORDER BY number`, repo)
|
|
833
|
+
.toArray()
|
|
834
|
+
.map((r) => JSON.parse(r.facts) as DeliverySnapshot["prs"][number]);
|
|
835
|
+
return { ...(JSON.parse(row.meta) as Omit<DeliverySnapshot, "prs">), prs };
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const DELIVERY_ROUTES = new Set(["/delivery/get", "/delivery/put", "/delivery/merge"]);
|
|
840
|
+
|
|
841
|
+
/** The pull request whose facts exceed the row fence, if any — checked before the transaction. */
|
|
842
|
+
function oversizedFacts(prs: readonly DeliverySnapshot["prs"][number][]): number | undefined {
|
|
843
|
+
const encoder = new TextEncoder();
|
|
844
|
+
return prs.find((pr) => encoder.encode(JSON.stringify(pr)).byteLength > MAX_PULL_REQUEST_FACTS_BYTES)?.number;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
async function handleDelivery(pathname: string, body: unknown, env: Env): Promise<Response> {
|
|
848
|
+
const b = (typeof body === "object" && body !== null ? body : {}) as Record<string, unknown>;
|
|
849
|
+
const dO = env.DELIVERY.get(env.DELIVERY.idFromName(DELIVERY_OBJECT));
|
|
850
|
+
if (pathname === "/delivery/get") {
|
|
851
|
+
if (typeof b.repo !== "string" || !REPO_SLUG.test(b.repo)) return json({ error: "repo must be owner/name" }, 400);
|
|
852
|
+
const snapshot = await dO.get(b.repo);
|
|
853
|
+
console.log(
|
|
854
|
+
`[delivery/get] ${b.repo} -> ${snapshot ? `${snapshot.prs.length} pull requests as of ${snapshot.snapshotAt}` : "none"}`,
|
|
855
|
+
);
|
|
856
|
+
return json({ snapshot });
|
|
857
|
+
}
|
|
858
|
+
if (pathname === "/delivery/put") {
|
|
859
|
+
if (!isDeliverySnapshot(b.snapshot))
|
|
860
|
+
return json(
|
|
861
|
+
{ error: "snapshot must be a DeliverySnapshot (repo, snapshotAt, range, prs[], truncated, completeFrom)" },
|
|
862
|
+
400,
|
|
863
|
+
);
|
|
864
|
+
const oversized = oversizedFacts(b.snapshot.prs);
|
|
865
|
+
if (oversized !== undefined)
|
|
866
|
+
return json(
|
|
867
|
+
{ error: `pull request ${oversized}'s facts must be at most ${MAX_PULL_REQUEST_FACTS_BYTES} bytes` },
|
|
868
|
+
413,
|
|
869
|
+
);
|
|
870
|
+
const prs = await dO.put(b.snapshot);
|
|
871
|
+
console.log(`[delivery/put] ${b.snapshot.repo} <- ${prs} pull requests as of ${b.snapshot.snapshotAt}`);
|
|
872
|
+
return json({ ok: true, prs });
|
|
873
|
+
}
|
|
874
|
+
if (pathname === "/delivery/merge") {
|
|
875
|
+
if (!isDeliverySnapshotPatch(b.patch))
|
|
876
|
+
return json(
|
|
877
|
+
{
|
|
878
|
+
error:
|
|
879
|
+
"patch must be a DeliverySnapshotPatch (repo, snapshotAt, range, truncated, completeFrom, upsert[], drop[])",
|
|
880
|
+
},
|
|
881
|
+
400,
|
|
882
|
+
);
|
|
883
|
+
const oversized = oversizedFacts(b.patch.upsert);
|
|
884
|
+
if (oversized !== undefined)
|
|
885
|
+
return json(
|
|
886
|
+
{ error: `pull request ${oversized}'s facts must be at most ${MAX_PULL_REQUEST_FACTS_BYTES} bytes` },
|
|
887
|
+
413,
|
|
888
|
+
);
|
|
889
|
+
const prs = await dO.merge(b.patch);
|
|
890
|
+
if (prs === null) return json({ error: `no snapshot for ${b.patch.repo} to merge into` }, 404);
|
|
891
|
+
console.log(
|
|
892
|
+
`[delivery/merge] ${b.patch.repo} <- ${b.patch.upsert.length} pull requests re-read, ${b.patch.drop.length} dropped, ${prs} stored as of ${b.patch.snapshotAt}`,
|
|
893
|
+
);
|
|
894
|
+
return json({ ok: true, prs });
|
|
895
|
+
}
|
|
896
|
+
return json({ error: "not found" }, 404);
|
|
897
|
+
}
|
|
898
|
+
|
|
703
899
|
/** Documents are small; a body over this is refused before storage. */
|
|
704
900
|
const MAX_CONFIG_DOCUMENT_BYTES = 256 * 1024;
|
|
705
901
|
|
|
@@ -977,6 +1173,102 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
977
1173
|
PRIMARY KEY (run_id, kind)
|
|
978
1174
|
);
|
|
979
1175
|
`);
|
|
1176
|
+
// The coordinator's parent records (run-history item 49): one row per
|
|
1177
|
+
// instance, written by the bot at the instance's creation and read by the
|
|
1178
|
+
// spawn route for the requester, channel and thread every child acts as.
|
|
1179
|
+
this.sql.exec(`
|
|
1180
|
+
CREATE TABLE IF NOT EXISTS coordinator_instances (
|
|
1181
|
+
instance_id TEXT PRIMARY KEY,
|
|
1182
|
+
json TEXT NOT NULL,
|
|
1183
|
+
created_at INTEGER NOT NULL
|
|
1184
|
+
);
|
|
1185
|
+
`);
|
|
1186
|
+
// The units of the plan an instance runs (run-history item 50): one row per
|
|
1187
|
+
// (instance, unit), replaced whole as the runner reaches the unit; the
|
|
1188
|
+
// rowid keeps the order the rows were first written — the plan's.
|
|
1189
|
+
this.sql.exec(`
|
|
1190
|
+
CREATE TABLE IF NOT EXISTS coordinator_units (
|
|
1191
|
+
instance_id TEXT NOT NULL,
|
|
1192
|
+
unit TEXT NOT NULL,
|
|
1193
|
+
json TEXT NOT NULL,
|
|
1194
|
+
updated_at INTEGER NOT NULL,
|
|
1195
|
+
PRIMARY KEY (instance_id, unit)
|
|
1196
|
+
);
|
|
1197
|
+
`);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// ---- the coordinator's parent records (run-history item 49) -----------------
|
|
1201
|
+
|
|
1202
|
+
/** Idempotent for the same record; a different record under a taken id is refused. */
|
|
1203
|
+
async putInstance(instance: CoordinatorInstance): Promise<{ ok: true } | { ok: false; reason: "exists" }> {
|
|
1204
|
+
let out: { ok: true } | { ok: false; reason: "exists" } = { ok: true };
|
|
1205
|
+
this.ctx.storage.transactionSync(() => {
|
|
1206
|
+
const text = JSON.stringify(instance);
|
|
1207
|
+
const existing = this.sql
|
|
1208
|
+
.exec<{ json: string }>(`SELECT json FROM coordinator_instances WHERE instance_id = ?`, instance.id)
|
|
1209
|
+
.toArray()[0];
|
|
1210
|
+
if (existing) {
|
|
1211
|
+
if (existing.json !== text) out = { ok: false, reason: "exists" };
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
this.sql.exec(
|
|
1215
|
+
`INSERT INTO coordinator_instances (instance_id, json, created_at) VALUES (?, ?, ?)`,
|
|
1216
|
+
instance.id,
|
|
1217
|
+
text,
|
|
1218
|
+
instance.createdAt,
|
|
1219
|
+
);
|
|
1220
|
+
});
|
|
1221
|
+
return out;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/** The record written over whatever the id holds and the id's unit rows
|
|
1225
|
+
* dropped, in one transaction — an attempt starting over: the leftover of one
|
|
1226
|
+
* whose Workflow instance was never created, once the shim said so. */
|
|
1227
|
+
async replaceInstance(instance: CoordinatorInstance): Promise<{ ok: true }> {
|
|
1228
|
+
this.ctx.storage.transactionSync(() => {
|
|
1229
|
+
this.sql.exec(
|
|
1230
|
+
`INSERT INTO coordinator_instances (instance_id, json, created_at) VALUES (?, ?, ?)
|
|
1231
|
+
ON CONFLICT(instance_id) DO UPDATE SET json = excluded.json, created_at = excluded.created_at`,
|
|
1232
|
+
instance.id,
|
|
1233
|
+
JSON.stringify(instance),
|
|
1234
|
+
instance.createdAt,
|
|
1235
|
+
);
|
|
1236
|
+
this.sql.exec(`DELETE FROM coordinator_units WHERE instance_id = ?`, instance.id);
|
|
1237
|
+
});
|
|
1238
|
+
return { ok: true };
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
async getInstance(id: string): Promise<CoordinatorInstance | null> {
|
|
1242
|
+
const row = this.sql
|
|
1243
|
+
.exec<{ json: string }>(`SELECT json FROM coordinator_instances WHERE instance_id = ?`, id)
|
|
1244
|
+
.toArray()[0];
|
|
1245
|
+
return row ? (JSON.parse(row.json) as CoordinatorInstance) : null;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// ---- the units of the plan an instance runs (run-history item 50) -----------
|
|
1249
|
+
|
|
1250
|
+
/** Each row replaced whole under its (instance, unit); a replace keeps the row's place. */
|
|
1251
|
+
async putUnits(units: CoordinatorUnit[], now: number): Promise<{ ok: true }> {
|
|
1252
|
+
this.ctx.storage.transactionSync(() => {
|
|
1253
|
+
for (const u of units) {
|
|
1254
|
+
this.sql.exec(
|
|
1255
|
+
`INSERT INTO coordinator_units (instance_id, unit, json, updated_at) VALUES (?, ?, ?, ?)
|
|
1256
|
+
ON CONFLICT(instance_id, unit) DO UPDATE SET json = excluded.json, updated_at = excluded.updated_at`,
|
|
1257
|
+
u.instanceId,
|
|
1258
|
+
u.unit,
|
|
1259
|
+
JSON.stringify(u),
|
|
1260
|
+
now,
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
return { ok: true };
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
async listUnits(instanceId: string): Promise<CoordinatorUnit[]> {
|
|
1268
|
+
return this.sql
|
|
1269
|
+
.exec<{ json: string }>(`SELECT json FROM coordinator_units WHERE instance_id = ? ORDER BY rowid`, instanceId)
|
|
1270
|
+
.toArray()
|
|
1271
|
+
.map((r) => JSON.parse(r.json) as CoordinatorUnit);
|
|
980
1272
|
}
|
|
981
1273
|
|
|
982
1274
|
// ---- the live-run ledger (run-history items 28–34) --------------------------
|
|
@@ -1004,6 +1296,7 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
1004
1296
|
agent: existing.meta.agent,
|
|
1005
1297
|
startedAt: existing.startedAt,
|
|
1006
1298
|
ownerGen: existing.ownerGen,
|
|
1299
|
+
idempotencyKey: existing.meta.idempotencyKey,
|
|
1007
1300
|
}
|
|
1008
1301
|
: undefined,
|
|
1009
1302
|
req,
|
|
@@ -1185,13 +1478,17 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
1185
1478
|
return out;
|
|
1186
1479
|
}
|
|
1187
1480
|
|
|
1188
|
-
/** The finished record replaces the live rows in ONE transaction (item 33).
|
|
1481
|
+
/** The finished record replaces the live rows in ONE transaction (item 33).
|
|
1482
|
+
* Fenced. Then, for a record carrying `parentInstanceId`, ONE `run
|
|
1483
|
+
* finished:<runId>` to that coordinator instance (item 47) — after the
|
|
1484
|
+
* commit, never inside it, and never able to undo it: a refused send (the
|
|
1485
|
+
* instance ended, no binding) is the answer's `event`, not an error. */
|
|
1189
1486
|
async finish(
|
|
1190
1487
|
runId: string,
|
|
1191
1488
|
gen: string,
|
|
1192
1489
|
record: RunRecord,
|
|
1193
1490
|
proposal?: RunPolicyProposal,
|
|
1194
|
-
): Promise<FenceResult & { stored?: boolean }> {
|
|
1491
|
+
): Promise<FenceResult & { stored?: boolean; event?: RunFinishedSend["kind"] }> {
|
|
1195
1492
|
let out: FenceResult & { stored?: boolean } = { ok: true };
|
|
1196
1493
|
this.ctx.storage.transactionSync(() => {
|
|
1197
1494
|
const fence = checkFence(this.liveRow(runId), gen);
|
|
@@ -1203,9 +1500,13 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
1203
1500
|
this.deleteLiveRows([runId]);
|
|
1204
1501
|
out = { ok: true, stored: put.stored };
|
|
1205
1502
|
});
|
|
1503
|
+
if (!out.ok) return out;
|
|
1206
1504
|
if ((await this.ctx.storage.getAlarm()) === null)
|
|
1207
1505
|
await this.ctx.storage.setAlarm(systemClock() + RUN_SWEEP_INTERVAL_MS);
|
|
1208
|
-
|
|
1506
|
+
const event = await sendRunFinished(this.env.SHIP_COORDINATOR, record);
|
|
1507
|
+
if (event.kind === "failed")
|
|
1508
|
+
console.warn(`[runs/finish] ${runId} → run finished not delivered to ${event.instance}: ${event.reason}`);
|
|
1509
|
+
return { ...out, event: event.kind };
|
|
1209
1510
|
}
|
|
1210
1511
|
|
|
1211
1512
|
/** The live rows go with no record (item 42): a reserved run that never
|
|
@@ -2157,6 +2458,11 @@ export class RunTranscriptDO extends DurableObject<Env> {
|
|
|
2157
2458
|
}
|
|
2158
2459
|
|
|
2159
2460
|
const LEDGER_ROUTES = new Set([
|
|
2461
|
+
"/runs/coordinator/put",
|
|
2462
|
+
"/runs/coordinator/replace",
|
|
2463
|
+
"/runs/coordinator/get",
|
|
2464
|
+
"/runs/coordinator/units/put",
|
|
2465
|
+
"/runs/coordinator/units/list",
|
|
2160
2466
|
"/runs/claim",
|
|
2161
2467
|
"/runs/heartbeat",
|
|
2162
2468
|
"/runs/append",
|
|
@@ -2180,6 +2486,17 @@ const LEDGER_ROUTES = new Set([
|
|
|
2180
2486
|
|
|
2181
2487
|
/** Routes whose bodies may carry a record, a transcript chunk, or an event batch. */
|
|
2182
2488
|
const WIDE_BODY_ROUTES = new Set(["/runs/put", "/runs/finish", "/runs/append", "/runs/transcript/write"]);
|
|
2489
|
+
/** A delivery snapshot written whole, or a refresh's patch: every merged pull request's reviews and
|
|
2490
|
+
* its branch's workflow runs — about 7 KB a pull request (measured: 291 pull requests, 2.1 MB), so
|
|
2491
|
+
* a first read at the listing cap is under 6 MB and a busy repository's whole window many MB. */
|
|
2492
|
+
const MAX_SNAPSHOT_BODY_BYTES = 16 * 1024 * 1024;
|
|
2493
|
+
|
|
2494
|
+
/** The request body ceiling per route, decided after routing and before the parse. */
|
|
2495
|
+
function bodyFenceFor(pathname: string): number {
|
|
2496
|
+
if (WIDE_BODY_ROUTES.has(pathname)) return MAX_RUN_PUT_BODY_BYTES;
|
|
2497
|
+
if (pathname === "/delivery/put" || pathname === "/delivery/merge") return MAX_SNAPSHOT_BODY_BYTES;
|
|
2498
|
+
return MAX_BODY_BYTES;
|
|
2499
|
+
}
|
|
2183
2500
|
|
|
2184
2501
|
const gen = (v: unknown): Validated<string> =>
|
|
2185
2502
|
typeof v === "string" && GEN_PATTERN.test(v)
|
|
@@ -2209,6 +2526,20 @@ function parseClaim(b: Record<string, unknown>): Validated<ClaimRequest> {
|
|
|
2209
2526
|
if (typeof r.startedAt !== "number" || !Number.isFinite(r.startedAt))
|
|
2210
2527
|
return invalid("run.startedAt must be a number");
|
|
2211
2528
|
if (typeof r.meta !== "object" || r.meta === null) return invalid("run.meta must be an object");
|
|
2529
|
+
// A coordinator's tag (run-history item 48) is stored at the claim and read
|
|
2530
|
+
// by the finish's send and the refusal a second claim meets: shaped or
|
|
2531
|
+
// refused, and both fields or neither — one alone is no tag.
|
|
2532
|
+
const meta = r.meta as Record<string, unknown>;
|
|
2533
|
+
if ((meta.parentInstanceId === undefined) !== (meta.idempotencyKey === undefined))
|
|
2534
|
+
return invalid("run.meta.parentInstanceId and run.meta.idempotencyKey come together or not at all");
|
|
2535
|
+
if (meta.parentInstanceId !== undefined) {
|
|
2536
|
+
if (typeof meta.parentInstanceId !== "string" || !INSTANCE_ID_PATTERN.test(meta.parentInstanceId))
|
|
2537
|
+
return invalid("run.meta.parentInstanceId must be a Workflow instance id");
|
|
2538
|
+
}
|
|
2539
|
+
if (meta.idempotencyKey !== undefined) {
|
|
2540
|
+
if (typeof meta.idempotencyKey !== "string" || !IDEMPOTENCY_KEY_PATTERN.test(meta.idempotencyKey))
|
|
2541
|
+
return invalid("run.meta.idempotencyKey must be <parentInstanceId>:<step>");
|
|
2542
|
+
}
|
|
2212
2543
|
if (typeof r.system !== "string") return invalid("run.system must be a string");
|
|
2213
2544
|
if (!Array.isArray(r.tools)) return invalid("run.tools must be an array");
|
|
2214
2545
|
if (r.card !== undefined && r.card !== null) {
|
|
@@ -2359,6 +2690,44 @@ async function handleLedger(pathname: string, body: unknown, env: Env): Promise<
|
|
|
2359
2690
|
return json(r);
|
|
2360
2691
|
}
|
|
2361
2692
|
|
|
2693
|
+
// The coordinator's parent records (run-history item 49): the record whole,
|
|
2694
|
+
// validated by the shared contract; a read by instance id.
|
|
2695
|
+
if (pathname === "/runs/coordinator/put") {
|
|
2696
|
+
if (!isCoordinatorInstance(b.instance))
|
|
2697
|
+
return json({ error: "instance must be a coordinator instance record" }, 400);
|
|
2698
|
+
const r = await stub.putInstance(b.instance);
|
|
2699
|
+
console.log(`[runs/coordinator/put] ${key.value} ${b.instance.id} → ${r.ok ? "stored" : r.reason}`);
|
|
2700
|
+
return r.ok ? json(r) : json(r, 409);
|
|
2701
|
+
}
|
|
2702
|
+
if (pathname === "/runs/coordinator/replace") {
|
|
2703
|
+
if (!isCoordinatorInstance(b.instance))
|
|
2704
|
+
return json({ error: "instance must be a coordinator instance record" }, 400);
|
|
2705
|
+
const r = await stub.replaceInstance(b.instance);
|
|
2706
|
+
console.log(`[runs/coordinator/replace] ${key.value} ${b.instance.id} → replaced`);
|
|
2707
|
+
return json(r);
|
|
2708
|
+
}
|
|
2709
|
+
if (pathname === "/runs/coordinator/get") {
|
|
2710
|
+
if (typeof b.id !== "string" || !INSTANCE_ID_PATTERN.test(b.id))
|
|
2711
|
+
return json({ error: "id must be a Workflow instance id" }, 400);
|
|
2712
|
+
return json({ instance: await stub.getInstance(b.id) });
|
|
2713
|
+
}
|
|
2714
|
+
// The units of the plan an instance runs (run-history item 50): rows
|
|
2715
|
+
// validated by the shared contract, each replaced whole; a list by instance.
|
|
2716
|
+
if (pathname === "/runs/coordinator/units/put") {
|
|
2717
|
+
if (!Array.isArray(b.units) || b.units.length === 0 || b.units.length > MAX_UNITS_PER_PUT)
|
|
2718
|
+
return json({ error: `units must be a non-empty array of at most ${MAX_UNITS_PER_PUT} unit rows` }, 400);
|
|
2719
|
+
if (!b.units.every(isCoordinatorUnit)) return json({ error: "every unit must be a coordinator unit row" }, 400);
|
|
2720
|
+
const units = b.units as CoordinatorUnit[];
|
|
2721
|
+
const r = await stub.putUnits(units, now);
|
|
2722
|
+
console.log(`[runs/coordinator/units/put] ${key.value} ${units[0]!.instanceId} ${units.length} row(s)`);
|
|
2723
|
+
return json(r);
|
|
2724
|
+
}
|
|
2725
|
+
if (pathname === "/runs/coordinator/units/list") {
|
|
2726
|
+
if (typeof b.instanceId !== "string" || !INSTANCE_ID_PATTERN.test(b.instanceId))
|
|
2727
|
+
return json({ error: "instanceId must be a Workflow instance id" }, 400);
|
|
2728
|
+
return json({ units: await stub.listUnits(b.instanceId) });
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2362
2731
|
const runId = parseRunId(b.runId);
|
|
2363
2732
|
if (!runId.ok) return json({ error: runId.error }, 400);
|
|
2364
2733
|
if (pathname === "/runs/inbox") {
|
|
@@ -2422,7 +2791,9 @@ async function handleLedger(pathname: string, body: unknown, env: Env): Promise<
|
|
|
2422
2791
|
if (!parsed.ok) return json({ error: parsed.error }, 400);
|
|
2423
2792
|
if (parsed.value.record.id !== runId.value) return json({ error: "record.id must equal runId" }, 400);
|
|
2424
2793
|
const r = await stub.finish(runId.value, g.value, parsed.value.record, parsed.value.proposal);
|
|
2425
|
-
console.log(
|
|
2794
|
+
console.log(
|
|
2795
|
+
`[runs/finish] ${key.value} ${runId.value} ok=${r.ok}${r.ok ? ` stored=${r.stored} event=${r.event}` : ` ${r.reason}`}`,
|
|
2796
|
+
);
|
|
2426
2797
|
return r.ok ? json(r) : json(r, 409);
|
|
2427
2798
|
}
|
|
2428
2799
|
return json({ error: "not found" }, 404);
|
|
@@ -2486,6 +2857,7 @@ async function handleRuns(pathname: string, body: unknown, env: Env): Promise<Re
|
|
|
2486
2857
|
|
|
2487
2858
|
const ROUTES = new Set([
|
|
2488
2859
|
...CONFIG_ROUTES,
|
|
2860
|
+
...DELIVERY_ROUTES,
|
|
2489
2861
|
"/retrieve",
|
|
2490
2862
|
"/write",
|
|
2491
2863
|
"/list",
|
|
@@ -2513,7 +2885,7 @@ interface Admission {
|
|
|
2513
2885
|
async function handleRequest(request: Request, env: Env, admission: Admission): Promise<Response> {
|
|
2514
2886
|
const url = new URL(request.url);
|
|
2515
2887
|
if (url.pathname === "/healthz" && request.method === "GET")
|
|
2516
|
-
return json({ ok: true, build: BUILD, features: ["memory", "schedules", "runs", "config"] });
|
|
2888
|
+
return json({ ok: true, build: BUILD, features: ["memory", "schedules", "runs", "config", "delivery"] });
|
|
2517
2889
|
if (!admission.known) return json({ error: "not found" }, 404);
|
|
2518
2890
|
if (request.method !== "POST") return json({ error: "method not allowed" }, 405);
|
|
2519
2891
|
if (!admission.authorized) return json({ error: "unauthorized" }, 401);
|
|
@@ -2533,8 +2905,9 @@ async function handleRequest(request: Request, env: Env, admission: Admission):
|
|
|
2533
2905
|
return json({ error: "body must declare a numeric Content-Length" }, 411);
|
|
2534
2906
|
}
|
|
2535
2907
|
// The ledger's bulk routes carry a finished record, a transcript chunk, or
|
|
2536
|
-
// an event batch (32 × 64 KiB) and share /runs/put's fence
|
|
2537
|
-
|
|
2908
|
+
// an event batch (32 × 64 KiB) and share /runs/put's fence; a delivery
|
|
2909
|
+
// snapshot carries a whole window of pull request facts and has its own.
|
|
2910
|
+
const maxBodyBytes = bodyFenceFor(url.pathname);
|
|
2538
2911
|
if (Number(header) > maxBodyBytes) {
|
|
2539
2912
|
return json({ error: `body must be at most ${maxBodyBytes} bytes` }, 413);
|
|
2540
2913
|
}
|
|
@@ -2563,6 +2936,7 @@ async function handleRequest(request: Request, env: Env, admission: Admission):
|
|
|
2563
2936
|
}
|
|
2564
2937
|
if (url.pathname.startsWith("/runs/")) return handleRuns(url.pathname, body, env);
|
|
2565
2938
|
if (url.pathname.startsWith("/config/")) return handleConfig(url.pathname, body, env);
|
|
2939
|
+
if (url.pathname.startsWith("/delivery/")) return handleDelivery(url.pathname, body, env);
|
|
2566
2940
|
|
|
2567
2941
|
if (url.pathname === "/retrieve") {
|
|
2568
2942
|
const parsed = parseRetrieve(body);
|
|
@@ -31,9 +31,28 @@
|
|
|
31
31
|
// transcript a resumed run continues from (docs/reference/specs/run-history.md item
|
|
32
32
|
// 32). Kept apart from the history object so transcript bytes never queue
|
|
33
33
|
// behind an admission decision; cleared at finish.
|
|
34
|
-
{ "name": "RUN_TRANSCRIPTS", "class_name": "RunTranscriptDO" }
|
|
34
|
+
{ "name": "RUN_TRANSCRIPTS", "class_name": "RunTranscriptDO" },
|
|
35
|
+
// ONE DeliveryDO (named "delivery"): the delivery page's snapshot per
|
|
36
|
+
// repository — the merged pull requests' facts over the window, one row
|
|
37
|
+
// each, and when they were read (docs/reference/specs/delivery.md item 10).
|
|
38
|
+
{ "name": "DELIVERY", "class_name": "DeliveryDO" }
|
|
35
39
|
]
|
|
36
40
|
},
|
|
41
|
+
// The bot shim's ship coordinator Workflow, bound across scripts by the bot's
|
|
42
|
+
// script name (docs/reference/specs/run-history.md item 47): `RunHistoryDO.finish`
|
|
43
|
+
// sends it `run finished:<runId>` for a record carrying `parentInstanceId`.
|
|
44
|
+
// The name is the bot's own Workflow name (`<bot script>-ship-coordinator`,
|
|
45
|
+
// deploy/cloudflare/wrangler.template.jsonc). The class is deployed with the
|
|
46
|
+
// bot, which deploys after this Worker — so this binding lands one release
|
|
47
|
+
// after the class did, never in the same one.
|
|
48
|
+
"workflows": [
|
|
49
|
+
{
|
|
50
|
+
"name": "{{bot.script}}-ship-coordinator",
|
|
51
|
+
"binding": "SHIP_COORDINATOR",
|
|
52
|
+
"class_name": "ShipCoordinator",
|
|
53
|
+
"script_name": "{{bot.script}}"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
37
56
|
// SQLite-backed DOs (durable across restarts — AGENTS.md invariant 6).
|
|
38
57
|
"migrations": [
|
|
39
58
|
{ "tag": "v1", "new_sqlite_classes": ["MemoryDO"] },
|
|
@@ -45,6 +64,7 @@
|
|
|
45
64
|
// The friction ledger is read from run history (RunHistoryDO carries every
|
|
46
65
|
// run's diagnosis); the FrictionDO that once mirrored those diagnoses is
|
|
47
66
|
// retired with its rows — docs/reference/specs/self-improvement.md item 1.
|
|
48
|
-
{ "tag": "v7", "deleted_classes": ["FrictionDO"] }
|
|
67
|
+
{ "tag": "v7", "deleted_classes": ["FrictionDO"] },
|
|
68
|
+
{ "tag": "v8", "new_sqlite_classes": ["DeliveryDO"] }
|
|
49
69
|
]
|
|
50
70
|
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# Resident container image: the @cloudflare/sandbox base (Ubuntu 22.04; already
|
|
2
|
-
# ships git, curl, jq,
|
|
3
|
-
# a pool of unprivileged users.
|
|
2
|
+
# ships git, curl, jq, npm, bun, cloudflared and a Node 22 this image replaces
|
|
3
|
+
# with Node 24 below) plus GitHub tooling and a pool of unprivileged users.
|
|
4
|
+
|
|
5
|
+
# The Node this image ships (copied in below), at the exact tag every image in
|
|
6
|
+
# this repository shares — its major is .nvmrc's, which is what CI runs and
|
|
7
|
+
# the install script requires. `--platform`: the cloudflare/sandbox base below
|
|
8
|
+
# publishes linux/amd64 only, so the stage the binary is copied from must be
|
|
9
|
+
# amd64 too — a bare FROM on an arm64 host resolves it to arm64 and the copied
|
|
10
|
+
# binary cannot run in the base (check:image on Apple silicon failed exactly
|
|
11
|
+
# so). src/deploy/imageNode.test.ts holds the tag, its major and the platform.
|
|
12
|
+
FROM --platform=linux/amd64 docker.io/library/node:24.21.0-slim AS node
|
|
13
|
+
|
|
4
14
|
# The tag MUST match the @cloudflare/sandbox version in package.json exactly
|
|
5
15
|
# (no 'latest' tag exists on the @next line) — bump both together.
|
|
6
16
|
FROM docker.io/cloudflare/sandbox:0.13.0-next.751.1
|
|
@@ -32,7 +42,29 @@ ENV CI=1 \
|
|
|
32
42
|
UV_THREADPOOL_SIZE=4 \
|
|
33
43
|
NODE_OPTIONS=--max-old-space-size=1536
|
|
34
44
|
|
|
35
|
-
#
|
|
45
|
+
# Node 24, not the base's. The 0.13.0-next base ships Node 22.23.2 like the
|
|
46
|
+
# 0.12.9 one (copied from node:22-slim: the binary at /usr/local/bin/node, npm
|
|
47
|
+
# and corepack under /usr/local/lib/node_modules, npm/npx symlinked into it),
|
|
48
|
+
# while this repository builds and tests on Node 24 (.nvmrc, CI) and its
|
|
49
|
+
# published CLI requires `>=24` — so a repository validated here read a
|
|
50
|
+
# different `verify` than CI did. Node comes from the official image at an
|
|
51
|
+
# exact tag, in the `node` stage above, the same way the sandbox image does it
|
|
52
|
+
# (deploy/cloudflare-sandbox/Dockerfile): the base's npm tree removed first,
|
|
53
|
+
# then the binary, npm's tree and the headers at the base's own paths, so its
|
|
54
|
+
# npm/npx symlinks keep resolving; the container server is a bun-compiled
|
|
55
|
+
# binary and does not run on this Node. The grep fails the BUILD if a bump
|
|
56
|
+
# lands another version; src/deploy/imageNode.test.ts holds it equal to the
|
|
57
|
+
# stage's tag. This layer sits before the package managers below so they are
|
|
58
|
+
# installed by this Node's npm, into this tree.
|
|
59
|
+
RUN rm -rf /usr/local/lib/node_modules /usr/local/include/node
|
|
60
|
+
COPY --from=node /usr/local/bin/node /usr/local/bin/node
|
|
61
|
+
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
|
|
62
|
+
COPY --from=node /usr/local/include/node /usr/local/include/node
|
|
63
|
+
RUN node --version | grep -qx 'v24.21.0' \
|
|
64
|
+
&& npm --version >/dev/null \
|
|
65
|
+
&& npx --version >/dev/null
|
|
66
|
+
|
|
67
|
+
# pnpm + yarn: the base ships npm + bun but neither pnpm nor yarn, so a
|
|
36
68
|
# repo whose onboard table is detected as pnpm or yarn (resident-repos item 52 —
|
|
37
69
|
# every manager `detectCommands` can emit must exist here, or the table fails
|
|
38
70
|
# deterministically at provision) couldn't be provisioned (install → build).
|
|
@@ -74,6 +106,53 @@ RUN apt-get update \
|
|
|
74
106
|
&& rm -rf /var/lib/apt/lists/* \
|
|
75
107
|
&& command -v unsquashfs >/dev/null
|
|
76
108
|
|
|
109
|
+
# The toolchain for what a run BUILDS and LOOKS AT — the same layer as the
|
|
110
|
+
# sandbox image's (deploy/cloudflare-sandbox/Dockerfile);
|
|
111
|
+
# src/deploy/imageToolchain.test.ts holds the two to one shape.
|
|
112
|
+
# - python3 + make + g++: node-gyp's needs. A cold `npm install` in a repo
|
|
113
|
+
# with a native module (node-pty) rebuilds it from source, and died on
|
|
114
|
+
# "no Python". Just the three — no build-essential, no recommends.
|
|
115
|
+
# - ffmpeg (Ubuntu's, with libx264): an agent pulls frames out of a video
|
|
116
|
+
# to look at it (`ffmpeg -i in.mp4 -vf fps=1 frame_%03d.png`) and encodes
|
|
117
|
+
# video from frames or a screen recording.
|
|
118
|
+
# - a headless Chromium through Playwright at an EXACT pin
|
|
119
|
+
# (src/deploy/imagePins.test.ts): screenshots, PDFs and `recordVideo`.
|
|
120
|
+
# Ubuntu 22.04's apt `chromium` is a snap stub that does not run in a
|
|
121
|
+
# container, so the browser is Playwright's own build — the headless shell
|
|
122
|
+
# only (`--only-shell`: there is no display), with the system libraries it
|
|
123
|
+
# needs (`--with-deps`) and the fonts pages render text with.
|
|
124
|
+
# PLAYWRIGHT_BROWSERS_PATH is set BEFORE the install so the browser lands
|
|
125
|
+
# under /opt, not root's home, and the tree is opened to every user
|
|
126
|
+
# (a+rX): every agent command here runs as an unprivileged workerN (the
|
|
127
|
+
# pool below), through `su` without `-`, which keeps this environment.
|
|
128
|
+
# NODE_PATH makes `require('playwright')` resolve from any working
|
|
129
|
+
# directory. Playwright launches Chromium with --no-sandbox
|
|
130
|
+
# (`chromiumSandbox: false`, its default) — what a process without user
|
|
131
|
+
# namespaces needs, root or not.
|
|
132
|
+
# Every claim is PROVEN by the layer itself, so the BUILD fails, not a run:
|
|
133
|
+
# the compilers answer --version, ffmpeg encodes a one-second testsrc clip to
|
|
134
|
+
# h264 and decodes a non-empty frame back out of it, the playwright CLI is on
|
|
135
|
+
# PATH at the pin, and a real headless screenshot of a page lands as a
|
|
136
|
+
# non-empty png — here as root; again as worker1 once the pool exists, below.
|
|
137
|
+
# The proof artefacts, the apt lists, apt's .deb archive (this base has no
|
|
138
|
+
# docker-clean hook: 400 MB of archives stayed behind without the `clean`)
|
|
139
|
+
# and npm's cache go in the same layer.
|
|
140
|
+
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright \
|
|
141
|
+
NODE_PATH=/usr/local/lib/node_modules
|
|
142
|
+
RUN apt-get update \
|
|
143
|
+
&& apt-get install -y --no-install-recommends python3 make g++ ffmpeg fonts-liberation fonts-noto-color-emoji \
|
|
144
|
+
&& npm install -g playwright@1.63.0 \
|
|
145
|
+
&& playwright install --with-deps --only-shell chromium \
|
|
146
|
+
&& chmod -R a+rX /opt/ms-playwright \
|
|
147
|
+
&& apt-get clean && rm -rf /var/lib/apt/lists/* && npm cache clean --force \
|
|
148
|
+
&& python3 --version && g++ --version && make --version \
|
|
149
|
+
&& ffmpeg -version \
|
|
150
|
+
&& ffmpeg -hide_banner -loglevel error -f lavfi -i testsrc=duration=1:size=320x240:rate=10 -c:v libx264 -pix_fmt yuv420p /tmp/proof.mp4 \
|
|
151
|
+
&& ffmpeg -hide_banner -loglevel error -i /tmp/proof.mp4 -vf fps=1 /tmp/proof_%03d.png && test -s /tmp/proof_001.png \
|
|
152
|
+
&& playwright --version | grep -qx 'Version 1.63.0' \
|
|
153
|
+
&& playwright screenshot --viewport-size=640,480 'data:text/html,<h1>ok</h1>' /tmp/ok.png && test -s /tmp/ok.png \
|
|
154
|
+
&& rm -f /tmp/proof.mp4 /tmp/proof_*.png /tmp/ok.png
|
|
155
|
+
|
|
77
156
|
# Agent commands never run as root (repo code runs unprivileged, one user per
|
|
78
157
|
# thread — docs/reference/specs/resident-repos.md). The SDK's exec has no user/uid option
|
|
79
158
|
# (verified against 0.13.0-next.751.1), so the container server itself runs as
|
|
@@ -89,3 +168,10 @@ RUN set -eux; \
|
|
|
89
168
|
for i in $(seq 1 17); do useradd -m -u "$((2000 + i))" -s /bin/bash "worker$i"; chmod 700 "/home/worker$i"; done; \
|
|
90
169
|
if dpkg -s sudo >/dev/null 2>&1; then apt-get remove -y --purge sudo; fi; \
|
|
91
170
|
passwd -l root
|
|
171
|
+
|
|
172
|
+
# The browser proof once more, as worker1 — exactly how a thread's commands
|
|
173
|
+
# run (`su -s /bin/bash workerN -c …`, root's environment kept). The layer
|
|
174
|
+
# above proved it as root; this one proves an unprivileged user can read the
|
|
175
|
+
# browser tree under /opt and launch it, so the BUILD fails if a chmod or the
|
|
176
|
+
# path ever regresses, not a run.
|
|
177
|
+
RUN su -s /bin/bash worker1 -c "playwright screenshot --viewport-size=640,480 'data:text/html,<h1>ok</h1>' /tmp/ok.png && test -s /tmp/ok.png && rm -f /tmp/ok.png"
|