@bongos/core 1.19.1073 → 1.19.1074

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 (297) hide show
  1. package/.bongos-core.json +320 -465
  2. package/.claude/skills/feedback/SKILL.md +2 -2
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +4 -60
  5. package/clients/bongos-client/index.cjs +4 -60
  6. package/clients/bongos-client/index.d.ts +5 -90
  7. package/clients/bongos-client/index.mjs +4 -60
  8. package/config/branding.neutral.json +1 -5
  9. package/config/modules.neutral.json +2 -3
  10. package/config/scheduled-routines.json +0 -2
  11. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
  12. package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
  13. package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
  14. package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
  15. package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
  16. package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
  17. package/docs/adr/README.md +1 -0
  18. package/docs/api/openapi.json +171 -1661
  19. package/docs/api-reference.md +9 -41
  20. package/docs/architecture.md +16 -1
  21. package/docs/branding-contract.md +0 -3
  22. package/docs/copy-inventory.md +454 -479
  23. package/docs/copy-registry.json +746 -978
  24. package/docs/file-map.md +5 -34
  25. package/docs/module-api-changelog.md +2 -0
  26. package/docs/modules-contract.md +13 -1
  27. package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
  28. package/docs/onboarding/diagrams/README.md +3 -3
  29. package/docs/onboarding/diagrams/assertions.json +2 -22
  30. package/docs/onboarding/primer.md +9 -28
  31. package/docs/page-inventory.json +4 -1
  32. package/docs/page-readings.json +1032 -1077
  33. package/docs/recipes/render-pilot.md +29 -10
  34. package/migrations/core_257_module_store_registry.sql +87 -0
  35. package/migrations/core_258_drop_dev_box_tables.sql +55 -0
  36. package/modules/agents/module.json +1 -0
  37. package/modules/autonomy/module.json +1 -0
  38. package/modules/builder-settings/module.json +1 -0
  39. package/modules/builder-settings/render-prefs.js +4 -5
  40. package/modules/copy-desk/docx.js +78 -5
  41. package/modules/copy-desk/module.json +1 -0
  42. package/modules/copy-desk/routes/copy-desk.js +9 -9
  43. package/modules/copy-desk/tests/copy_docx.mjs +107 -1
  44. package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
  45. package/modules/discord/module.json +1 -0
  46. package/modules/economy/module.json +1 -0
  47. package/modules/economy/routes/credits.js +1 -2
  48. package/modules/government/catalog.js +13 -17
  49. package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
  50. package/modules/government/module.json +1 -0
  51. package/modules/government/protected-surfaces.json +3 -11
  52. package/modules/government/resolver.js +3 -46
  53. package/modules/government/routes/government.js +0 -2
  54. package/modules/government/session-scopes.js +43 -83
  55. package/modules/government/session-scopes.json +8 -18
  56. package/modules/grading/module.json +1 -0
  57. package/modules/hall-ui/module.json +1 -0
  58. package/modules/hall-ui/public/board-lib.js +2 -2
  59. package/modules/hall-ui/public/builders.js +29 -40
  60. package/modules/hall-ui/public/collab.html +1 -1
  61. package/modules/hall-ui/public/collab.js +1 -1
  62. package/modules/hall-ui/public/diagrams.html +3 -3
  63. package/modules/hall-ui/public/dom-utils.js +1 -1
  64. package/modules/hall-ui/public/gate.js +2 -2
  65. package/modules/hall-ui/public/gate.states.json +1 -1
  66. package/modules/hall-ui/public/hall-render.js +43 -79
  67. package/modules/hall-ui/public/index.html +5 -1
  68. package/modules/hall-ui/public/modules.js +1 -0
  69. package/modules/hall-ui/public/oversight.css +5 -5
  70. package/modules/hall-ui/public/palette.js +0 -11
  71. package/modules/hall-ui/public/primer.js +1 -1
  72. package/modules/hall-ui/public/sessions.html +1 -1
  73. package/modules/hall-ui/public/settings-sessions.js +1 -1
  74. package/modules/hall-ui/public/settings.css +0 -1
  75. package/modules/hall-ui/public/settings.html +4 -33
  76. package/modules/hall-ui/public/settings.js +3 -33
  77. package/modules/hall-ui/public/settings.states.json +2 -2
  78. package/modules/hall-ui/public/studio.html +1 -1
  79. package/modules/hall-ui/public/style.css +10 -13
  80. package/modules/hall-ui/public/task.html +4 -1
  81. package/modules/hall-ui/public/task.js +36 -1
  82. package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
  83. package/modules/hall-ui/public/tweak-editor.css +16 -0
  84. package/modules/hall-ui/public/tweak-editor.html +26 -3
  85. package/modules/hall-ui/public/tweak-editor.js +32 -1
  86. package/modules/hall-ui/public/work.js +1 -1
  87. package/modules/hall-ui/records/tweak-editor.md +1 -0
  88. package/modules/ideas/module.json +1 -0
  89. package/modules/lifecycle/db-claim-reads.js +1 -58
  90. package/modules/lifecycle/db-ship.js +0 -5
  91. package/modules/lifecycle/db.js +0 -4
  92. package/modules/lifecycle/kickoff-checklist.js +0 -4
  93. package/modules/lifecycle/lifecycle.js +0 -8
  94. package/modules/lifecycle/module.json +1 -0
  95. package/modules/lifecycle/routes/tasks.js +5 -7
  96. package/modules/memory/module.json +1 -0
  97. package/modules/npm-release/module.json +1 -0
  98. package/modules/onboarding/module.json +1 -0
  99. package/modules/onboarding/onboarding-state.js +17 -36
  100. package/modules/onboarding/routes/access-requests.js +3 -3
  101. package/modules/platform-identity/module.json +1 -0
  102. package/modules/platform-identity/platform-identity.js +13 -5
  103. package/modules/platform-identity/routes/sso.js +2 -2
  104. package/modules/platform-identity/tests/platform-identity.mjs +17 -3
  105. package/modules/provisioning/module.json +1 -0
  106. package/modules/provisioning/provisioning.js +2 -2
  107. package/modules/provisioning/starter-bundles.js +5 -25
  108. package/modules/public-landing/module.json +1 -0
  109. package/modules/public-landing/public/assets/cosmos.css +1 -1
  110. package/modules/public-landing/public/contact.html +1 -1
  111. package/modules/public-landing/public/privacy.html +1 -1
  112. package/modules/public-landing/public/projects.html +101 -32
  113. package/modules/public-landing/public/projects.probes.json +2 -2
  114. package/modules/public-landing/public/projects.states.json +4 -3
  115. package/modules/public-landing/public/terms.html +1 -1
  116. package/modules/security/module.json +1 -0
  117. package/modules/sessions/module.json +1 -0
  118. package/modules/specialities/module.json +1 -0
  119. package/modules/status-ui/module.json +1 -0
  120. package/modules/ui-design/module.json +1 -0
  121. package/package-lock.json +2 -2
  122. package/package.json +1 -1
  123. package/release-notes.json +78 -0
  124. package/scripts/gds/artifact-format.js +203 -0
  125. package/scripts/gds/audit-authorship.sh +1 -13
  126. package/scripts/gds/claim.js +0 -15
  127. package/scripts/gds/cli-lib.js +8 -8
  128. package/scripts/gds/diagram-facts.js +12 -23
  129. package/scripts/gds/do-api.js +13 -80
  130. package/scripts/gds/doc-cli-guard.js +2 -0
  131. package/scripts/gds/doctor.js +1 -1
  132. package/scripts/gds/feedback-latest.js +139 -9
  133. package/scripts/gds/fitness-checks-identity.js +2 -7
  134. package/scripts/gds/fitness-checks-write-validation.js +4 -0
  135. package/scripts/gds/fitness-ratchets.js +4 -4
  136. package/scripts/gds/fitness.js +10 -10
  137. package/scripts/gds/gen-diagrams.js +49 -71
  138. package/scripts/gds/http-api-client.js +12 -19
  139. package/scripts/gds/init.js +1 -1
  140. package/scripts/gds/lint-infra-exec.js +1 -1
  141. package/scripts/gds/local-preview-lib.js +8 -39
  142. package/scripts/gds/local-preview.js +2 -15
  143. package/scripts/gds/migration-namespace.js +3 -3
  144. package/scripts/gds/module-artifact.js +212 -0
  145. package/scripts/gds/module.js +95 -6
  146. package/scripts/gds/package-core.js +5 -116
  147. package/scripts/gds/provision-config.js +2 -2
  148. package/scripts/gds/provision-net.js +2 -3
  149. package/scripts/gds/provision.js +9 -9
  150. package/scripts/gds/publish-manifest.js +1 -2
  151. package/scripts/gds/regen-instance-docs.js +1 -1
  152. package/scripts/gds/run-unit-tests.js +2 -16
  153. package/scripts/gds/sandbox-stage.js +39 -341
  154. package/scripts/gds/ship-flow.js +6 -6
  155. package/scripts/gds/ship-land.js +3 -4
  156. package/scripts/gds/ship-regen.js +10 -41
  157. package/scripts/gds/ship.js +4 -24
  158. package/scripts/gds/start.js +0 -15
  159. package/scripts/hall-preview/README.md +5 -5
  160. package/scripts/hall-preview/server.js +6 -6
  161. package/scripts/render-diagrams.sh +1 -1
  162. package/src/bongos/api-errors.js +1 -1
  163. package/src/bongos/auth-github.js +2 -3
  164. package/src/bongos/auth.js +51 -126
  165. package/src/bongos/db-kernel.js +2 -3
  166. package/src/bongos/db.js +5 -28
  167. package/src/bongos/module-scope-map.js +14 -29
  168. package/src/bongos/module-store.js +141 -0
  169. package/src/bongos/route-rank-check.js +3 -16
  170. package/src/bongos/routes/auth.js +0 -116
  171. package/src/bongos/routes/builders.js +5 -15
  172. package/src/bongos/routes/instance.js +8 -14
  173. package/src/bongos/routes/me.js +6 -22
  174. package/src/bongos/routes/modules.js +83 -0
  175. package/src/bongos/routes.js +5 -0
  176. package/src/module-api.js +13 -6
  177. package/src/module-loader/catalog.js +3 -0
  178. package/src/module-loader/loader.js +4 -4
  179. package/src/module-loader/manifest-schema.js +15 -7
  180. package/src/module-seams.js +2 -3
  181. package/src/modules.js +42 -14
  182. package/tests/agents_spend_guard.mjs +1 -1
  183. package/tests/api_client.mjs +0 -15
  184. package/tests/auth_page_gate.mjs +10 -10
  185. package/tests/canonical_profile_url.mjs +1 -3
  186. package/tests/claim_action.mjs +4 -1
  187. package/tests/claim_from_task_and_home.mjs +127 -0
  188. package/tests/consumer_layout_boot.mjs +2 -0
  189. package/tests/copy_desk_page_docx.mjs +33 -2
  190. package/tests/core_upgrade_runner.mjs +6 -6
  191. package/tests/deploy_divergence_line.mjs +11 -11
  192. package/tests/diagram_facts_offset.mjs +1 -1
  193. package/tests/do_api.mjs +106 -0
  194. package/tests/feedback_latest.mjs +122 -0
  195. package/tests/fitness.mjs +44 -42
  196. package/tests/gate_approvals.mjs +0 -1
  197. package/tests/go_live.mjs +3 -3
  198. package/tests/government_abuse_matrix.mjs +20 -19
  199. package/tests/government_ownership_scope.mjs +33 -35
  200. package/tests/government_parity.mjs +2 -2
  201. package/tests/government_protected_surfaces.mjs +56 -37
  202. package/tests/government_require_permission.mjs +3 -3
  203. package/tests/government_seed.mjs +37 -4
  204. package/tests/government_session_scope.mjs +92 -366
  205. package/tests/hall_error_envelope.mjs +4 -0
  206. package/tests/hall_landing_boot.mjs +4 -1
  207. package/tests/hall_palette.mjs +1 -16
  208. package/tests/hall_record_world.mjs +6 -0
  209. package/tests/hall_tweak_editor.mjs +52 -1
  210. package/tests/host_topology_skips.mjs +1 -2
  211. package/tests/html_comment_nesting.mjs +137 -0
  212. package/tests/infra_exec_paths.mjs +9 -10
  213. package/tests/init.mjs +4 -5
  214. package/tests/instance_manifest.mjs +18 -25
  215. package/tests/kickoff_checklist.mjs +1 -1
  216. package/tests/lib_sh_resolution.mjs +0 -221
  217. package/tests/local_preview.mjs +0 -12
  218. package/tests/module-scope-map.mjs +9 -9
  219. package/tests/module_catalog.mjs +1 -1
  220. package/tests/module_cli.mjs +15 -15
  221. package/tests/module_contributions.mjs +6 -6
  222. package/tests/module_loader.mjs +6 -6
  223. package/tests/module_manifest.mjs +13 -4
  224. package/tests/module_store_publish.mjs +307 -0
  225. package/tests/module_store_publish_route.mjs +121 -0
  226. package/tests/module_store_registry_migration.mjs +70 -0
  227. package/tests/modules.mjs +56 -26
  228. package/tests/onboarding_route_signals.mjs +21 -31
  229. package/tests/onboarding_state.mjs +35 -49
  230. package/tests/permission_path.mjs +0 -3
  231. package/tests/platform_boot.mjs +1 -2
  232. package/tests/profile_route.mjs +1 -6
  233. package/tests/projects_hub.mjs +5 -5
  234. package/tests/projects_hub_app_step.mjs +138 -0
  235. package/tests/projects_hub_module_picker.mjs +2 -2
  236. package/tests/projects_hub_pre_uat.mjs +4 -5
  237. package/tests/provision.mjs +7 -7
  238. package/tests/provisioning_starter_bundles.mjs +0 -22
  239. package/tests/publish_branch_route.mjs +10 -18
  240. package/tests/publish_manifest.mjs +1 -2
  241. package/tests/rank_tier_single_source.mjs +1 -13
  242. package/tests/repo_map.mjs +3 -4
  243. package/tests/runner_drift.mjs +3 -21
  244. package/tests/sandbox_stage.mjs +57 -390
  245. package/tests/session_rename_fallback.mjs +0 -21
  246. package/tests/ship_error_shape.mjs +1 -1
  247. package/tests/task_visual_slots.mjs +31 -0
  248. package/tests/terms_acceptance.mjs +1 -1
  249. package/tests/upgrade.mjs +4 -4
  250. package/tests/watch_sealed_floor.mjs +0 -1
  251. package/tests/wizard_draft_resume.mjs +2 -2
  252. package/tests/wizard_intent_resume.mjs +21 -10
  253. package/tests/wizard_preselect_why.mjs +18 -18
  254. package/docs/onboarding/browser-terminal-guide.md +0 -73
  255. package/docs/recipes/managed-settings-remote-control.md +0 -416
  256. package/modules/dev-box/CLAUDE.md +0 -15
  257. package/modules/dev-box/box-access.js +0 -623
  258. package/modules/dev-box/box-credential.js +0 -94
  259. package/modules/dev-box/box-onboard.js +0 -486
  260. package/modules/dev-box/boxes.js +0 -1038
  261. package/modules/dev-box/db.js +0 -39
  262. package/modules/dev-box/module.json +0 -19
  263. package/modules/dev-box/routes/box.js +0 -1223
  264. package/scripts/gds/box-auth-check.js +0 -160
  265. package/scripts/gds/box-infra.js +0 -321
  266. package/scripts/gds/box-sync.js +0 -216
  267. package/scripts/gds/box.js +0 -1332
  268. package/scripts/gds/cf-tunnel.js +0 -558
  269. package/scripts/gds/smoke-box.sh +0 -150
  270. package/scripts/gds/tree-preflight.js +0 -241
  271. package/src/bongos/app-pair.js +0 -172
  272. package/tests/app_pair.mjs +0 -168
  273. package/tests/box_access.mjs +0 -578
  274. package/tests/box_auth_check.mjs +0 -114
  275. package/tests/box_code_staleness.mjs +0 -129
  276. package/tests/box_connect_e2e.mjs +0 -196
  277. package/tests/box_cost.mjs +0 -70
  278. package/tests/box_credential.mjs +0 -125
  279. package/tests/box_dns_repoint.mjs +0 -153
  280. package/tests/box_env.mjs +0 -64
  281. package/tests/box_host_keys.mjs +0 -126
  282. package/tests/box_infra_resolve.mjs +0 -246
  283. package/tests/box_onboard.mjs +0 -317
  284. package/tests/box_scope_session.mjs +0 -119
  285. package/tests/box_sweep_docs.mjs +0 -119
  286. package/tests/box_sync.mjs +0 -55
  287. package/tests/box_sync_scope_report.mjs +0 -126
  288. package/tests/box_task_scope.mjs +0 -169
  289. package/tests/box_task_scope_pause.mjs +0 -66
  290. package/tests/box_terminal_ssrf.mjs +0 -96
  291. package/tests/boxes.mjs +0 -1585
  292. package/tests/cf_ruleset_preflight.mjs +0 -142
  293. package/tests/cf_tunnel.mjs +0 -496
  294. package/tests/docs_scrubber_damage.mjs +0 -111
  295. package/tests/module_scope_active_claims.mjs +0 -94
  296. package/tests/ship_api_client_pathspec.mjs +0 -100
  297. package/tests/tree_preflight.mjs +0 -243
@@ -185,9 +185,11 @@ const ADDED_SINCE_R101 = [
185
185
  },
186
186
  // task 1002891 (from idea 1000692, raised by the grader's hacker worker). Group
187
187
  // 'box-access' protected who DECIDES box access but nothing that HOLDS the
188
- // infrastructure credentials those decisions spend. The five readers below are the
188
+ // infrastructure credentials those decisions spend. The readers below are the
189
189
  // COMPUTED set — the credential-reader tripwire further down recomputes it from the
190
190
  // tracked tree on every run — plus do-api.js, which the credential is handed to.
191
+ // (Three more readers — the box control plane's box.js, box-infra.js and
192
+ // cf-tunnel.js — were deleted with it in task 1003893, entries and all.)
191
193
  {
192
194
  after: 'modules/dev-box/box-credential.js',
193
195
  glob: 'scripts/gds/provision-config.js',
@@ -205,29 +207,6 @@ const ADDED_SINCE_R101 = [
205
207
  },
206
208
  {
207
209
  after: 'scripts/gds/provision-net.js',
208
- glob: 'scripts/gds/box-infra.js',
209
- why: 'reads process.env.CLOUDFLARE_API_TOKEN and process.env.CLOUDFLARE_ACCOUNT_ID '
210
- + 'into the Cloudflare config the box plane provisions tunnels and DNS against.',
211
- },
212
- {
213
- after: 'scripts/gds/box-infra.js',
214
- glob: 'scripts/gds/cf-tunnel.js',
215
- why: 'defaults its token parameter to process.env.CLOUDFLARE_API_TOKEN, so it reads '
216
- + 'the credential whenever a caller omits one. The recorded decision did not name '
217
- + 'this file; the recomputation found it, which is the case for computing the set '
218
- + 'rather than transcribing a list.',
219
- },
220
- {
221
- after: 'scripts/gds/cf-tunnel.js',
222
- glob: 'scripts/gds/box.js',
223
- why: 'reads process.env.DO_API_TOKEN — the DigitalOcean control plane for the box '
224
- + 'fleet, and also not named by the recorded decision. Adding it widens no module '
225
- + 'wall: the dev-box key already lists this path and already derives protected '
226
- + 'from the modules/dev-box/ entries, so the derived protected-module set is '
227
- + 'unchanged (asserted below).',
228
- },
229
- {
230
- after: 'scripts/gds/box.js',
231
210
  glob: 'scripts/gds/do-api.js',
232
211
  why: 'the DigitalOcean API client the token is handed TO. It reads no process.env '
233
212
  + 'value — the token arrives as a parameter and the constructor throws when a live '
@@ -281,9 +260,6 @@ const ADDED_SINCE_R101 = [
281
260
  ...carve('src/bongos/db.js', ['db-kernel'],
282
261
  'the kernel slice carved out of db.js: builder identity and the rank reads the '
283
262
  + 'SEC #863 entry exists for.'),
284
- ...carve('scripts/gds/box.js', ['box-auth-check', 'box-sync'],
285
- 'the box.js family: the dev-box session-start auth gate and the box sync; box-infra.js '
286
- + 'was already listed by name.'),
287
263
  ...carve('modules/grading/grader.js', ['grader-config', 'grader-model', 'grader-no-artifact',
288
264
  'grader-prompt', 'grader-score', 'grader-subagent'],
289
265
  'the grader.js carve: the deterministic permission-path pre-pass and the server-side '
@@ -338,8 +314,24 @@ const RENAMED_SINCE_R101 = [
338
314
  },
339
315
  ];
340
316
 
317
+ // Surfaces RETIRED since R101 — the protected FILE itself was deleted from the
318
+ // repo, so there is nothing left to protect. The third shape, and the narrowest: a
319
+ // retirement is accepted only for a path the tracked tree no longer contains (the
320
+ // test below checks the tree, not this prose), so it can never be used to drop
321
+ // protection from code that still runs. Applied LAST, after renames and additions,
322
+ // so an addition anchored on a retired entry still resolves.
323
+ const RETIRED_SINCE_R101 = [
324
+ ...['boxes.js', 'box-access.js', 'box-credential.js'].map((f) => ({
325
+ glob: `modules/dev-box/${f}`,
326
+ why: 'task 1003892 (goal 1000120) deleted the dev-box module outright — the rank floor '
327
+ + 'and source-credential code these entries guarded no longer exists anywhere in '
328
+ + 'the tree, and the fleet it decided access to was already torn down.',
329
+ })),
330
+ ];
331
+
341
332
  // The list the registry must produce TODAY: the pre-R101 set with each
342
- // documented addition spliced in at its declared position.
333
+ // documented addition spliced in at its declared position, then each documented
334
+ // retirement removed.
343
335
  const EXPECTED_GLOBS = (() => {
344
336
  let out = [...EXPECTED_GLOBS_BEFORE_R101];
345
337
  for (const r of RENAMED_SINCE_R101) {
@@ -352,6 +344,11 @@ const EXPECTED_GLOBS = (() => {
352
344
  assert.notEqual(at, -1, `ADDED_SINCE_R101 anchors on '${a.after}', which is not in the list`);
353
345
  out.splice(at + 1, 0, a.glob);
354
346
  }
347
+ for (const r of RETIRED_SINCE_R101) {
348
+ const at = out.indexOf(r.glob);
349
+ assert.notEqual(at, -1, `RETIRED_SINCE_R101 retires '${r.glob}', which is not in the list`);
350
+ out.splice(at, 1);
351
+ }
355
352
  return out;
356
353
  })();
357
354
 
@@ -363,8 +360,10 @@ test('nothing protected before R101 has been unprotected since', () => {
363
360
  // replacement must actually be present — otherwise "it was renamed" becomes a
364
361
  // way to unprotect a trust surface with a comment.
365
362
  const renamedFrom = new Map(RENAMED_SINCE_R101.map((r) => [r.from, r.to]));
363
+ const retired = new Set(RETIRED_SINCE_R101.map((r) => r.glob));
366
364
  for (const glob of EXPECTED_GLOBS_BEFORE_R101) {
367
365
  if (registry.PROTECTED_GLOBS.includes(glob)) continue;
366
+ if (retired.has(glob)) continue; // proven gone from the tree by the test below
368
367
  const to = renamedFrom.get(glob);
369
368
  assert.ok(to,
370
369
  `'${glob}' was protected before R101 and is no longer in the registry — `
@@ -376,6 +375,20 @@ test('nothing protected before R101 has been unprotected since', () => {
376
375
  }
377
376
  });
378
377
 
378
+ test('a surface is retired only once its path is gone from the tracked tree', () => {
379
+ // The whole guarantee of RETIRED_SINCE_R101: "the file was deleted" is checked
380
+ // against git, so a retirement cannot unprotect a live path with a comment.
381
+ const tracked = execFileSync('git', ['ls-files'], { cwd: ROOT, encoding: 'utf8' })
382
+ .split('\n').map((f) => f.trim()).filter(Boolean);
383
+ for (const r of RETIRED_SINCE_R101) {
384
+ assert.ok(r.why && r.why.length > 40, `${r.glob} must record WHY it was retired`);
385
+ assert.ok(!registry.PROTECTED_GLOBS.includes(r.glob), `${r.glob} is declared retired but is still registered`);
386
+ const prefix = r.glob.endsWith('/') ? r.glob : null;
387
+ const live = tracked.filter((f) => (prefix ? f.startsWith(prefix) : f === r.glob));
388
+ assert.deepEqual(live, [], `${r.glob} is declared retired but the tree still tracks it — a live path cannot be unprotected this way`);
389
+ }
390
+ });
391
+
379
392
  test('every rename since R101 carries a documented reason', () => {
380
393
  for (const r of RENAMED_SINCE_R101) {
381
394
  assert.ok(r.why && r.why.length > 40,
@@ -452,7 +465,8 @@ test('every execution root in the closure is a protected surface (MIT1 drift tri
452
465
  //
453
466
  // task 1004024. Splitting a protected file into `<name>-<phase>.js` siblings is this repo's
454
467
  // carve convention, and every carve so far left only the facade registered: ship.js (21
455
- // files), grader.js (6), auth.js (5), db.js, box.js. A require()-graph rule was measured and
468
+ // files), grader.js (6), auth.js (5), db.js (and one more family since deleted outright,
469
+ // task 1003893). A require()-graph rule was measured and
456
470
  // rejected — from the protected scripts it reaches 52 unprotected files (upgrade.js, login.js,
457
471
  // package-core.js…), floor-gating ordinary work. The name rule is the shape a carve leaves.
458
472
  function stemSiblings(entry, tracked) {
@@ -494,7 +508,7 @@ test('every file split out of a protected file is itself protected (1004024 carv
494
508
  `${rel} is floored at '${surface.floor}', below the ${entry} it was carved from.`);
495
509
  }
496
510
  }
497
- assert.ok(checked >= 35, `fixture sanity: expected the five known carves (35 files), saw ${checked}`);
511
+ assert.ok(checked >= 35, `fixture sanity: expected the four known carves (35 files), saw ${checked}`);
498
512
  });
499
513
 
500
514
  // ---------- the credential tripwire: every credential READER is a protected surface ----------
@@ -612,10 +626,13 @@ test('credentialReadsIn does not fire on a near-miss', () => {
612
626
 
613
627
  test('every credential reader in the tree is a protected surface (1002891 tripwire)', () => {
614
628
  const readers = credentialReaders();
615
- assert.ok(readers.length >= 5,
629
+ // The floor was five from task 1002891 until task 1003893 deleted three of the
630
+ // readers (the box control plane's box.js, box-infra.js and cf-tunnel.js), leaving
631
+ // provision-config.js and provision-net.js.
632
+ assert.ok(readers.length >= 2,
616
633
  'fixture sanity: the scan found '
617
- + `${readers.length} credential reader(s), and the tree has had at least five since `
618
- + 'task 1002891. A scan that finds nothing passes every assertion below without '
634
+ + `${readers.length} credential reader(s), and the tree has had at least two since `
635
+ + 'task 1003893. A scan that finds nothing passes every assertion below without '
619
636
  + 'checking anything — treat this as the scanner being broken, not as good news.');
620
637
  for (const rel of readers) {
621
638
  const surface = ppc.surfaceFor(rel);
@@ -623,7 +640,7 @@ test('every credential reader in the tree is a protected surface (1002891 tripwi
623
640
  `${rel} READS an infrastructure credential off process.env `
624
641
  + `(${credentialReadsIn(fs.readFileSync(path.join(ROOT, rel), 'utf8')).join(', ')}) `
625
642
  + 'but no protected surface covers it. Add it to '
626
- + 'modules/government/protected-surfaces.json (group "box-access") and declare it '
643
+ + 'modules/government/protected-surfaces.json (group "control-plane") and declare it '
627
644
  + 'in ADDED_SINCE_R101 above.');
628
645
  assert.ok(ppc.RANK_TIER[surface.floor] >= ppc.RANK_TIER.metic,
629
646
  `${rel} holds an infrastructure credential but is floored at '${surface.floor}' — `
@@ -698,9 +715,11 @@ test('registering the credential readers widened no module wall', () => {
698
715
  'the provisioning module derived protected:true — a credential-reader entry reached '
699
716
  + 'modules/provisioning/ or scripts/gds/provision.js, which walls goals 1000032, '
700
717
  + '1000046 and 1000059 behind an Archon join. Keep the entries file-level.');
701
- assert.ok(protectedNow.includes('dev-box'),
702
- 'dev-box was already protected via its modules/dev-box/ globs before 1002891; if it '
703
- + 'is not, the premise of the box.js entry has changed.');
718
+ // Since task 1003892 the readers are owned by their own control-plane scope, which
719
+ // is protected BY them — that is where the wall belongs, not on provisioning.
720
+ assert.ok(protectedNow.includes('control-plane'),
721
+ 'the control-plane scope owns the credential readers and must derive protected:true; '
722
+ + 'if it does not, a reader lost its registry entry or its owner.');
704
723
  });
705
724
 
706
725
  // ---------- the routine-switch tripwire: every SWITCHBOARD is a protected surface ----------
@@ -255,7 +255,7 @@ test('over the four seeded ranks, requirePermission admits exactly who requireRa
255
255
  // means a floor moving without an owner decision reds the build, which is the whole
256
256
  // point of keeping parity as an assertion instead of prose.
257
257
  const DELEGATED_TO_METIC_BY_ADR_0155 = [
258
- 'autonomy.precheck', 'box.fleet.manage', 'discord.channels.manage', 'idea.ratify',
258
+ 'autonomy.precheck', 'discord.channels.manage', 'idea.ratify',
259
259
  'claim.release.any', 'criterion.unsatisfy', 'goal.reorder', 'task.newcomer_restock',
260
260
  'task.peer_votes.tally', 'override_request.decide', 'memory.read.any',
261
261
  'project.curate', 'provisioning.fleet.manage', 'security.report.adjudicate',
@@ -439,7 +439,7 @@ test('every modules/*/routes gate is migrated, bar the one documented holdout',
439
439
  // the OWNERSHIP axis cannot gate a route (task 1003197 (C))
440
440
  // ---------------------------------------------------------------------------
441
441
  //
442
- // `held.has('box.manage.own')` is true for anyone whose rank carries the atom, so a
442
+ // `held.has('task.abandon.own')` is true for anyone whose rank carries the atom, so a
443
443
  // gate mounted on an ownership-scoped key would admit UNCONDITIONALLY — `.own`
444
444
  // collapsed into `.any`, the exact degradation the axis exists to prevent, because
445
445
  // the gate is never handed the row whose owner is the other half of the question.
@@ -463,7 +463,7 @@ const OWNED_KEY = catalog.permissionKeys().find((k) => catalog.isOwnershipScoped
463
463
 
464
464
  test('the catalog still declares at least one ownership-scoped key', () => {
465
465
  // If the axis were ever emptied, the assertions below would pass vacuously.
466
- assert.ok(OWNED_KEY, 'expected an ownership-scoped atom in the catalog (e.g. box.manage.own)');
466
+ assert.ok(OWNED_KEY, 'expected an ownership-scoped atom in the catalog (e.g. task.abandon.own)');
467
467
  });
468
468
 
469
469
  test('an ownership-scoped key 403s even when the builder HOLDS it', async () => {
@@ -91,6 +91,13 @@ function grantBlocksForRole(role) {
91
91
  // not a guard.
92
92
  const REVOKE_RE =
93
93
  /DELETE\s+FROM\s+govern(?:ance_role|ment_rank)_permissions\s+WHERE\s+(?:rank_key|role_key)\s*=\s*'([^']+)'\s+AND\s+permission_key\s*=\s*'([^']+)'/gi;
94
+ // The EVERY-RANK form (task 1003894): a key RETIRED from the catalog is revoked
95
+ // from every rank at once, custom ranks included, so the statement names no rank:
96
+ // DELETE FROM government_rank_permissions WHERE permission_key IN ('a', 'b');
97
+ // government_019 is the first. Read as "gone from every role" by the fold below.
98
+ const REVOKE_ALL_RE =
99
+ /DELETE\s+FROM\s+govern(?:ance_role|ment_rank)_permissions\s+WHERE\s+permission_key\s+IN\s*\(([^)]*)\)\s*;/gi;
100
+ const keysIn = (list) => [...list.matchAll(/'([^']+)'/g)].map((x) => x[1]);
94
101
 
95
102
 
96
103
  // The EFFECTIVE grant set: walk the migrations in filename order, adding each
@@ -109,6 +116,9 @@ function effectiveKeysForRole(role) {
109
116
  for (const m of code.matchAll(new RegExp(REVOKE_RE.source, 'gi'))) {
110
117
  if (m[1] === role) held.delete(RENAMED_KEYS.get(m[2]) || m[2]);
111
118
  }
119
+ for (const m of code.matchAll(new RegExp(REVOKE_ALL_RE.source, 'gi'))) {
120
+ for (const k of keysIn(m[1])) held.delete(RENAMED_KEYS.get(k) || k);
121
+ }
112
122
  }
113
123
  return held;
114
124
  }
@@ -152,14 +162,37 @@ test('every revoking migration is in the shape the union model subtracts', () =>
152
162
  const deletes = [...code.matchAll(/DELETE\s+FROM\s+(govern(?:ance_role|ment_rank)_permissions)\b/gi)];
153
163
  if (deletes.length === 0) continue;
154
164
  const parsed = [...code.matchAll(new RegExp(REVOKE_RE.source, 'gi'))];
155
- assert.equal(parsed.length, deletes.length,
156
- `${file}: ${deletes.length} DELETE(s) but only ${parsed.length} parseable by REVOKE_RE — `
165
+ const parsedAll = [...code.matchAll(new RegExp(REVOKE_ALL_RE.source, 'gi'))];
166
+ assert.equal(parsed.length + parsedAll.length, deletes.length,
167
+ `${file}: ${deletes.length} DELETE(s) but only ${parsed.length + parsedAll.length} parseable by REVOKE_RE / REVOKE_ALL_RE — `
157
168
  + 'the union model would silently overstate the grant set. Match the '
158
- + "`DELETE FROM government_rank_permissions WHERE rank_key = '…' AND permission_key = '…'` shape.");
169
+ + "`DELETE FROM government_rank_permissions WHERE rank_key = '…' AND permission_key = '…'` shape, "
170
+ + "or `… WHERE permission_key IN ('…')` to revoke from every rank.");
159
171
  for (const m of parsed) {
160
172
  assert.ok(RANK_ORDER.includes(m[1]), `${file}: revokes from unknown rank '${m[1]}'`);
161
173
  }
174
+ for (const m of parsedAll) {
175
+ assert.ok(keysIn(m[1]).length > 0, `${file}: an every-rank revoke names no key`);
176
+ }
177
+ }
178
+ });
179
+
180
+ test('an every-rank revocation removes a retired key from EVERY rank', () => {
181
+ // government_019 (task 1003894) retires the three dev-box keys with one statement
182
+ // and no rank predicate. governance_002 granted box.manage.own to all four seeded
183
+ // ranks, so all four must come out without it — and the raw union must still
184
+ // hold it, or the historical grant was edited away rather than superseded.
185
+ for (const role of RANK_ORDER) {
186
+ for (const key of ['box.source.full', 'box.fleet.manage', 'box.manage.own']) {
187
+ assert.equal(effectiveKeysForRole(role).has(key), false, `${role} still holds ${key}`);
188
+ }
162
189
  }
190
+ const rawUnion = applyRenames(grantBlocksForRole('xenos').flatMap((b) => b.keys));
191
+ assert.ok(rawUnion.includes('box.manage.own'),
192
+ 'the historical grant must remain in governance_002 — migrations are run-once and are never rewritten');
193
+ // The shape the fold reads must be the shape the migration was written in.
194
+ const sql = fs.readFileSync(path.join(MIG_DIR, 'government_019_retire_box_permissions.sql'), 'utf8');
195
+ assert.equal([...stripComments(sql).matchAll(new RegExp(REVOKE_ALL_RE.source, 'gi'))].length, 1);
163
196
  });
164
197
 
165
198
  test('a revocation actually removes the key from the effective set', () => {
@@ -217,7 +250,7 @@ test('later grant migrations are idempotent + transactional', () => {
217
250
  const code = stripComments(sql);
218
251
  const inserts = code.match(/INSERT INTO/g) || [];
219
252
  const onConflict = code.match(/ON CONFLICT/g) || [];
220
- const revokes = [...code.matchAll(new RegExp(REVOKE_RE.source, 'gi'))];
253
+ const revokes = [...code.matchAll(new RegExp(REVOKE_RE.source, 'gi')), ...code.matchAll(new RegExp(REVOKE_ALL_RE.source, 'gi'))];
221
254
  // A migration must DO something to the grant set — but since BV1.R21 (task
222
255
  // 1003608) "something" includes taking a permission away, not only adding
223
256
  // one. A REVOKE-only migration inserts nothing by design and needs no ON