@coreplane/switchboard 1.239.0 → 1.241.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/assets/config/config.example.yaml +32 -7
  2. package/dist/assets/deploy/cloudflare/worker.ts +77 -18
  3. package/dist/assets/deploy/cloudflare-memory/worker.ts +115 -1
  4. package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +7 -1
  5. package/dist/assets/deploy/cloudflare-resident/worker.ts +224 -70
  6. package/dist/assets/deploy/secrets.manifest.json +7 -1
  7. package/dist/assets/package-lock.json +207 -3
  8. package/dist/assets/package.json +2 -1
  9. package/dist/assets/source.json +3 -3
  10. package/dist/assets/src/agents/registry.ts +27 -8
  11. package/dist/assets/src/core/authz/actor.ts +38 -2
  12. package/dist/assets/src/core/authz/grants.ts +20 -9
  13. package/dist/assets/src/core/authz/policy.ts +6 -0
  14. package/dist/assets/src/core/coordinator/contract.ts +49 -1
  15. package/dist/assets/src/core/coordinator/driver.ts +30 -1
  16. package/dist/assets/src/core/coordinator/instancesRoute.ts +72 -0
  17. package/dist/assets/src/core/costs.ts +1119 -0
  18. package/dist/assets/src/core/costsSnapshotStore.ts +209 -0
  19. package/dist/assets/src/core/delivery.ts +4 -11
  20. package/dist/assets/src/core/deliverySnapshotStore.ts +12 -28
  21. package/dist/assets/src/core/ingressTokens.ts +27 -13
  22. package/dist/assets/src/core/runLedger/types.ts +3 -0
  23. package/dist/assets/src/core/runRecord.ts +10 -3
  24. package/dist/assets/src/core/schedules.ts +2 -1
  25. package/dist/assets/src/core/ship/coordinator.ts +92 -7
  26. package/dist/assets/src/core/snapshotAge.ts +30 -0
  27. package/dist/assets/src/core/stateWorkerRef.ts +41 -0
  28. package/dist/assets/src/core/trace/attrs.ts +1 -1
  29. package/dist/assets/src/execution/bindingPurge.ts +2 -2
  30. package/dist/assets/src/execution/residentAutoRebuild.ts +73 -0
  31. package/dist/assets/src/execution/residentRefresh.ts +25 -3
  32. package/dist/assets/src/execution/restoreWaiters.ts +61 -0
  33. package/dist/assets/src/mcp/registry.ts +7 -0
  34. package/dist/assets/web/dist/.vite/manifest.json +509 -427
  35. package/dist/assets/web/dist/assets/AppShell-DEy5jRuS.js +1 -0
  36. package/dist/assets/web/dist/assets/CostsPage-BuHD-Bv7.js +2 -0
  37. package/dist/assets/web/dist/assets/DeliveryPage-BGpPr5xr.js +1 -0
  38. package/dist/assets/web/dist/assets/HomePage-dKBvWh-E.js +2 -0
  39. package/dist/assets/web/dist/assets/NotFoundPage-DtmQM2gD.js +1 -0
  40. package/dist/assets/web/dist/assets/PendingTurnRow-CrEAWx5b.js +1 -0
  41. package/dist/assets/web/dist/assets/{ResidentDetailPage-CrfCd8fx.js → ResidentDetailPage-C-48fxat.js} +1 -1
  42. package/dist/assets/web/dist/assets/ResidentsIndexPage-CBBIjPOa.js +1 -0
  43. package/dist/assets/web/dist/assets/RunFoldRow-B6vxjcBI.js +1 -0
  44. package/dist/assets/web/dist/assets/RunRoutePage-CQjd3Cz-.js +6 -0
  45. package/dist/assets/web/dist/assets/RunsIndexPage-Cy9G6D0p.js +1 -0
  46. package/dist/assets/web/dist/assets/{RunsTabs-XpCqeWu4.js → RunsTabs-CtNGNnhN.js} +1 -1
  47. package/dist/assets/web/dist/assets/ScheduledPage-Ci1Ygqnc.js +1 -0
  48. package/dist/assets/web/dist/assets/SettingsPage-DxmJvAmT.js +1 -0
  49. package/dist/assets/web/dist/assets/SlackMark-VDNs7Vjh.js +1 -0
  50. package/dist/assets/web/dist/assets/{StatusDot-DkXALvKf.js → StatusDot-DPWE1JmO.js} +1 -1
  51. package/dist/assets/web/dist/assets/{Tooltip-DtLIyi5P.js → Tooltip-DKhSRH9t.js} +1 -1
  52. package/dist/assets/web/dist/assets/UnitRoutePage-DEN5bSQf.js +1 -0
  53. package/dist/assets/web/dist/assets/{angular-html-DYtV9uIN.js → angular-html-DgSK1qvr.js} +1 -1
  54. package/dist/assets/web/dist/assets/{angular-ts-DF2P25Zp.js → angular-ts-D3gNdiSG.js} +1 -1
  55. package/dist/assets/web/dist/assets/{apl-Ff96wxko.js → apl-CkHCYM8I.js} +1 -1
  56. package/dist/assets/web/dist/assets/{astro-LbBnZna-.js → astro-DLm45axt.js} +1 -1
  57. package/dist/assets/web/dist/assets/{blade-DltuxeBW.js → blade-BKa-VE-c.js} +1 -1
  58. package/dist/assets/web/dist/assets/{c-DBg7yuPx.js → c-C8NCNWai.js} +1 -1
  59. package/dist/assets/web/dist/assets/{chapel-C83hUAds.js → chapel-DqDQ7IKH.js} +1 -1
  60. package/dist/assets/web/dist/assets/{cobol-DB0yV_2D.js → cobol-j4vCD6AJ.js} +1 -1
  61. package/dist/assets/web/dist/assets/{coffee-Bpih6Kzl.js → coffee-C6oDVH-0.js} +1 -1
  62. package/dist/assets/web/dist/assets/{cpp-CU00ZeRn.js → cpp-Bd3A1Rtc.js} +1 -1
  63. package/dist/assets/web/dist/assets/{crystal-f8ImJkux.js → crystal-CUOgOw0d.js} +1 -1
  64. package/dist/assets/web/dist/assets/{css-4VrC4ISo.js → css-RYljyv7G.js} +1 -1
  65. package/dist/assets/web/dist/assets/delivery-CTdOqpVK.js +1 -0
  66. package/dist/assets/web/dist/assets/{dist-DtA7JiH5.js → dist-BnJXKHCY.js} +2 -2
  67. package/dist/assets/web/dist/assets/durationTone-DXG-3R7_.js +1 -0
  68. package/dist/assets/web/dist/assets/{edge-BwFDzfS_.js → edge-BJhNdQDc.js} +1 -1
  69. package/dist/assets/web/dist/assets/{elixir-wwIYIpJm.js → elixir-g1AWOAfT.js} +1 -1
  70. package/dist/assets/web/dist/assets/{elm-DyDuM3v3.js → elm-CER5e4Dz.js} +1 -1
  71. package/dist/assets/web/dist/assets/{erb-DFJBEVrh.js → erb-D5aOmMQf.js} +1 -1
  72. package/dist/assets/web/dist/assets/eventSource-tRprhqm7.js +1 -0
  73. package/dist/assets/web/dist/assets/favicon-D1hkyC5j.js +1 -0
  74. package/dist/assets/web/dist/assets/{git-rebase-CuDv-44X.js → git-rebase-D6rfV8jp.js} +1 -1
  75. package/dist/assets/web/dist/assets/{glimmer-js-ZFnrm4J3.js → glimmer-js-BvDH3-mC.js} +1 -1
  76. package/dist/assets/web/dist/assets/{glimmer-ts-XY4Yqshp.js → glimmer-ts-DChtNs9V.js} +1 -1
  77. package/dist/assets/web/dist/assets/{glsl-xnR8efx0.js → glsl-BHlsWrxf.js} +1 -1
  78. package/dist/assets/web/dist/assets/{graphql-D-gO5tnP.js → graphql-BsaBHb53.js} +1 -1
  79. package/dist/assets/web/dist/assets/{hack-CSxbW4ax.js → hack-0lbDosCd.js} +1 -1
  80. package/dist/assets/web/dist/assets/{haml-D4ar42QT.js → haml-BKtco2vr.js} +1 -1
  81. package/dist/assets/web/dist/assets/{handlebars-CMLbDE7o.js → handlebars-BE_cj04z.js} +1 -1
  82. package/dist/assets/web/dist/assets/{html-CVekU4Fs.js → html-BNcr8EVd.js} +1 -1
  83. package/dist/assets/web/dist/assets/{html-derivative-St3HHw_N.js → html-derivative-BsJt2Kei.js} +1 -1
  84. package/dist/assets/web/dist/assets/{http-Mon0DWHS.js → http-BR5P8ER_.js} +1 -1
  85. package/dist/assets/web/dist/assets/{hurl-Cbl5IvBs.js → hurl-CtHalpWS.js} +1 -1
  86. package/dist/assets/web/dist/assets/{localIso-CNA-bhS-.js → indexFormat-B-pd4r7Y.js} +1 -1
  87. package/dist/assets/web/dist/assets/{indexRow-FLJu5mR9.js → indexRow-BD1VT8o8.js} +1 -1
  88. package/dist/assets/web/dist/assets/{java-Bprck1l-.js → java-BXVdhN11.js} +1 -1
  89. package/dist/assets/web/dist/assets/{javascript-C73G1-UR.js → javascript-CyoShgNQ.js} +1 -1
  90. package/dist/assets/web/dist/assets/{jinja-C4DuRxYJ.js → jinja-BQYZzHex.js} +1 -1
  91. package/dist/assets/web/dist/assets/{jison-B2U03GEC.js → jison-DqrUa1Eq.js} +1 -1
  92. package/dist/assets/web/dist/assets/{json-CvTZUDzx.js → json-CQiTF9Jj.js} +1 -1
  93. package/dist/assets/web/dist/assets/{jsx-DMWKCzwo.js → jsx-Y16MkCYQ.js} +1 -1
  94. package/dist/assets/web/dist/assets/{julia-Czn7bV_3.js → julia-zn16YONe.js} +1 -1
  95. package/dist/assets/web/dist/assets/{just-BBxpNfvC.js → just-DcVeN_MN.js} +1 -1
  96. package/dist/assets/web/dist/assets/{latex-Bw9yg5mr.js → latex-BNNoF-WP.js} +1 -1
  97. package/dist/assets/web/dist/assets/{liquid-BRCjaydE.js → liquid-DAi6qHzn.js} +1 -1
  98. package/dist/assets/web/dist/assets/localIso-L06jV29p.js +1 -0
  99. package/dist/assets/web/dist/assets/{lua-DUu2KIC0.js → lua-BswS0axw.js} +1 -1
  100. package/dist/assets/web/dist/assets/main-B3is4sk6.js +28 -0
  101. package/dist/assets/web/dist/assets/main-DP_zSemY.css +1 -0
  102. package/dist/assets/web/dist/assets/{marko-CZJZIlSQ.js → marko-C-4hkmQZ.js} +1 -1
  103. package/dist/assets/web/dist/assets/{mdc-BmClq6fd.js → mdc-0ZaHUkS7.js} +1 -1
  104. package/dist/assets/web/dist/assets/{nginx-Bmt3xSEm.js → nginx-DBDg5tOR.js} +1 -1
  105. package/dist/assets/web/dist/assets/{nim-Da-i0utK.js → nim-BVMz49Yw.js} +1 -1
  106. package/dist/assets/web/dist/assets/{org-BvAre2qP.js → org-1hO_K6ya.js} +1 -1
  107. package/dist/assets/web/dist/assets/{perl-v9WwFFtB.js → perl-CVRQLvVk.js} +1 -1
  108. package/dist/assets/web/dist/assets/{php-DTU4Zyze.js → php-K9nCrB9n.js} +1 -1
  109. package/dist/assets/web/dist/assets/{pug-DhTwEqTO.js → pug-CTxOcmO3.js} +1 -1
  110. package/dist/assets/web/dist/assets/{qml-BUD9AC_c.js → qml-CmCkfG5q.js} +1 -1
  111. package/dist/assets/web/dist/assets/{r-CAnu56n7.js → r-pP47Xn1X.js} +1 -1
  112. package/dist/assets/web/dist/assets/{razor-Dn0P4gAq.js → razor-BpW6r3tC.js} +1 -1
  113. package/dist/assets/web/dist/assets/{regexp-CZyBRaVW.js → regexp-CINgdY4N.js} +1 -1
  114. package/dist/assets/web/dist/assets/{rst-Ckyd5UsY.js → rst-BZIUEq2Q.js} +1 -1
  115. package/dist/assets/web/dist/assets/{ruby-BBQ62fsx.js → ruby-nzAOxz6r.js} +1 -1
  116. package/dist/assets/web/dist/assets/{sas-CW35hMxG.js → sas-QIS1bFth.js} +1 -1
  117. package/dist/assets/web/dist/assets/{scss-CpV8pF4J.js → scss-BuhBBVNt.js} +1 -1
  118. package/dist/assets/web/dist/assets/settingsApi-priOPiwu.js +1 -0
  119. package/dist/assets/web/dist/assets/{shellscript-CRJvsmE1.js → shellscript-CQc1vXbk.js} +1 -1
  120. package/dist/assets/web/dist/assets/{shellsession-CyriUZxf.js → shellsession-CoubCAUv.js} +1 -1
  121. package/dist/assets/web/dist/assets/{soy-D5QzYUeg.js → soy-CPRzlder.js} +1 -1
  122. package/dist/assets/web/dist/assets/{sql-CUBf_Mse.js → sql-W9krb8-9.js} +1 -1
  123. package/dist/assets/web/dist/assets/sseReplay-yji8a1wM.js +9 -0
  124. package/dist/assets/web/dist/assets/{stata-B-4n4ITz.js → stata-CuISJEC0.js} +1 -1
  125. package/dist/assets/web/dist/assets/{surrealql-RuWZBLtr.js → surrealql-CSet7584.js} +1 -1
  126. package/dist/assets/web/dist/assets/{svelte-CO5YzCsK.js → svelte-ChXTSYwK.js} +1 -1
  127. package/dist/assets/web/dist/assets/{templ-DbkWVaBF.js → templ-Bm55v62k.js} +1 -1
  128. package/dist/assets/web/dist/assets/{tex-IuxoEcId.js → tex-CICMX8Gj.js} +1 -1
  129. package/dist/assets/web/dist/assets/{ts-tags-g6dGQPjd.js → ts-tags-CA1UzWyB.js} +1 -1
  130. package/dist/assets/web/dist/assets/{tsx-WnXwiyH2.js → tsx-Dy04HNbv.js} +1 -1
  131. package/dist/assets/web/dist/assets/{twig-Lx1G7PnY.js → twig-BBHsVnVD.js} +1 -1
  132. package/dist/assets/web/dist/assets/{typescript-BQd7biLF.js → typescript-Dvc-wVBT.js} +1 -1
  133. package/dist/assets/web/dist/assets/{typst-D2lXPHK7.js → typst-Dvlqx3_q.js} +1 -1
  134. package/dist/assets/web/dist/assets/{vue-html-DZPaRte4.js → vue-html-D4YxT3An.js} +1 -1
  135. package/dist/assets/web/dist/assets/{vue-vine-DbmZCmu-.js → vue-vine-0YE7uQlH.js} +1 -1
  136. package/dist/assets/web/dist/assets/{vue-4wVQD9y9.js → vue-zWi1MNU2.js} +1 -1
  137. package/dist/assets/web/dist/assets/{xml-DV16szyo.js → xml-gm-iksZB.js} +1 -1
  138. package/dist/assets/web/dist/assets/{xsl-eo5-1sk0.js → xsl-D_W5BcrY.js} +1 -1
  139. package/dist/assets/web/dist/assets/{yaml-BXdX8HZf.js → yaml-CHmZ21wZ.js} +1 -1
  140. package/dist/cli.js +6014 -2362
  141. package/package.json +1 -1
  142. package/dist/assets/web/dist/assets/AppShell-MYHNxJks.js +0 -1
  143. package/dist/assets/web/dist/assets/CostsPage-DKk9OjB0.js +0 -2
  144. package/dist/assets/web/dist/assets/DeliveryPage-Kz6js49i.js +0 -1
  145. package/dist/assets/web/dist/assets/NotFoundPage-BXLrQQzy.js +0 -1
  146. package/dist/assets/web/dist/assets/ResidentsIndexPage-BdW0k2h3.js +0 -1
  147. package/dist/assets/web/dist/assets/RunFoldRow-CbTd9SiS.js +0 -9
  148. package/dist/assets/web/dist/assets/RunRoutePage-br0H-8Pz.js +0 -6
  149. package/dist/assets/web/dist/assets/RunsIndexPage-Bnzb15zH.js +0 -1
  150. package/dist/assets/web/dist/assets/ScheduledPage-NLhjzHAz.js +0 -1
  151. package/dist/assets/web/dist/assets/SettingsPage-D-i0uVw-.js +0 -1
  152. package/dist/assets/web/dist/assets/UnitRoutePage-DGXukEAH.js +0 -1
  153. package/dist/assets/web/dist/assets/durationTone-CYMux25J.js +0 -1
  154. package/dist/assets/web/dist/assets/favicon-CSt-qDvc.js +0 -1
  155. package/dist/assets/web/dist/assets/main-CFHF2RvQ.css +0 -1
  156. package/dist/assets/web/dist/assets/main-Dht0in4v.js +0 -28
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.239.0",
3
+ "version": "1.241.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "switchboard",
9
- "version": "1.239.0",
9
+ "version": "1.241.0",
10
10
  "license": "Apache-2.0",
11
11
  "workspaces": [
12
12
  "web",
@@ -34,6 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@earendil-works/pi-coding-agent": "0.85.1",
36
36
  "@eslint/js": "^10.0.1",
37
+ "@opencode/cli": "^2.0.3",
37
38
  "@opencode/protocol": "2.0.3",
38
39
  "@opencode/schema": "2.0.3",
39
40
  "@types/node": "^24.0.0",
@@ -6242,6 +6243,209 @@
6242
6243
  "dev": true,
6243
6244
  "license": "MIT"
6244
6245
  },
6246
+ "node_modules/@opencode/cli": {
6247
+ "version": "2.0.3",
6248
+ "resolved": "https://registry.npmjs.org/@opencode/cli/-/cli-2.0.3.tgz",
6249
+ "integrity": "sha512-FC4+2a5kwdKS2o/pIcGCscpSQB7PuhBObwGMfqnx/2XtoaZBMtSq/99nuEdeDNAFvy1qyK6mp2hwvoiMWsiI4w==",
6250
+ "cpu": [
6251
+ "arm64",
6252
+ "x64"
6253
+ ],
6254
+ "dev": true,
6255
+ "hasInstallScript": true,
6256
+ "license": "MIT",
6257
+ "os": [
6258
+ "darwin",
6259
+ "linux",
6260
+ "win32"
6261
+ ],
6262
+ "bin": {
6263
+ "opencode": "bin/opencode.exe",
6264
+ "opencode2": "bin/opencode.exe"
6265
+ },
6266
+ "optionalDependencies": {
6267
+ "@opencode/cli-darwin-arm64": "2.0.3",
6268
+ "@opencode/cli-darwin-x64": "2.0.3",
6269
+ "@opencode/cli-darwin-x64-baseline": "2.0.3",
6270
+ "@opencode/cli-linux-arm64": "2.0.3",
6271
+ "@opencode/cli-linux-arm64-musl": "2.0.3",
6272
+ "@opencode/cli-linux-x64": "2.0.3",
6273
+ "@opencode/cli-linux-x64-baseline": "2.0.3",
6274
+ "@opencode/cli-linux-x64-baseline-musl": "2.0.3",
6275
+ "@opencode/cli-linux-x64-musl": "2.0.3",
6276
+ "@opencode/cli-windows-arm64": "2.0.3",
6277
+ "@opencode/cli-windows-x64": "2.0.3",
6278
+ "@opencode/cli-windows-x64-baseline": "2.0.3"
6279
+ }
6280
+ },
6281
+ "node_modules/@opencode/cli-darwin-arm64": {
6282
+ "version": "2.0.3",
6283
+ "resolved": "https://registry.npmjs.org/@opencode/cli-darwin-arm64/-/cli-darwin-arm64-2.0.3.tgz",
6284
+ "integrity": "sha512-2JzNaHmMHUCk2uLXo2kCKCs81KOzErt7V7ypFFp5hUCIXRPjt9D7C5CKsz2GKWfpHRivUKILKrfUS9MpYzfq6A==",
6285
+ "cpu": [
6286
+ "arm64"
6287
+ ],
6288
+ "dev": true,
6289
+ "license": "MIT",
6290
+ "optional": true,
6291
+ "os": [
6292
+ "darwin"
6293
+ ]
6294
+ },
6295
+ "node_modules/@opencode/cli-darwin-x64": {
6296
+ "version": "2.0.3",
6297
+ "resolved": "https://registry.npmjs.org/@opencode/cli-darwin-x64/-/cli-darwin-x64-2.0.3.tgz",
6298
+ "integrity": "sha512-dMAH/N5KD10Wlct2BbCa8tEV6SiPgBg1kF9Ud7N7ZUctMEeGoz3fqZTrcGrQz7yoAkyyHhGgq+gmxqobwrAhhg==",
6299
+ "cpu": [
6300
+ "x64"
6301
+ ],
6302
+ "dev": true,
6303
+ "license": "MIT",
6304
+ "optional": true,
6305
+ "os": [
6306
+ "darwin"
6307
+ ]
6308
+ },
6309
+ "node_modules/@opencode/cli-darwin-x64-baseline": {
6310
+ "version": "2.0.3",
6311
+ "resolved": "https://registry.npmjs.org/@opencode/cli-darwin-x64-baseline/-/cli-darwin-x64-baseline-2.0.3.tgz",
6312
+ "integrity": "sha512-sm9ZjG2KOk/v4ht6WHAo+pAeOfxnnImwT5rFWNIn15fVlXncbWH2QmKWDC3Nf/h7vpgeghZ1UBbBmo8fP3lBCA==",
6313
+ "cpu": [
6314
+ "x64"
6315
+ ],
6316
+ "dev": true,
6317
+ "license": "MIT",
6318
+ "optional": true,
6319
+ "os": [
6320
+ "darwin"
6321
+ ]
6322
+ },
6323
+ "node_modules/@opencode/cli-linux-arm64": {
6324
+ "version": "2.0.3",
6325
+ "resolved": "https://registry.npmjs.org/@opencode/cli-linux-arm64/-/cli-linux-arm64-2.0.3.tgz",
6326
+ "integrity": "sha512-q3QhCIW5CxkmJjzKI6GXJ89M0OF9lOsi1e3e4YTvQL6Htv0S7eHyZ+3Wg0UYQaoYqegAf3vBoYiPR3Z9ieIVrQ==",
6327
+ "cpu": [
6328
+ "arm64"
6329
+ ],
6330
+ "dev": true,
6331
+ "license": "MIT",
6332
+ "optional": true,
6333
+ "os": [
6334
+ "linux"
6335
+ ]
6336
+ },
6337
+ "node_modules/@opencode/cli-linux-arm64-musl": {
6338
+ "version": "2.0.3",
6339
+ "resolved": "https://registry.npmjs.org/@opencode/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.3.tgz",
6340
+ "integrity": "sha512-cif0oXReK/lVZ9kFplNc3XTNM3uuAKLIgcfcEIVMbjTivLzdHe0O5Dp8y7rakJOdXqQ5yM1k6oP8kP2rfpitxg==",
6341
+ "cpu": [
6342
+ "arm64"
6343
+ ],
6344
+ "dev": true,
6345
+ "license": "MIT",
6346
+ "optional": true,
6347
+ "os": [
6348
+ "linux"
6349
+ ]
6350
+ },
6351
+ "node_modules/@opencode/cli-linux-x64": {
6352
+ "version": "2.0.3",
6353
+ "resolved": "https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-2.0.3.tgz",
6354
+ "integrity": "sha512-H8m5m4g1Z/zkx1O8+pos7euLYpY/zgc5NTdUbZZ7Dh8xQ35qoHCnJjt9H8Y1hnpm4ClNKDVHmEISw/qPiskX5A==",
6355
+ "cpu": [
6356
+ "x64"
6357
+ ],
6358
+ "dev": true,
6359
+ "license": "MIT",
6360
+ "optional": true,
6361
+ "os": [
6362
+ "linux"
6363
+ ]
6364
+ },
6365
+ "node_modules/@opencode/cli-linux-x64-baseline": {
6366
+ "version": "2.0.3",
6367
+ "resolved": "https://registry.npmjs.org/@opencode/cli-linux-x64-baseline/-/cli-linux-x64-baseline-2.0.3.tgz",
6368
+ "integrity": "sha512-14CFor+PFx7tPAFOB3uEcg2EOQZNB1gK4uit4p8gfN30FK7kwNHBjs/m79QGICPMjwZrCOVdZ3ZiI+AgbjanjA==",
6369
+ "cpu": [
6370
+ "x64"
6371
+ ],
6372
+ "dev": true,
6373
+ "license": "MIT",
6374
+ "optional": true,
6375
+ "os": [
6376
+ "linux"
6377
+ ]
6378
+ },
6379
+ "node_modules/@opencode/cli-linux-x64-baseline-musl": {
6380
+ "version": "2.0.3",
6381
+ "resolved": "https://registry.npmjs.org/@opencode/cli-linux-x64-baseline-musl/-/cli-linux-x64-baseline-musl-2.0.3.tgz",
6382
+ "integrity": "sha512-AYMNzjwPU63FjqjKLEm61s2tBL7+yipt/lzn+2eMpPHYGggV2urJH9TEME2+mReENeYFfOSItPR7gWLX1p2g5A==",
6383
+ "cpu": [
6384
+ "x64"
6385
+ ],
6386
+ "dev": true,
6387
+ "license": "MIT",
6388
+ "optional": true,
6389
+ "os": [
6390
+ "linux"
6391
+ ]
6392
+ },
6393
+ "node_modules/@opencode/cli-linux-x64-musl": {
6394
+ "version": "2.0.3",
6395
+ "resolved": "https://registry.npmjs.org/@opencode/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.3.tgz",
6396
+ "integrity": "sha512-YDZCk0MddTJ8lDwlEmE5bVWF5R13nDpLEQPcCA5IY2njB/y2g6UaAiIVbJE1rVmFd7/Z7JmyB2X3z60uk3C/dA==",
6397
+ "cpu": [
6398
+ "x64"
6399
+ ],
6400
+ "dev": true,
6401
+ "license": "MIT",
6402
+ "optional": true,
6403
+ "os": [
6404
+ "linux"
6405
+ ]
6406
+ },
6407
+ "node_modules/@opencode/cli-windows-arm64": {
6408
+ "version": "2.0.3",
6409
+ "resolved": "https://registry.npmjs.org/@opencode/cli-windows-arm64/-/cli-windows-arm64-2.0.3.tgz",
6410
+ "integrity": "sha512-WQuojS+6O6YNlRsal9kbePiEEv4hZHVL7l7ILRnlyZSLx7M2ynTOUahtGzC42AbdH9sdIa4OGQDmDwcbfCqHuA==",
6411
+ "cpu": [
6412
+ "arm64"
6413
+ ],
6414
+ "dev": true,
6415
+ "license": "MIT",
6416
+ "optional": true,
6417
+ "os": [
6418
+ "win32"
6419
+ ]
6420
+ },
6421
+ "node_modules/@opencode/cli-windows-x64": {
6422
+ "version": "2.0.3",
6423
+ "resolved": "https://registry.npmjs.org/@opencode/cli-windows-x64/-/cli-windows-x64-2.0.3.tgz",
6424
+ "integrity": "sha512-4pCiryt3LZG4dCkhvDOiTttzBECE36HUKe5uwCSAu9Ny7Sh2H1iVw1XFKo3QYqh6XdkWa9Uyza0lqLB6SSAGTw==",
6425
+ "cpu": [
6426
+ "x64"
6427
+ ],
6428
+ "dev": true,
6429
+ "license": "MIT",
6430
+ "optional": true,
6431
+ "os": [
6432
+ "win32"
6433
+ ]
6434
+ },
6435
+ "node_modules/@opencode/cli-windows-x64-baseline": {
6436
+ "version": "2.0.3",
6437
+ "resolved": "https://registry.npmjs.org/@opencode/cli-windows-x64-baseline/-/cli-windows-x64-baseline-2.0.3.tgz",
6438
+ "integrity": "sha512-9R6QdvoEd6bxv+Izi2kwg+85Mc3lCoTRgAsTZpNadVaRgAcU8vzEjjgrzFM6gLr8j+65EgG7vtP1gCVhfyQRwg==",
6439
+ "cpu": [
6440
+ "x64"
6441
+ ],
6442
+ "dev": true,
6443
+ "license": "MIT",
6444
+ "optional": true,
6445
+ "os": [
6446
+ "win32"
6447
+ ]
6448
+ },
6245
6449
  "node_modules/@opencode/protocol": {
6246
6450
  "version": "2.0.3",
6247
6451
  "resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-2.0.3.tgz",
@@ -20241,7 +20445,7 @@
20241
20445
  },
20242
20446
  "packages/switchboard": {
20243
20447
  "name": "@coreplane/switchboard",
20244
- "version": "1.239.0",
20448
+ "version": "1.241.0",
20245
20449
  "license": "Apache-2.0",
20246
20450
  "dependencies": {
20247
20451
  "@earendil-works/pi-ai": "0.85.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.239.0",
3
+ "version": "1.241.0",
4
4
  "private": true,
5
5
  "description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
6
6
  "license": "Apache-2.0",
@@ -91,6 +91,7 @@
91
91
  "devDependencies": {
92
92
  "@earendil-works/pi-coding-agent": "0.85.1",
93
93
  "@eslint/js": "^10.0.1",
94
+ "@opencode/cli": "^2.0.3",
94
95
  "@opencode/protocol": "2.0.3",
95
96
  "@opencode/schema": "2.0.3",
96
97
  "@types/node": "^24.0.0",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.239.0",
3
- "commit": "f8a99fe783b535df45b729baf09bf69557962c3a",
4
- "builtAt": "2026-09-16T23:24:01.904Z"
2
+ "version": "1.241.0",
3
+ "commit": "645b2a6d20ac39178acdb7c412a6a72f6013771c",
4
+ "builtAt": "2026-09-17T01:34:41.433Z"
5
5
  }
@@ -133,6 +133,23 @@ export interface AgentDef {
133
133
  // and rendered against the head sha at render time, so a repush is a
134
134
  // re-render by Switchboard — the agent only resubmits when the CONTENT (line
135
135
  // numbers included) changed.
136
+ /** The status-card rule every tool-running preset carries (docs/reference/specs/run-visibility.md
137
+ * item 2). One sentence in one place: the card shows the command running right
138
+ * now beside the checklist, so the checklist's markers must be facts — ✱ from
139
+ * the item's first command, ✓ only once its result has been read — or the two
140
+ * contradict each other on the card. `examples` are the preset's own outcome
141
+ * phrasings; the rule itself never varies by preset. */
142
+ export function statusCardRule(examples = '"Implement the fix", "Run the test suite"'): string {
143
+ return (
144
+ "Maintain the user-facing status card with the update_status tool. Post your plan as a checklist (○ pending) as soon as you have it, " +
145
+ "then keep it truthful at every moment: the markers are facts, not intentions. Mark an item ✱ when you issue the first command that does it, " +
146
+ "and ✓ only after you have read the result that proves it happened — never in the same turn as the command, never because you intend to run it next, " +
147
+ "never for a reporting or posting step you have not done. The order is: mark the item ✱, run its commands, read the result, then mark it ✓ and the next item ✱. " +
148
+ "The card shows the command running right now beside your checklist, so a ✓ item whose command is still running reads as a lie. " +
149
+ `Items are short outcomes (${examples}), never commands. This is the only progress the user sees while you work.`
150
+ );
151
+ }
152
+
136
153
  const PR_DESCRIPTION_TEMPLATE = `PR description — submit it with the submit_pr_description tool for EVERY PR (this is the default, not something to wait to be asked for). Switchboard renders the GitHub body from the object you submit, so never author PR-body markdown yourself. Before submitting, judge your title with the ${PR_TITLE_GUARD} gate — \`npm run check:pr-title -- "<title>"\` — and submit only a title it accepts; the same gate refuses the PR in CI. Content contract per field (each renders as its own section): prose is unwrapped — no hard line breaks inside a paragraph. Always hyperlink the triggering issue/request. Never fabricate validation — state exactly what you ran and the real result. Keep each field concise, not padded.
137
154
  EVERY PR includes one that already exists when you push — opened by a person, by dependabot, or by an earlier run. After EVERY push to such a PR: read its current title and body (\`github_issue_get\` with the PR number works for pull requests; \`gh pr view\` where gh exists), judge them against the change as it now stands at the pushed head, and submit the object that describes the PR as it is NOW — carry forward what the existing body says that is still true (a dependency bump's release notes belong in whatWhy), add what you changed, and anchor the Tour at the new head. Switchboard replaces the PR's title and body with your rendering. A description that describes an earlier state of its branch is a bug; "it is someone else's PR" is never a reason to leave it.
138
155
  - **title**: the PR title — one line naming the change, specific enough to pick out of a PR list.
@@ -237,7 +254,7 @@ ${SHOW_FILES}
237
254
 
238
255
  ${NOTEPAD}
239
256
 
240
- Maintain the user-facing status card with the update_status tool: right after you decide your plan, post it as a checklist (○ pending items), then update it whenever an item starts (✱) or finishes (✓). Items are short outcomes ("Clone repo and read the diff", "Run the test suite"), never commands. Mark an item ✓ only after it has actually happened — never pre-mark reporting/posting steps. This is the only progress the user sees while you work.
257
+ ${statusCardRule('"Clone repo and read the diff", "Run the test suite"')}
241
258
 
242
259
  If the request doesn't name a repository and you can't infer it, ask for it instead of guessing.
243
260
  Report outcomes faithfully: if tests fail or a step was skipped, say so plainly.
@@ -280,7 +297,7 @@ ${SHOW_FILES}
280
297
 
281
298
  ${NOTEPAD}
282
299
 
283
- Maintain the user-facing status card with the update_status tool: right after you decide your plan, post it as a checklist (○ pending items), then update it whenever an item starts (✱) or finishes (✓). Items are short outcomes ("Implement the fix", "Run the test suite"), never commands. Mark an item ✓ only after it has actually happened — never pre-mark reporting/posting steps. This is the only progress the user sees while you work.
300
+ ${statusCardRule()}
284
301
 
285
302
  Report outcomes faithfully: if tests fail or a step was skipped, say so plainly.
286
303
  ${FENCED_CONTENT_RULE}
@@ -319,7 +336,7 @@ ${SHOW_FILES}
319
336
 
320
337
  ${NOTEPAD}
321
338
 
322
- Maintain the user-facing status card with the update_status tool: right after you decide your plan, post it as a checklist (○ pending items), then update it whenever an item starts (✱) or finishes (✓). Items are short outcomes ("Implement the fix", "Run the test suite"), never commands. Mark an item ✓ only after it has actually happened — never pre-mark reporting/posting steps. This is the only progress the user sees while you work.
339
+ ${statusCardRule()}
323
340
 
324
341
  Report outcomes faithfully: if tests fail or a step was skipped, say so plainly.
325
342
  ${FENCED_CONTENT_RULE}
@@ -384,7 +401,7 @@ ${REVIEW_VERDICT_INSTRUCTION}
384
401
 
385
402
  ${NOTEPAD}
386
403
 
387
- Maintain the user-facing status card with the update_status tool: post your plan as a checklist (○ pending), update as items start (✱) and finish (✓ — only after they actually happened; never pre-mark reporting steps). Items are short outcomes, never commands.
404
+ ${statusCardRule('"Gather the diff and the files", "Analyze the change", "Post the verdict"')}
388
405
 
389
406
  ${FENCED_CONTENT_RULE}
390
407
  Your final message is posted to Slack. Lead with a one-line verdict, then the findings.`;
@@ -417,7 +434,7 @@ ${REVIEW_VERDICT_INSTRUCTION}
417
434
 
418
435
  ${NOTEPAD}
419
436
 
420
- Maintain the user-facing status card with the update_status tool: post your plan as a checklist (○ pending), update as items start (✱) and finish (✓ — only after they actually happened; never pre-mark reporting steps). Items are short outcomes, never commands.
437
+ ${statusCardRule('"Gather the diff and the files", "Analyze the change", "Post the verdict"')}
421
438
 
422
439
  ${FENCED_CONTENT_RULE}
423
440
  Your final message is posted to Slack. Lead with a one-line verdict, then the findings.`;
@@ -451,7 +468,7 @@ ${REVIEW_VERDICT_INSTRUCTION}
451
468
 
452
469
  ${NOTEPAD}
453
470
 
454
- Maintain the user-facing status card with the update_status tool: post your plan as a checklist (○ pending), update as items start (✱) and finish (✓ — only after they actually happened; never pre-mark reporting steps). Items are short outcomes, never commands.
471
+ ${statusCardRule('"Gather the diff and the files", "Analyze the change", "Post the verdict"')}
455
472
 
456
473
  ${FENCED_CONTENT_RULE}
457
474
  Your final message is posted to Slack. Lead with a one-line verdict, then the findings.`;
@@ -469,7 +486,7 @@ How to work:
469
486
  2. Prefer primary sources; corroborate a surprising claim with a second source.
470
487
  3. Answer concisely and cite the URLs (or repo paths) you used. If sources conflict or you couldn't verify something, say so plainly. If web search is unconfigured, use web_fetch / the GitHub tools on what you have and say search was unavailable.
471
488
 
472
- Maintain the user-facing status card with the update_status tool: post a short checklist (○ pending) after you plan, and update items as they start (✱) and finish (✓ — only once they actually happened).
489
+ ${statusCardRule('"Search the sources", "Write the answer"')}
473
490
 
474
491
  ${FENCED_CONTENT_RULE}
475
492
  Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks). Your final message is posted to Slack — lead with the answer, then supporting detail and sources.`;
@@ -485,6 +502,8 @@ Answer directly and concisely. Use Slack-friendly formatting (no markdown header
485
502
 
486
503
  Your tools work without a workspace: the GitHub tools — \`github_repos\` (the org repositories you can reach), \`github_tree\` / \`github_file\` / \`github_search_code\` (browse, read, search their code and docs, private repos included), \`github_issue_list\` / \`github_issue_get\` (read issues), \`github_issue_create\` / \`github_issue_update\` / \`github_issue_comment\` / \`github_issue_delete\` (act on issues) — and \`web_fetch\` (read a public URL). Use them: when the user names a repo loosely ("the switchboard app"), resolve it with github_repos (or the thread) rather than asking; when asked about one of our repos, read it before answering. Report exactly what a tool did (issue number + URL) — never claim an action you did not perform, and never fabricate file contents, URLs, or command output.
487
504
 
505
+ ${statusCardRule('"Read the issue and its thread", "Post the comment"')} A one-step answer needs no checklist; post one when the request has steps the person would wait on.
506
+
488
507
  You cannot run commands, clone repositories, edit code, or review pull requests, and you cannot search the web. Other Switchboard agents can: for code changes or PRs tell the user to re-send with \`agent:coding\`; for a PR review, \`agent:review\`; for a web-research question, \`agent:research\` (e.g. "\`agent:coding fix the failing login test in acme/api\`", "\`agent:research compare X and Y\`"). Delete an issue only when the user explicitly asked to delete it (closing is an update).`;
489
508
 
490
509
  // The explore agent (docs/reference/specs/agent-explore.md): a long, read-only
@@ -512,7 +531,7 @@ READ-ONLY: NEVER open a pull request, and never commit or push — no branch, no
512
531
 
513
532
  You cannot attach or post files: your whole answer is text. Never say a file is attached or below — name its path in the workspace and describe it (what it shows, its size) instead; a person who needs the file itself asks \`agent:coding\`, which can attach.
514
533
 
515
- Maintain the user-facing status card with the update_status tool: post your plan as a checklist (○ pending) once you have it, and update items as they start (✱) and finish (✓ — only after they actually happened). Items are short outcomes ("Clone and install", "Time the full suite"), never commands.
534
+ ${statusCardRule('"Clone and install", "Time the full suite"')}
516
535
 
517
536
  ${NOTEPAD}
518
537
 
@@ -76,12 +76,35 @@ export function resolveActor(input: ActorInput, grantsFor: GrantsLookup): Actor
76
76
  return { kind: kindFor(input.surface), id, grants: grantsFor(id), ...(origin ? { origin } : {}) };
77
77
  }
78
78
 
79
+ /** The identity fields a bound credential's message carries (`IncomingMessage`). */
80
+ interface BoundFields {
81
+ /** The credential's actor id when the sender is the person it is bound to. */
82
+ authenticatedAs?: string;
83
+ userName?: string;
84
+ }
85
+
86
+ /** The actor id whose grants govern a chat message: the credential that
87
+ * authenticated it when it was bound to a person (`authenticatedAs`), else
88
+ * the sender. Every `canRunAgent` / `canUseRepo` / `canManageRepos` /
89
+ * `canEditChannelConfig` question in the dispatch path asks about THIS id,
90
+ * never `msg.userId`: naming the person on a run must not lend the run the
91
+ * person's grants (authorization.md item 15). A relayed message (`postedBy`)
92
+ * is out of scope here — its gates are item 14's. */
93
+ export function grantsSubject(msg: { userId: string; authenticatedAs?: string }): string {
94
+ return msg.authenticatedAs ?? msg.userId;
95
+ }
96
+
79
97
  const CHAT_SURFACES: Readonly<Record<string, ActorSurface>> = {
80
98
  slack: "slack",
81
99
  http: "http",
82
100
  mcp: "mcp",
83
101
  cli: "cli",
84
102
  schedule: "schedule",
103
+ // The web chat (record 0043): a browser session speaks as text, so its
104
+ // `access:<sub>` is a chat credential too — the browser actor, its baseline
105
+ // and its entry. A service token never reaches the chat (the dashboard gate
106
+ // serves it `/api/*` only), so `access:svc:` is no chat surface.
107
+ access: "access-browser",
85
108
  };
86
109
 
87
110
  /** A chat message's `userId` is already namespaced by its adapter (`slack:U…`,
@@ -89,11 +112,24 @@ const CHAT_SURFACES: Readonly<Record<string, ActorSurface>> = {
89
112
  * A namespace this module does not know stays a `user` with the id as given —
90
113
  * its grants are whatever config names for that id, never a guess. */
91
114
  export function resolveChatActor(
92
- msg: { userId: string; channelId: string; threadKey: string; postedBy?: string },
115
+ msg: { userId: string; channelId: string; threadKey: string; postedBy?: string } & BoundFields,
93
116
  grantsFor: GrantsLookup,
94
117
  ): Actor {
95
118
  const person = resolveNamespacedActor(msg.userId, msg, grantsFor);
96
- if (msg.postedBy === undefined) return person;
119
+ if (msg.postedBy === undefined) {
120
+ if (msg.authenticatedAs === undefined || msg.authenticatedAs === msg.userId) return person;
121
+ // A credential bound to a person (authorization.md item 15): the adapter
122
+ // proved the credential and config bound it to the person, so the actor IS
123
+ // the credential — its kind, its id, its grants, exactly as an unbound
124
+ // token's — and the person is its `self` and `asUser`, the same
125
+ // identity-not-authority link a dashboard session carries (record 0042).
126
+ const credential = resolveNamespacedActor(msg.authenticatedAs, msg, grantsFor);
127
+ return {
128
+ ...credential,
129
+ self: [credential.id, msg.userId],
130
+ asUser: { id: msg.userId, ...(msg.userName !== undefined ? { name: msg.userName } : {}) },
131
+ };
132
+ }
97
133
  // A request an app posted for a person (slack-channel.md item 13): the
98
134
  // message text named the person, and text is forgeable, so the person's
99
135
  // grants alone must never govern. The actor is the app, acting on the
@@ -62,10 +62,20 @@ export const CHAT_OPEN_ACTIONS: readonly string[] = [
62
62
  "mcp:write",
63
63
  ];
64
64
 
65
- /** What an Access browser session holds implicitly: every registered
66
- * group's read — never a write, never an exec. */
67
- export function browserReadActions(commandGroups: readonly string[]): Set<string> {
68
- return new Set(commandGroups.map((g) => `${g}:read`));
65
+ /** The two writes a browser session holds beside its reads (record 0043): the
66
+ * same personal writes every Slack user holds (`CHAT_OPEN_ACTIONS`), because
67
+ * the web chat makes a browser session a chat user — `memory remember` and
68
+ * `mcp add` for its own tier. The tier rows still decide the target: a shared
69
+ * tier needs the grant it always needed. */
70
+ export const BROWSER_WRITE_ACTIONS: readonly string[] = ["memory:write", "mcp:write"];
71
+
72
+ /** What an Access browser session holds implicitly: every registered group's
73
+ * read plus the two personal chat writes — never another write, never an exec.
74
+ * No groups known (a process that registered no commands) → nothing at all,
75
+ * fail-closed: the writes ride with the catalogue they belong to. */
76
+ export function browserActions(commandGroups: readonly string[]): Set<string> {
77
+ if (commandGroups.length === 0) return new Set();
78
+ return new Set([...commandGroups.map((g) => `${g}:read`), ...BROWSER_WRITE_ACTIONS]);
69
79
  }
70
80
 
71
81
  // ---- the native `grants` block ----------------------------------------------
@@ -243,8 +253,9 @@ export interface GrantsTable {
243
253
  surfaces: Map<string, Grants>;
244
254
  /** What every `slack:` user holds, listed or not: the open chat commands and `agent:run:<name>` for every unrestricted agent. */
245
255
  everyone: Grants;
246
- /** What every Access browser session (`access:<sub>`, never `access:svc:`) holds: each registered group's read. */
247
- browserReads: Grants;
256
+ /** What every Access browser session (`access:<sub>`, never `access:svc:`) holds:
257
+ * each registered group's read and the two personal chat writes. */
258
+ browser: Grants;
248
259
  restrict: Restriction;
249
260
  }
250
261
 
@@ -253,9 +264,9 @@ export interface GrantsTable {
253
264
  * `<group>:read`. Every other namespace (`schedule:`, `access:svc:`, `http:`,
254
265
  * `mcp:`) is a credential or a job that holds exactly what names it — an
255
266
  * unlisted one is `NO_GRANTS` (fail-closed). */
256
- export function namespaceBaseline(actorId: string, table: Pick<GrantsTable, "everyone" | "browserReads">): Grants {
267
+ export function namespaceBaseline(actorId: string, table: Pick<GrantsTable, "everyone" | "browser">): Grants {
257
268
  if (actorId.startsWith("slack:")) return table.everyone;
258
- if (actorId.startsWith("access:") && !actorId.startsWith("access:svc:")) return table.browserReads;
269
+ if (actorId.startsWith("access:") && !actorId.startsWith("access:svc:")) return table.browser;
259
270
  return NO_GRANTS;
260
271
  }
261
272
 
@@ -271,7 +282,7 @@ export function grantsTable(source: GrantsSource): GrantsTable {
271
282
  const openAgents = (source.agentNames ?? []).filter((a) => !restrict.agents.has(a)).map(agentRunAction);
272
283
  const baselines = {
273
284
  everyone: { ...NO_GRANTS, actions: new Set([...CHAT_OPEN_ACTIONS, ...openAgents]) },
274
- browserReads: { ...NO_GRANTS, actions: browserReadActions(source.commandGroups ?? []) },
285
+ browser: { ...NO_GRANTS, actions: browserActions(source.commandGroups ?? []) },
275
286
  };
276
287
  const grants = new Map<string, Grants>();
277
288
  const surfaces = new Map<string, Grants>();
@@ -81,6 +81,12 @@ export const POLICY: readonly Rule[] = [
81
81
  // `friction propose` files issues: the repo-management gate, now the `friction:write` grant.
82
82
  { action: "friction:write", resource: "command", when: [grant("friction:write")] },
83
83
 
84
+ // ── costs ────────────────────────────────────────────────────────────────
85
+ // `costs snapshot` reads both billing providers and replaces what every
86
+ // viewer of the costs page sees: the grant, never a baseline (the admins'
87
+ // `all` and a named `grants` entry hold it).
88
+ { action: "costs:write", resource: "command", when: [grant("costs:write")] },
89
+
84
90
  // ── repos ────────────────────────────────────────────────────────────────
85
91
  { action: "repo:read", resource: "command", when: [grant("repo:read")] },
86
92
  { action: "repo:write", resource: "repo", when: [grant("repo:write")] },
@@ -8,6 +8,8 @@
8
8
  // parent record the spawn route reads the requester from, and the names the
9
9
  // routes decide on.
10
10
  //
11
+ import type { AddressSeverity, AddressSeveritySource } from "../ship/coordinator.js";
12
+
11
13
  // A coordinator is a Workflow instance in the shim Worker whose children are
12
14
  // ordinary `dispatch()` runs as the requesting user. It holds no credential of
13
15
  // its own: every step is a call into the bot with the `coordinator` bearer,
@@ -78,6 +80,22 @@ export function runFinishedEventType(runId: string): string {
78
80
  return `${RUN_FINISHED_EVENT_PREFIX}${runId}`;
79
81
  }
80
82
 
83
+ /** The event the bot's GitHub check-run intake sends a merge-waiting parent:
84
+ * the type carries the head sha (hex — inside the platform's alphabet), so a
85
+ * driver waiting at that head matches its own event and any other head's is
86
+ * buffered harmlessly. */
87
+ export const CHECKS_SETTLED_EVENT_PREFIX = "checks-settled-";
88
+ export function checksSettledEventType(headSha: string): string {
89
+ return `${CHECKS_SETTLED_EVENT_PREFIX}${headSha}`;
90
+ }
91
+
92
+ /** What the checks-settled event carries — the head and a clock; the parent
93
+ * re-asks the merge door before it acts, so nothing more rides here. */
94
+ export interface ChecksSettledPayload {
95
+ headSha: string;
96
+ settledAt: number;
97
+ }
98
+
81
99
  /** What the event carries — ids, a status and a clock; the parent confirms
82
100
  * through `read-record` before it acts, so nothing more rides here. */
83
101
  export interface RunFinishedPayload {
@@ -126,9 +144,12 @@ export function coordinatorFields(tag: CoordinatorTag | undefined): {
126
144
  export interface CoordinatorInstance {
127
145
  id: string;
128
146
  kind: "ship";
129
- /** The requesting user (platform-namespaced), whose grants every child is authorized under. */
147
+ /** The requesting person (platform-namespaced); every child is dispatched as them. */
130
148
  userId: string;
131
149
  userName?: string;
150
+ /** The bound credential behind the person (authorization.md item 15), when
151
+ * there was one: every child is authorized under ITS grants, as the request was. */
152
+ authenticatedAs?: string;
132
153
  channelId: string;
133
154
  channelName?: string;
134
155
  /** The requesting thread: where the card lives and where a generated plan's
@@ -153,6 +174,11 @@ export interface CoordinatorInstance {
153
174
  * hand-off, answered by the plan route, checked at the merge door; absent
154
175
  * (a record written before the field existed) reads as `person`. */
155
176
  merge?: "runner" | "person";
177
+ /** The severity to address: resolved once by the hand-off
178
+ * (directive > user > channel > org) and written here beside `merge`, so
179
+ * the machine reads one value; absent reads as the default (`minor`, org). */
180
+ addressSeverity?: AddressSeverity;
181
+ addressSeveritySource?: AddressSeveritySource;
156
182
  /** The pipeline's caps as the profile gate clipped them: the rounds cap and the wall clock per unit. */
157
183
  caps?: { maxRounds: number; maxMinutes: number };
158
184
  /** The status card in the requesting thread, when the channel has one — what
@@ -234,6 +260,7 @@ export function isCoordinatorInstance(v: unknown): v is CoordinatorInstance {
234
260
  if (r.kind !== "ship") return false;
235
261
  if (!isText(r.userId) || !isText(r.channelId) || !isText(r.threadKey)) return false;
236
262
  if (!isOptionalText(r.userName) || !isOptionalText(r.channelName) || !isOptionalText(r.sourceUrl)) return false;
263
+ if (!isOptionalText(r.authenticatedAs)) return false;
237
264
  if (typeof r.repo !== "string" || !REPO_SLUG.test(r.repo)) return false;
238
265
  if (!isText(r.branch) || !isOptionalText(r.base)) return false;
239
266
  if (!isFinite(r.createdAt)) return false;
@@ -299,6 +326,27 @@ export type RunFinishedSend =
299
326
  | { kind: "no-binding"; instance: string }
300
327
  | { kind: "failed"; instance: string; type: string; reason: string };
301
328
 
329
+ /** The one send per settled head (http-ingress.md item 12): best effort like
330
+ * `sendRunFinished` — a refusal is answered, never thrown, and the parent's
331
+ * bounded merge wait times out on its own. */
332
+ export async function sendChecksSettled(
333
+ workflow: WorkflowSender | undefined,
334
+ instance: string,
335
+ headSha: string,
336
+ settledAt: number,
337
+ ): Promise<RunFinishedSend> {
338
+ if (!workflow) return { kind: "no-binding", instance };
339
+ const type = checksSettledEventType(headSha);
340
+ const payload: ChecksSettledPayload = { headSha, settledAt };
341
+ try {
342
+ const handle = await workflow.get(instance);
343
+ await handle.sendEvent({ type, payload });
344
+ return { kind: "sent", instance, type };
345
+ } catch (err) {
346
+ return { kind: "failed", instance, type, reason: err instanceof Error ? err.message : String(err) };
347
+ }
348
+ }
349
+
302
350
  /** The one send per committed terminal record (run-history item 47). */
303
351
  export async function sendRunFinished(
304
352
  workflow: WorkflowSender | undefined,