@bongos/core 1.19.1073 → 1.19.1075

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 (377) hide show
  1. package/.bongos-core.json +404 -549
  2. package/.claude/skills/feedback/SKILL.md +2 -2
  3. package/bin/bongos.js +1 -3
  4. package/clients/bongos-client/README.md +1 -1
  5. package/clients/bongos-client/bongos-client.global.js +4 -60
  6. package/clients/bongos-client/index.cjs +4 -60
  7. package/clients/bongos-client/index.d.ts +5 -90
  8. package/clients/bongos-client/index.mjs +4 -60
  9. package/config/branding.neutral.json +1 -5
  10. package/config/modules.neutral.json +2 -3
  11. package/config/scheduled-routines.json +0 -2
  12. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
  13. package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
  14. package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
  15. package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
  16. package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
  17. package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
  18. package/docs/adr/README.md +1 -0
  19. package/docs/api/openapi.json +175 -1665
  20. package/docs/api-reference.md +9 -41
  21. package/docs/architecture.md +16 -1
  22. package/docs/branding-contract.md +0 -3
  23. package/docs/copy-inventory.md +454 -479
  24. package/docs/copy-registry.json +746 -978
  25. package/docs/file-map.md +5 -34
  26. package/docs/module-api-changelog.md +4 -0
  27. package/docs/modules-contract.md +13 -1
  28. package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
  29. package/docs/onboarding/diagrams/README.md +3 -3
  30. package/docs/onboarding/diagrams/assertions.json +2 -22
  31. package/docs/onboarding/primer.md +9 -28
  32. package/docs/page-inventory.json +4 -1
  33. package/docs/page-readings.json +1032 -1077
  34. package/docs/recipes/render-pilot.md +29 -10
  35. package/migrations/core_257_module_store_registry.sql +87 -0
  36. package/migrations/core_258_drop_dev_box_tables.sql +55 -0
  37. package/modules/agents/module.json +1 -0
  38. package/modules/autonomy/module.json +1 -0
  39. package/modules/builder-settings/module.json +1 -0
  40. package/modules/builder-settings/render-prefs.js +4 -5
  41. package/modules/copy-desk/docx.js +78 -5
  42. package/modules/copy-desk/module.json +1 -0
  43. package/modules/copy-desk/routes/copy-desk.js +10 -10
  44. package/modules/copy-desk/tests/copy_docx.mjs +107 -1
  45. package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
  46. package/modules/discord/module.json +1 -0
  47. package/modules/discord/ship-broadcast.js +3 -3
  48. package/modules/economy/credits.js +2 -2
  49. package/modules/economy/module.json +1 -0
  50. package/modules/economy/routes/credits.js +1 -2
  51. package/modules/government/catalog.js +13 -17
  52. package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
  53. package/modules/government/module.json +1 -0
  54. package/modules/government/protected-surfaces.json +3 -11
  55. package/modules/government/resolver.js +3 -46
  56. package/modules/government/routes/government.js +0 -2
  57. package/modules/government/session-scopes.js +43 -83
  58. package/modules/government/session-scopes.json +8 -18
  59. package/modules/grading/module.json +1 -0
  60. package/modules/hall-ui/module.json +1 -0
  61. package/modules/hall-ui/public/board-lib.js +2 -2
  62. package/modules/hall-ui/public/builders.js +29 -40
  63. package/modules/hall-ui/public/collab.html +1 -1
  64. package/modules/hall-ui/public/collab.js +1 -1
  65. package/modules/hall-ui/public/diagrams.html +3 -3
  66. package/modules/hall-ui/public/dom-utils.js +1 -1
  67. package/modules/hall-ui/public/gate.js +2 -2
  68. package/modules/hall-ui/public/gate.states.json +1 -1
  69. package/modules/hall-ui/public/hall-render.js +43 -79
  70. package/modules/hall-ui/public/index.html +5 -1
  71. package/modules/hall-ui/public/modules.js +1 -0
  72. package/modules/hall-ui/public/oversight.css +5 -5
  73. package/modules/hall-ui/public/palette.js +0 -11
  74. package/modules/hall-ui/public/primer.js +1 -1
  75. package/modules/hall-ui/public/sessions.html +1 -1
  76. package/modules/hall-ui/public/settings-sessions.js +1 -1
  77. package/modules/hall-ui/public/settings.css +0 -1
  78. package/modules/hall-ui/public/settings.html +4 -33
  79. package/modules/hall-ui/public/settings.js +3 -33
  80. package/modules/hall-ui/public/settings.states.json +2 -2
  81. package/modules/hall-ui/public/studio.html +1 -1
  82. package/modules/hall-ui/public/style.css +10 -13
  83. package/modules/hall-ui/public/task.html +4 -1
  84. package/modules/hall-ui/public/task.js +36 -1
  85. package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
  86. package/modules/hall-ui/public/tweak-editor.css +16 -0
  87. package/modules/hall-ui/public/tweak-editor.html +26 -3
  88. package/modules/hall-ui/public/tweak-editor.js +32 -1
  89. package/modules/hall-ui/public/work.js +1 -1
  90. package/modules/hall-ui/records/tweak-editor.md +1 -0
  91. package/modules/ideas/module.json +1 -0
  92. package/modules/ideas/projection.js +1 -1
  93. package/modules/lifecycle/db-claim-reads.js +1 -58
  94. package/modules/lifecycle/db-ship.js +4 -9
  95. package/modules/lifecycle/db.js +0 -4
  96. package/modules/lifecycle/github-push.js +12 -14
  97. package/modules/lifecycle/kickoff-checklist.js +0 -4
  98. package/modules/lifecycle/lifecycle.js +0 -8
  99. package/modules/lifecycle/module.json +1 -0
  100. package/modules/lifecycle/publish-reconciler.js +10 -10
  101. package/modules/lifecycle/routes/tasks.js +13 -15
  102. package/modules/lifecycle/ship-card.js +1 -1
  103. package/modules/lifecycle/task-visuals.js +2 -2
  104. package/modules/memory/module.json +1 -0
  105. package/modules/memory/routes/memory.js +2 -2
  106. package/modules/npm-release/module.json +1 -0
  107. package/modules/onboarding/module.json +1 -0
  108. package/modules/onboarding/onboarding-state.js +17 -36
  109. package/modules/onboarding/routes/access-requests.js +3 -3
  110. package/modules/platform-identity/module.json +1 -0
  111. package/modules/platform-identity/platform-identity.js +13 -5
  112. package/modules/platform-identity/routes/sso.js +5 -4
  113. package/modules/platform-identity/tests/platform-identity.mjs +17 -3
  114. package/modules/provisioning/capacity.js +1 -1
  115. package/modules/provisioning/module.json +1 -0
  116. package/modules/provisioning/provisioning.js +6 -5
  117. package/modules/provisioning/routes/provisioning.js +4 -4
  118. package/modules/provisioning/starter-bundles.js +5 -25
  119. package/modules/public-landing/module.json +1 -0
  120. package/modules/public-landing/public/assets/cosmos.css +1 -1
  121. package/modules/public-landing/public/contact.html +1 -1
  122. package/modules/public-landing/public/privacy.html +1 -1
  123. package/modules/public-landing/public/projects.html +101 -32
  124. package/modules/public-landing/public/projects.probes.json +2 -2
  125. package/modules/public-landing/public/projects.states.json +4 -3
  126. package/modules/public-landing/public/terms.html +1 -1
  127. package/modules/security/module.json +1 -0
  128. package/modules/sessions/db.js +1 -1
  129. package/modules/sessions/module.json +1 -0
  130. package/modules/specialities/module.json +1 -0
  131. package/modules/status-ui/module.json +1 -0
  132. package/modules/ui-design/module.json +1 -0
  133. package/package-lock.json +2 -2
  134. package/package.json +1 -1
  135. package/release-notes.json +84 -0
  136. package/scripts/gds/artifact-format.js +203 -0
  137. package/scripts/gds/artifact-staleness.js +2 -2
  138. package/scripts/gds/audit-authorship.sh +1 -13
  139. package/scripts/gds/backfill-task-rewards.js +5 -5
  140. package/scripts/gds/claim.js +0 -15
  141. package/scripts/gds/claude-materialize.js +2 -2
  142. package/scripts/gds/cli-lib.js +15 -15
  143. package/scripts/gds/codemod-rename-src-gds.js +1 -1
  144. package/scripts/gds/context-pack.js +5 -7
  145. package/scripts/gds/diagram-facts.js +12 -23
  146. package/scripts/gds/do-api.js +13 -80
  147. package/scripts/gds/doc-cli-guard.js +2 -0
  148. package/scripts/gds/doctor.js +4 -4
  149. package/scripts/gds/feedback-latest.js +139 -9
  150. package/scripts/gds/fitness-checks-identity.js +2 -7
  151. package/scripts/gds/fitness-checks-packaging.js +4 -4
  152. package/scripts/gds/fitness-checks-write-validation.js +4 -0
  153. package/scripts/gds/fitness-ratchets.js +5 -5
  154. package/scripts/gds/fitness.js +10 -10
  155. package/scripts/gds/gen-api-client.js +4 -4
  156. package/scripts/gds/gen-api-docs.js +1 -1
  157. package/scripts/gds/gen-diagrams.js +49 -71
  158. package/scripts/gds/gen-repo-map.js +13 -14
  159. package/scripts/gds/gen-session-index.js +8 -8
  160. package/scripts/gds/http-api-client.js +12 -19
  161. package/scripts/gds/init.js +1 -1
  162. package/scripts/gds/leak-scan-allowlist.js +1 -1
  163. package/scripts/gds/lint-infra-exec.js +1 -1
  164. package/scripts/gds/local-preview-lib.js +8 -39
  165. package/scripts/gds/local-preview.js +2 -15
  166. package/scripts/gds/migration-namespace.js +3 -3
  167. package/scripts/gds/module-artifact.js +212 -0
  168. package/scripts/gds/module.js +95 -6
  169. package/scripts/gds/package-core.js +5 -116
  170. package/scripts/gds/plain-cards.js +1 -1
  171. package/scripts/gds/provision-config.js +2 -2
  172. package/scripts/gds/provision-net.js +2 -3
  173. package/scripts/gds/provision.js +9 -9
  174. package/scripts/gds/publish-manifest.js +1 -2
  175. package/scripts/gds/push-path-brief.js +10 -10
  176. package/scripts/gds/regen-instance-docs.js +1 -1
  177. package/scripts/gds/rename-history-check.js +1 -1
  178. package/scripts/gds/run-unit-tests.js +2 -16
  179. package/scripts/gds/sandbox-stage.js +39 -341
  180. package/scripts/gds/session-digest-build.js +2 -2
  181. package/scripts/gds/ship-deploy-target.js +13 -13
  182. package/scripts/gds/ship-flow.js +7 -7
  183. package/scripts/gds/ship-land.js +9 -10
  184. package/scripts/gds/ship-merge.js +4 -4
  185. package/scripts/gds/ship-preflight-steps.js +4 -4
  186. package/scripts/gds/ship-regen.js +15 -49
  187. package/scripts/gds/ship.js +13 -33
  188. package/scripts/gds/skill-preflight.js +9 -8
  189. package/scripts/gds/start.js +0 -15
  190. package/scripts/hall-preview/README.md +5 -5
  191. package/scripts/hall-preview/server.js +6 -6
  192. package/scripts/render-diagrams.sh +1 -1
  193. package/src/bongos/api-errors.js +1 -1
  194. package/src/bongos/api-prefix.js +4 -4
  195. package/src/bongos/auth-github.js +2 -3
  196. package/src/bongos/auth.js +51 -126
  197. package/src/bongos/db-kernel.js +2 -3
  198. package/src/bongos/db.js +5 -28
  199. package/src/bongos/module-scope-map.js +14 -29
  200. package/src/bongos/module-store.js +141 -0
  201. package/src/bongos/platform-visibility-gate.js +1 -1
  202. package/src/bongos/route-rank-check.js +3 -16
  203. package/src/bongos/routes/auth.js +0 -116
  204. package/src/bongos/routes/builders.js +5 -15
  205. package/src/bongos/routes/instance.js +8 -14
  206. package/src/bongos/routes/me.js +7 -23
  207. package/src/bongos/routes/modules.js +83 -0
  208. package/src/bongos/routes/security.js +3 -3
  209. package/src/bongos/routes.js +7 -2
  210. package/src/bongos/serve-internal.js +5 -6
  211. package/src/bongos-downloads.js +1 -6
  212. package/src/branding.js +2 -2
  213. package/src/build-info.js +4 -6
  214. package/src/instance-config.js +2 -2
  215. package/src/module-api.js +13 -6
  216. package/src/module-loader/catalog.js +3 -0
  217. package/src/module-loader/loader.js +4 -4
  218. package/src/module-loader/manifest-schema.js +16 -8
  219. package/src/module-seams.js +2 -3
  220. package/src/modules.js +42 -14
  221. package/tests/agents_spend_guard.mjs +1 -1
  222. package/tests/api_alias_caller_ratchet.mjs +1 -1
  223. package/tests/api_alias_exceptions.mjs +1 -1
  224. package/tests/api_client.mjs +1 -16
  225. package/tests/api_path_404.mjs +1 -1
  226. package/tests/auth_page_gate.mjs +10 -10
  227. package/tests/backfill_task_rewards.mjs +1 -1
  228. package/tests/canonical_profile_url.mjs +1 -3
  229. package/tests/claim_action.mjs +4 -1
  230. package/tests/claim_from_task_and_home.mjs +127 -0
  231. package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
  232. package/tests/cli_sessions.mjs +1 -1
  233. package/tests/consumer_layout_boot.mjs +2 -0
  234. package/tests/context_pack.mjs +13 -13
  235. package/tests/copy_desk_page_docx.mjs +33 -2
  236. package/tests/copy_inventory.mjs +7 -7
  237. package/tests/core_upgrade_runner.mjs +6 -6
  238. package/tests/credit_grant.mjs +2 -2
  239. package/tests/criterion_suggest.mjs +1 -1
  240. package/tests/deploy_divergence_line.mjs +11 -11
  241. package/tests/design_tokens_sync.mjs +3 -2
  242. package/tests/diagram_facts_offset.mjs +1 -1
  243. package/tests/do_api.mjs +106 -0
  244. package/tests/feedback_latest.mjs +122 -0
  245. package/tests/fitness.mjs +44 -42
  246. package/tests/fitness_ratchets.mjs +2 -2
  247. package/tests/gate_approvals.mjs +0 -1
  248. package/tests/github_push_land_proof.mjs +1 -1
  249. package/tests/go_live.mjs +3 -3
  250. package/tests/goal_advisory.mjs +3 -3
  251. package/tests/goal_suggest.mjs +3 -3
  252. package/tests/government_abuse_matrix.mjs +21 -19
  253. package/tests/government_ownership_scope.mjs +33 -35
  254. package/tests/government_parity.mjs +2 -2
  255. package/tests/government_protected_surfaces.mjs +56 -37
  256. package/tests/government_require_permission.mjs +3 -3
  257. package/tests/government_seed.mjs +37 -4
  258. package/tests/government_session_scope.mjs +92 -366
  259. package/tests/hall_audit.mjs +1 -1
  260. package/tests/hall_error_envelope.mjs +4 -0
  261. package/tests/hall_landing_boot.mjs +4 -1
  262. package/tests/hall_palette.mjs +1 -16
  263. package/tests/hall_record_world.mjs +6 -0
  264. package/tests/hall_tweak_editor.mjs +52 -1
  265. package/tests/helpers.mjs +1 -1
  266. package/tests/host_topology_skips.mjs +3 -4
  267. package/tests/html_comment_nesting.mjs +137 -0
  268. package/tests/infra_exec_paths.mjs +9 -10
  269. package/tests/init.mjs +4 -5
  270. package/tests/instance_manifest.mjs +18 -25
  271. package/tests/kickoff_checklist.mjs +1 -1
  272. package/tests/lib_sh_resolution.mjs +0 -221
  273. package/tests/local_preview.mjs +0 -12
  274. package/tests/migration_namespace.mjs +6 -6
  275. package/tests/module-scope-map.mjs +9 -9
  276. package/tests/module_api.mjs +2 -2
  277. package/tests/module_catalog.mjs +1 -1
  278. package/tests/module_cli.mjs +20 -20
  279. package/tests/module_contributions.mjs +6 -6
  280. package/tests/module_loader.mjs +6 -6
  281. package/tests/module_manifest.mjs +26 -17
  282. package/tests/module_route_rank.mjs +2 -2
  283. package/tests/module_store_publish.mjs +307 -0
  284. package/tests/module_store_publish_route.mjs +121 -0
  285. package/tests/module_store_registry_migration.mjs +70 -0
  286. package/tests/modules.mjs +56 -26
  287. package/tests/modules_route_wiring.mjs +1 -1
  288. package/tests/onboarding_route_signals.mjs +21 -31
  289. package/tests/onboarding_state.mjs +35 -49
  290. package/tests/paste_token_session_store.mjs +1 -1
  291. package/tests/permission_path.mjs +0 -3
  292. package/tests/platform_boot.mjs +1 -2
  293. package/tests/platform_visibility_gate.mjs +1 -1
  294. package/tests/profile_route.mjs +1 -6
  295. package/tests/project_modules_ui.mjs +17 -17
  296. package/tests/projects_hub.mjs +5 -5
  297. package/tests/projects_hub_app_step.mjs +138 -0
  298. package/tests/projects_hub_module_picker.mjs +77 -76
  299. package/tests/projects_hub_pre_uat.mjs +4 -5
  300. package/tests/provision.mjs +7 -7
  301. package/tests/provisioning_capacity.mjs +1 -1
  302. package/tests/provisioning_recommendations.mjs +2 -1
  303. package/tests/provisioning_starter_bundles.mjs +0 -22
  304. package/tests/publish_branch_route.mjs +11 -19
  305. package/tests/publish_manifest.mjs +2 -3
  306. package/tests/publish_reconciler.mjs +8 -8
  307. package/tests/push_path_brief.mjs +10 -9
  308. package/tests/rank_tier_single_source.mjs +1 -13
  309. package/tests/regrade_eligibility.mjs +1 -1
  310. package/tests/rename_history_restraint.mjs +1 -1
  311. package/tests/repo_map.mjs +12 -13
  312. package/tests/runner_drift.mjs +3 -21
  313. package/tests/sandbox_stage.mjs +57 -390
  314. package/tests/seam_wiring_guard.mjs +11 -11
  315. package/tests/search_isolation.mjs +3 -3
  316. package/tests/session_records.mjs +1 -1
  317. package/tests/session_rename_fallback.mjs +1 -22
  318. package/tests/session_start_freshness.mjs +3 -3
  319. package/tests/session_token_hash_db.mjs +1 -1
  320. package/tests/ship_card.mjs +1 -1
  321. package/tests/ship_ci_deploy.mjs +17 -17
  322. package/tests/ship_error_shape.mjs +1 -1
  323. package/tests/ship_premerge.mjs +1 -1
  324. package/tests/ship_resume.mjs +1 -1
  325. package/tests/skill_preflight.mjs +2 -2
  326. package/tests/skip_is_not_pass.mjs +1 -1
  327. package/tests/task_visual_slots.mjs +31 -0
  328. package/tests/terms_acceptance.mjs +2 -2
  329. package/tests/upgrade.mjs +4 -4
  330. package/tests/watch_sealed_floor.mjs +0 -1
  331. package/tests/wizard_draft_resume.mjs +2 -2
  332. package/tests/wizard_intent_resume.mjs +21 -10
  333. package/tests/wizard_preselect_why.mjs +18 -18
  334. package/docs/onboarding/browser-terminal-guide.md +0 -73
  335. package/docs/recipes/managed-settings-remote-control.md +0 -416
  336. package/modules/dev-box/CLAUDE.md +0 -15
  337. package/modules/dev-box/box-access.js +0 -623
  338. package/modules/dev-box/box-credential.js +0 -94
  339. package/modules/dev-box/box-onboard.js +0 -486
  340. package/modules/dev-box/boxes.js +0 -1038
  341. package/modules/dev-box/db.js +0 -39
  342. package/modules/dev-box/module.json +0 -19
  343. package/modules/dev-box/routes/box.js +0 -1223
  344. package/scripts/gds/box-auth-check.js +0 -160
  345. package/scripts/gds/box-infra.js +0 -321
  346. package/scripts/gds/box-sync.js +0 -216
  347. package/scripts/gds/box.js +0 -1332
  348. package/scripts/gds/cf-tunnel.js +0 -558
  349. package/scripts/gds/smoke-box.sh +0 -150
  350. package/scripts/gds/tree-preflight.js +0 -241
  351. package/src/bongos/app-pair.js +0 -172
  352. package/tests/app_pair.mjs +0 -168
  353. package/tests/box_access.mjs +0 -578
  354. package/tests/box_auth_check.mjs +0 -114
  355. package/tests/box_code_staleness.mjs +0 -129
  356. package/tests/box_connect_e2e.mjs +0 -196
  357. package/tests/box_cost.mjs +0 -70
  358. package/tests/box_credential.mjs +0 -125
  359. package/tests/box_dns_repoint.mjs +0 -153
  360. package/tests/box_env.mjs +0 -64
  361. package/tests/box_host_keys.mjs +0 -126
  362. package/tests/box_infra_resolve.mjs +0 -246
  363. package/tests/box_onboard.mjs +0 -317
  364. package/tests/box_scope_session.mjs +0 -119
  365. package/tests/box_sweep_docs.mjs +0 -119
  366. package/tests/box_sync.mjs +0 -55
  367. package/tests/box_sync_scope_report.mjs +0 -126
  368. package/tests/box_task_scope.mjs +0 -169
  369. package/tests/box_task_scope_pause.mjs +0 -66
  370. package/tests/box_terminal_ssrf.mjs +0 -96
  371. package/tests/boxes.mjs +0 -1585
  372. package/tests/cf_ruleset_preflight.mjs +0 -142
  373. package/tests/cf_tunnel.mjs +0 -496
  374. package/tests/docs_scrubber_damage.mjs +0 -111
  375. package/tests/module_scope_active_claims.mjs +0 -94
  376. package/tests/ship_api_client_pathspec.mjs +0 -100
  377. package/tests/tree_preflight.mjs +0 -243
@@ -40,7 +40,7 @@ const LEGACY_DIRS = ['otb'];
40
40
  //
41
41
  // WHY THESE MUST OUTLIVE THIS TASK: these names are set OUTSIDE the repo —
42
42
  // /etc/cloudbongos/web.env on the droplet, systemd units, CI secrets, shipped
43
- // Dev Box binaries, live dev boxes. Deleting a spelling here is an outage the
43
+ // CLI binaries, builders' own machines. Deleting a spelling here is an outage the
44
44
  // next time one of those restarts. Task 1003706 (C3d) retires them deliberately,
45
45
  // after evidence — and asserts their ABSENCE, which is why the fallback needs a
46
46
  // test that it FIRES rather than one that merely tolerates it.
@@ -140,7 +140,7 @@ function configPath(name, b = safeBrand()) {
140
140
  // Lives here, not in cli-lib.js which owns the session STORE, because this module
141
141
  // is the lowest one that has to read the file (resolveApiBase below) and cli-lib
142
142
  // imports this — so this is the only place both can share. A rename APPENDS a
143
- // name; dropping one signs out every builder machine and every live dev box at
143
+ // name; dropping one signs out every builder machine at
144
144
  // once. Part 4 (task 1003706) retires the tail. (task 1003704)
145
145
  const SESSION_FILENAMES = Object.freeze([
146
146
  'bongos-session.json',
package/src/module-api.js CHANGED
@@ -71,10 +71,14 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.1073'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ // The body behind a retired doorway export that must stay callable until the next
75
+ // MAJOR (see allowBoxScope below): passes the request through untouched.
76
+ function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
+
78
+ const CORE_VERSION = '1.19.1075'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
79
 
76
80
  // A namespaced logger so a module's log lines are attributable + consistent.
77
- // Usage: const log = api.logger('dev-box'); log.info('mounted');
81
+ // Usage: const log = api.logger('discord'); log.info('mounted');
78
82
  // task 1003208 (goal 1000079): this USED TO BE `console.log(tag, ...a)`. Every
79
83
  // module that had "adopted the logger" was therefore still writing unstructured
80
84
  // text to stdout with no request correlation and no redaction — the criterion's
@@ -131,10 +135,13 @@ module.exports = {
131
135
  get requirePermission() { return require('./bongos/auth').requirePermission; },
132
136
  get rankMeetsThreshold() { return require('./bongos/auth').rankMeetsThreshold; },
133
137
  get requireBfgPrincipal() { return require('./bongos/auth').requireBfgPrincipal; },
134
- // allowBoxScope — opt a route in to box-scoped (dev-box bootstrap) session
135
- // tokens. A module's own bootstrap routes flag themselves; everything else
136
- // stays closed to box tokens (the ADR 0016 trust boundary).
137
- get allowBoxScope() { return require('./bongos/auth').allowBoxScope; },
138
+ // allowBoxScope — DEPRECATED no-op, kept so the doorway loses no export inside
139
+ // 1.x (removing one is a MAJOR, and every module declares ^1). It used to opt
140
+ // a route in to the narrowing machine-baked session scope; that scope and its
141
+ // tokens were retired (goal 1000120, task 1003894), so an undeclared session source is now denied
142
+ // on every route whatever a route mounts. Passing it changes nothing. Drop it
143
+ // at the next MAJOR.
144
+ get allowBoxScope() { return deprecatedNoopMiddleware; },
138
145
  // resolveSession(req) — resolve the request to a live session row (or null)
139
146
  // WITHOUT the 401-or-pass behaviour of requireBuilder. A module route that
140
147
  // must make its own auth decision — e.g. a browser page that REDIRECTS to
@@ -125,6 +125,9 @@ function buildCatalog({
125
125
  provenance: deriveProvenance(manifest.origin, ownProjectName),
126
126
  enabled: enabled[key] === true,
127
127
  default: manifest.default === true,
128
+ // The module's own version (task 1003781) — null only for a manifest
129
+ // that predates the field (the validator now requires it).
130
+ version: manifest.version || null,
128
131
  coreVersion: manifest.coreVersion || null,
129
132
  contributes: manifest.contributes || {},
130
133
  // DECLARED — exactly what module.json says (null when it says nothing).
@@ -7,7 +7,7 @@
7
7
  // routes.js mounts discovered module routers behind the kernel's auth/audit.
8
8
  //
9
9
  // SAFE-BY-CONSTRUCTION during the strangler migration: with no modules/ dir
10
- // (the state until BV1.R43 moves dev-box), discovery returns EMPTY and every
10
+ // (the state before BV1.R43 moved the first module), discovery returns EMPTY and every
11
11
  // function below is a no-op — OTB behaves byte-identically. As built-ins move
12
12
  // out, they become discovered manifests and the closed registry shrinks.
13
13
  //
@@ -173,7 +173,7 @@ function verifyEnabledPorts(enabled) {
173
173
  }
174
174
 
175
175
  // Mount each discovered + ENABLED module's route factories onto `router`. A
176
- // module declares contributes.routes:["box"] → factory at modules/<key>/routes/box.js
176
+ // module declares contributes.routes:["copy-desk"] → factory at modules/<key>/routes/copy-desk.js
177
177
  // exporting a function returning an express Router. Auth model: audit is composed
178
178
  // at the kernel level in routes.js before this is called; each route self-gates at
179
179
  // its required rank via api.requireBuilder / api.requireRank (public read routes
@@ -309,8 +309,8 @@ function startModulePollers(deps = {}, { isEnabled } = {}, opts) {
309
309
  poller = require(pollerPath);
310
310
  } catch (e) {
311
311
  // No modules/<key>/pollers/<pk>.js → a DECLARATIVE-ONLY / non-web poller
312
- // (e.g. dev-box's box-lifecycle sweep runs on the control plane, not in
313
- // this process). Skip silently — only a web-process poller ships a file
312
+ // (e.g. a sweep that runs on the control plane, not in this process).
313
+ // Skip silently — only a web-process poller ships a file
314
314
  // here. A file that EXISTS but throws is a real error (the catch below).
315
315
  if (e && e.code === 'MODULE_NOT_FOUND' && String(e.message).includes(pollerPath)) continue;
316
316
  console.error(`[module-loader] poller ${key}/${pk} failed to load: ${e && e.message ? e.message : e}`);
@@ -9,9 +9,11 @@
9
9
  //
10
10
  // THE SCHEMA (module.json):
11
11
  // {
12
- // "key": "dev-box", // REQUIRED. ^[a-z][a-z0-9-]*$ ; must match the module's dir name.
13
- // "title": "Dev boxes", // REQUIRED. Human label.
12
+ // "key": "art-pipeline", // REQUIRED. ^[a-z][a-z0-9-]*$ ; must match the module's dir name.
13
+ // "title": "Art pipeline", // REQUIRED. Human label.
14
14
  // "description": "…", // REQUIRED. One sentence.
15
+ // "version": "1.4.2", // REQUIRED. The module's OWN exact version "X.Y.Z" (task 1003781) —
16
+ // // what update, rollout channels and re-assessment name.
15
17
  // "coreVersion": "^1.0.0", // REQUIRED. semver range of src/module-api.js CORE_VERSION it supports.
16
18
  // "default": false, // OPTIONAL (default false). Enablement fail-safe when no config names it.
17
19
  // "author": "…", // OPTIONAL. Attribution (ADR 0107 §2) — portable, not a cross-GDS FK.
@@ -28,12 +30,12 @@
28
30
  // // nobody does. `maintained` needs a named `maintainer`;
29
31
  // // `deprecated`/`orphaned` need a `successor` or a `note`.
30
32
  // "contributes": { // REQUIRED object (may be empty). What the module mounts:
31
- // "routes": ["box"], // route-factory keys (mounted behind kernel-composed auth)
33
+ // "routes": ["stage"], // route-factory keys (mounted behind kernel-composed auth)
32
34
  // "rooms": ["WorldRoom"], // Colyseus room classes (realtime seam)
33
- // "pollers": ["box-lifecycle"],// background pollers to start/stop
35
+ // "pollers": ["nightly-sweep"],// background pollers to start/stop
34
36
  // "disciplines": ["artist"], // disciplines this module adds
35
37
  // "skills": ["otb-…"], // slash-command skills it ships
36
- // "uiSections": ["boxes"], // hall/status UI sections it owns
38
+ // "uiSections": ["gallery"], // hall/status UI sections it owns
37
39
  // "webSurfaces": [{ "host": "status.", "dir": "public" }], // static web surfaces. Each names a Host
38
40
  // // PREFIX (BV1.R84) or, as { "apexRoot": true, "dir": "public" },
39
41
  // // the APEX ROOT — never both (ADR 0218).
@@ -46,7 +48,7 @@
46
48
  // // refuses the enablement when the join door is `open` and no
47
49
  // // payer is configured.
48
50
  // "declarativeSeams": { // OPTIONAL. Per-entry exemptions for the seam-wiring
49
- // "pollers": ["box-lifecycle"] // guard (task 1003819): "this contributes entry ships no
51
+ // "pollers": ["nightly-sweep"] // guard (task 1003819): "this contributes entry ships no
50
52
  // }, // artifact ON PURPOSE". Every key must be a key the module
51
53
  // // actually declares in `contributes`, and every entry must be
52
54
  // // one it actually lists there — a typo'd exemption is an error,
@@ -162,6 +164,12 @@ function validateManifest(obj) {
162
164
  if (!isStr(obj.title)) E('title: required non-empty string');
163
165
  if (!isStr(obj.description)) E('description: required non-empty string');
164
166
 
167
+ // The module's own version (task 1003781) — EXACT, unlike coreVersion's range:
168
+ // it names one published build, so update parity, rollout channels and
169
+ // re-assessment on publish (ADR 0338/0344) all have something to point at.
170
+ if (!isStr(obj.version)) E('version: required exact semver string "X.Y.Z" — the module\'s own version, e.g. "1.0.0"');
171
+ else if (!parseVersion(obj.version)) E(`version: "${obj.version}" must be an exact version "X.Y.Z" (not a range)`);
172
+
165
173
  if (!isStr(obj.coreVersion)) E('coreVersion: required non-empty semver range string (e.g. "^1.0.0")');
166
174
  else if (!isValidRange(obj.coreVersion)) E(`coreVersion: "${obj.coreVersion}" is not a valid semver range`);
167
175
 
@@ -239,8 +247,8 @@ function validateManifest(obj) {
239
247
  // --- optional declarativeSeams (task 1003819) ---
240
248
  // The exemption list for the seam-wiring guard: "this contributes entry ships no
241
249
  // artifact on purpose". Some declarations are legitimately declarative-only —
242
- // dev-box's `box-lifecycle` poller runs on the control plane, not in this process,
243
- // and its `boxes` uiSection ships no hall widget — so the guard needs a way to say
250
+ // a poller that runs on the control plane, not in this process, or a uiSection
251
+ // that ships no hall widget — so the guard needs a way to say
244
252
  // so without a blanket skip that would hide the real cases it exists to catch.
245
253
  //
246
254
  // Validated HERE, against `contributes`, because this is the one place that holds
@@ -179,9 +179,8 @@ function listEvents() {
179
179
  // fire-and-forget reaction; a CONTRIBUTION accumulates — zero-or-more modules
180
180
  // each add a piece to a named extension point, and the kernel hands the whole
181
181
  // set back to a core consumer that renders them together. This is how a module
182
- // adds its own slice to a core surface without core hardcoding it: dev-box adds
183
- // box fields + a box settings panel, discord adds its link state, art adds its
184
- // key surface — each `contribute()`s; core merges. The strangler prerequisite
182
+ // adds its own slice to a core surface without core hardcoding it: discord adds
183
+ // its link state, art adds its key surface — each `contribute()`s; core merges. The strangler prerequisite
185
184
  // that lets those modules leave core (the surfaces in src/bongos/routes/me.js +
186
185
  // modules/hall-ui/public/settings.js stop being hardcoded). With no module enabled the
187
186
  // registry is empty and every consumer renders exactly what it does today.
package/src/modules.js CHANGED
@@ -18,8 +18,8 @@
18
18
  //
19
19
  // This is a config-flag exclusion mechanism, NOT a full plugin registry — the
20
20
  // cheaper near-term shape (ADR 0062 §4). R59 (task 1198) moves game + art-pipeline
21
- // in; R60 (task 1199) moves discord + dev-box in (UI sections / skills / pollers);
22
- // they consume the registry declared here.
21
+ // in; R60 (task 1199) moves discord in (UI sections / skills / pollers); they
22
+ // consume the registry declared here.
23
23
 
24
24
  const fs = require('node:fs');
25
25
  const path = require('node:path');
@@ -45,9 +45,9 @@ const INSTANCE_PATH = path.join(resolveInstanceRoot(), 'config', 'modules.json')
45
45
  // CORE (always on, never a module): auth, builders, tasks, claims, versions,
46
46
  // done-when, credits/cost, inbox, blockers, leaderboard, analytics,
47
47
  // audit-log, security, access-requests, sessions, gate-approvals, healthz,
48
- // public. These mount unconditionally. The four optional feature modules AND the
49
- // first TWO carved CORE-domain modules have graduated to modules/<key>/ (dev-box
50
- // BV1.R43, discord BV1.R47, art-pipeline BV1.R48, game BV1.R49, memory BV1.R72 —
48
+ // public. These mount unconditionally. The optional feature modules AND the
49
+ // first TWO carved CORE-domain modules have graduated to modules/<key>/ (discord
50
+ // BV1.R47, art-pipeline BV1.R48, game BV1.R49, memory BV1.R72 —
51
51
  // memory/recall/search/learnings, ADR 0091 §2 — and grading BV1.R73 — the
52
52
  // adversarial grade panel + rubric, behind the `grade` kernel port, ADR 0091 §4).
53
53
  // A core-domain module like memory or grading is `default: true` (it ships to
@@ -166,6 +166,27 @@ function spendRefusals(resolved, registry, { joinDoorOpen = false, payer = null
166
166
  // something they explicitly asked for, so it has to say what to do about it.
167
167
  // `log` is injectable for the same reason instance-config's legacy-env warning
168
168
  // injects it: a test asserts the warning fired, not only that the value resolved.
169
+ // RETIRED MODULE KEYS (goal 1000120, task 1003892). A module deleted from the core
170
+ // outright, which an instance's committed config/modules.json — written while the
171
+ // module still shipped — may still switch ON. fromConfig below throws on enabling
172
+ // an unknown module (the task-1824 typo guard), so without this a core upgrade
173
+ // would become a boot failure on every instance that ever enabled one:
174
+ // cloudbongos.com's own config still said `"dev-box": true` when the module went.
175
+ // A retired key is DROPPED with a one-line warning instead of thrown on. Keep the
176
+ // list short and say why each key left — an entry is a promise about someone
177
+ // else's config file, not a way to silence a typo.
178
+ const RETIRED_MODULE_KEYS = Object.freeze({
179
+ 'dev-box': 'cloud dev boxes were removed from the core (goal 1000120)',
180
+ });
181
+
182
+ function warnRetiredModule(key, source, log = console) {
183
+ log.warn(
184
+ `[bongos] module "${key}" is switched on in the ${source} config, but it was retired: `
185
+ + `${RETIRED_MODULE_KEYS[key]}. It is ignored — remove the key from that config to `
186
+ + 'silence this line.',
187
+ );
188
+ }
189
+
169
190
  function warnSpendRefusal(key, envPrefix, log = console) {
170
191
  log.warn(
171
192
  `[bongos] module "${key}" is enabled in config but was REFUSED: it spends money on behalf of `
@@ -185,7 +206,7 @@ function resolveModules({
185
206
  // caller that does not know about spend resolves exactly as before; loadModules
186
207
  // is the one that reads the real posture, and the one that fails closed when it
187
208
  // cannot.
188
- spendGuard = NO_SPEND_RISK, warn = warnSpendRefusal,
209
+ spendGuard = NO_SPEND_RISK, warn = warnSpendRefusal, warnRetired = warnRetiredModule,
189
210
  // THE DB OVERRIDE LAYER (task 1002857) — a { <moduleKey>: boolean } snapshot of
190
211
  // module_enablement_overrides for this scope, injected rather than read, so this
191
212
  // resolver stays pure and synchronous. Defaulted to {} so every existing caller
@@ -207,13 +228,19 @@ function resolveModules({
207
228
  for (const [k, v] of Object.entries(m)) {
208
229
  if (known.includes(k)) { out[k] = v; continue; }
209
230
  // Unknown key. A config may name a module this build hasn't installed: a stripped
210
- // CORE has no game/art-pipeline/discord/dev-box, yet config/modules.neutral.json
211
- // (which ships WITH the core) names them so the SAME file is valid in the full repo
212
- // too. Tolerate an unknown module ONLY when it is DISABLED — a disabled-and-absent
213
- // module is a no-op. ENABLING an absent module is a real error (the feature you asked
214
- // for isn't here) and still throws, which also keeps the typo guard for the dangerous
215
- // direction. (task 1824: this is what lets the vanilla core boot instead of crashing
216
- // on `unknown module "game"`.)
231
+ // CORE has no game/art-pipeline/discord, yet config/modules.neutral.json (which
232
+ // ships WITH the core) names them so the SAME file is valid in the full repo too.
233
+ // Tolerate an unknown module ONLY when it is DISABLED — a disabled-and-absent
234
+ // module is a no-op. ENABLING an absent module is a real error (the feature you
235
+ // asked for isn't here) and still throws, which also keeps the typo guard for the
236
+ // dangerous direction. (task 1824: this is what lets the vanilla core boot instead
237
+ // of crashing on `unknown module "game"`.) The one exception is a RETIRED key
238
+ // (RETIRED_MODULE_KEYS above): it can never be installed again, so enabling it is
239
+ // a stale config line, not a request — warn and drop it.
240
+ if (coerceBool(v) === true && Object.prototype.hasOwnProperty.call(RETIRED_MODULE_KEYS, k)) {
241
+ warnRetired(k, source);
242
+ continue;
243
+ }
217
244
  if (coerceBool(v) === true) {
218
245
  throw new Error(`modules: cannot enable unknown module "${k}" in ${source} config — it is not installed (known: ${known.join(', ') || '(none)'}).`);
219
246
  }
@@ -257,7 +284,7 @@ function resolveModules({
257
284
  const merged = Object.assign({}, ...layers.map(([, m]) => m));
258
285
 
259
286
  // Env overrides: <PREFIX>_MODULE_<KEY> where KEY is upper-snake of the module
260
- // name (dev-box -> DEV_BOX). Highest precedence.
287
+ // name (art-pipeline -> ART_PIPELINE). Highest precedence.
261
288
  for (const key of known) {
262
289
  const suffix = `MODULE_${key.toUpperCase().replace(/-/g, '_')}`;
263
290
  const envName = `${envPrefix}_${suffix}`;
@@ -489,6 +516,7 @@ module.exports = {
489
516
  metersSpend,
490
517
  spendRefusals,
491
518
  warnSpendRefusal,
519
+ RETIRED_MODULE_KEYS,
492
520
  resolveModules,
493
521
  resolveInstancePackPath,
494
522
  moduleScopeKey,
@@ -122,7 +122,7 @@ test('spendRefusals never names a module that was already off', () => {
122
122
  });
123
123
 
124
124
  test('the manifest declaration is a CLOSED key set — a typo must not read as "no spend"', () => {
125
- const base = { key: 'x', title: 't', description: 'd', coreVersion: '^1.0.0', contributes: {} };
125
+ const base = { key: 'x', title: 't', description: 'd', version: '1.0.0', coreVersion: '^1.0.0', contributes: {} };
126
126
  assert.equal(validateManifest({ ...base, spend: { requiresPayer: true } }).valid, true);
127
127
  assert.equal(validateManifest({ ...base, spend: { requirespayer: true } }).valid, false,
128
128
  'a misspelled key silently declaring NO spend is the direction that costs money');
@@ -66,7 +66,7 @@ test('the exclusions are real and each one is reachable', () => {
66
66
  'scripts/gds/fitness-ratchets.js',
67
67
  'scripts/gds/api-alias-caller-scan.js',
68
68
  'tests/api_alias_exceptions.mjs', // 5 tests
69
- 'modules/dev-box/app/test/thing.mjs',
69
+ 'modules/agents/app/test/thing.mjs',
70
70
  'docs/adr/0016-permissions.md', // 6 the FROZEN RECORD under docs/ (narrowed, task 1004113)
71
71
  'docs/session-logs/2026-09-16-x.md',
72
72
  'docs/audits/2026-08-01-x.md',
@@ -27,7 +27,7 @@ const ALIAS = '/api/gds';
27
27
  test('the alias is still mounted — everything below depends on it', () => {
28
28
  const { API_PREFIX, LEGACY_API_PREFIXES, ALL_API_PREFIXES } = require(path.join(ROOT, 'src', 'bongos', 'api-prefix.js'));
29
29
  assert.equal(API_PREFIX, '/api/bongos', 'the canonical prefix is what in-repo callers now ask for');
30
- assert.ok(LEGACY_API_PREFIXES.includes(ALIAS), 'shipped Dev Box binaries and live dev boxes call the alias');
30
+ assert.ok(LEGACY_API_PREFIXES.includes(ALIAS), 'shipped binaries and live instances call the alias');
31
31
  assert.ok(ALL_API_PREFIXES.includes(ALIAS), 'and the router still mounts it');
32
32
  });
33
33
 
@@ -128,21 +128,6 @@ test('every method the README example names actually exists on the client', () =
128
128
  assert.ok(count >= 3, 'README example should exercise several methods');
129
129
  });
130
130
 
131
- test('devbox vendored client is byte-identical to the canonical CJS build (run devbox-app vendor if stale)', () => {
132
- // The devbox Electron app can't require() clients/ at runtime (packaged
133
- // separately, ADR 0045), so scripts/vendor-client.js copies index.cjs into
134
- // devbox-app/src/vendor/. Guard that the vendored copy never drifts from the
135
- // canonical build — CI catches a forgotten `npm run vendor` after a spec change.
136
- // devbox-app/ is instance content the neutral core does not carry (task 1002470).
137
- // Absent APP => nothing to vendor into; absent VENDORED FILE inside a present app
138
- // is the real "forgot to run vendor" drift this guards, and still fails.
139
- if (!fs.existsSync(path.join(ROOT, 'devbox-app'))) return;
140
- const vendored = path.join(ROOT, 'devbox-app/src/vendor/bongos-client.cjs');
141
- if (!fs.existsSync(vendored)) throw new assert.AssertionError({ message: 'devbox-app/src/vendor/bongos-client.cjs missing — run `npm run vendor` in devbox-app/' });
142
- const canonical = fs.readFileSync(path.join(ROOT, 'clients/bongos-client/index.cjs'), 'utf8');
143
- assert.equal(fs.readFileSync(vendored, 'utf8'), canonical, 'devbox vendored client is STALE — run `npm run vendor` in devbox-app/');
144
- });
145
-
146
131
  test('baseUrl:"" is honored (absolute-path passthrough for the hall fetch wrappers)', async () => {
147
132
  const rec = {};
148
133
  const api = createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false, fetch: fakeFetch(rec, { ok: true, status: 200, body: { x: 1 } }) });
@@ -223,7 +208,7 @@ test('generated .d.ts types success responses (task 2061)', () => {
223
208
  });
224
209
 
225
210
  // The CJS + browser-global builds are pure transforms of the ESM source (task
226
- // 1996 / R09). devbox (require) and classic <script> web pages consume them, so
211
+ // 1996 / R09). CommonJS callers (require) and classic <script> web pages consume them, so
227
212
  // guard that they load and shape requests IDENTICALLY to the ESM build.
228
213
  test('CJS build behaves identically to the ESM build', async () => {
229
214
  const cjs = require('../clients/bongos-client/index.cjs');
@@ -38,7 +38,7 @@ t('API-shaped: /api/*, /gds/*, and bare GDS roots (/tasks, /me, …) match', ()
38
38
 
39
39
  t('NOT API-shaped: game/hall/static paths fall through to HTML/static', () => {
40
40
  for (const p of ['/', '/index.html', '/builders', '/builders/work', '/atlas-ab12.png',
41
- '/vendor/phaser.min.js', '/downloads/devbox/mac', '/game', '/healthz', '/version']) {
41
+ '/vendor/phaser.min.js', '/downloads/bongos/latest.json', '/game', '/healthz', '/version']) {
42
42
  assert.equal(isApiShapedPath(p), false, `${p} should NOT be API-shaped`);
43
43
  }
44
44
  });
@@ -99,18 +99,18 @@ await t('session lookup error → fails CLOSED (redirect, next() NOT called)', a
99
99
  'must redirect to sign-in on a lookup error (fail closed)');
100
100
  });
101
101
 
102
- // ---- SESSION-SCOPE on the page path (task 1003197 (A)) ----------------------
102
+ // ---- SESSION SOURCE on the page path (task 1003197 (A)) ---------------------
103
103
  //
104
104
  // resolveSession accepts a Bearer token with no source filter, so before this the
105
- // page gate never asked the question requireBuilder asks: is a page inside this
106
- // SESSION's scope at all? A box-baked token (which lives on the box, outside the
107
- // ADR 0016 trust boundary) could therefore fetch a gated page SHELL whenever its
108
- // builder's rank held the page permission. The stub above resolves the permission
109
- // for EVERY builder, which is what makes these assertions about scope alone.
105
+ // page gate never asked the question requireBuilder asks: does the session-source
106
+ // registry declare this session's source at all? Without it, a token of an
107
+ // undeclared source could fetch a gated page SHELL whenever its builder's rank held
108
+ // the page permission. The stub above resolves the permission for EVERY builder,
109
+ // which is what makes these assertions about the source alone.
110
110
 
111
111
  console.log('\nrequireBuilderPage — session scope:');
112
112
 
113
- await t('box-scoped session → 302 to sign-in even though its builder holds the permission', async () => {
113
+ await t('a leftover dev-box session (source box, now undeclared) → 302 to sign-in even though its builder holds the permission', async () => {
114
114
  nextSession = { builder_id: 3, github_login: 'boxbot', rank: 'archon', status: 'active', source: 'box' };
115
115
  lookupThrows = false;
116
116
  const req = { headers: { authorization: 'Bearer boxtoken' } }; // a page route declares no scope
@@ -145,9 +145,9 @@ await t('cli and web sessions are untouched by the scope check', async () => {
145
145
  });
146
146
 
147
147
  await t('a legacy session with no source field still reaches the page', async () => {
148
- // The same invariant tests/box_scope_session.mjs pins for requireBuilder: absent
149
- // source resolves to the UNSCOPED scope, so this gate can never lock out a real
150
- // browser session minted before `source` was recorded.
148
+ // The same invariant tests/government_session_scope.mjs pins for requireBuilder:
149
+ // an absent source is the declared legacy row, so this gate can never lock out a
150
+ // real browser session minted before `source` was recorded.
151
151
  nextSession = { builder_id: 2, github_login: 'legacy', rank: 'metic', status: 'active' };
152
152
  const req = { headers: { cookie: 'gds_session=validtoken' } };
153
153
  const res = fakeRes();
@@ -77,7 +77,7 @@ await test('computeTaskReward: 0 raw → 0 effective (a 0-mapping pays nothing)'
77
77
 
78
78
  await test('netForBuilder: would-be minus already-paid', () => {
79
79
  assert.equal(netForBuilder(100, 30), 70);
80
- assert.equal(netForBuilder(100, 0), 100); // cost-plus paid nothing (dev-box/CI case)
80
+ assert.equal(netForBuilder(100, 0), 100); // cost-plus paid nothing (the CI case)
81
81
  });
82
82
 
83
83
  await test('netForBuilder: cost-plus over-paid → floored at 0 (never claws back)', () => {
@@ -288,8 +288,6 @@ const BUILDER = {
288
288
  anthropic_user_id: 'SENTINEL-anthropic-user',
289
289
  monthly_budget_usd: 999,
290
290
  system_role: 'SENTINEL-system-role',
291
- box_blocked: true,
292
- box_blocked_reason: 'SENTINEL-box-blocked',
293
291
  deactivated_reason: 'SENTINEL-deactivated',
294
292
  };
295
293
 
@@ -380,7 +378,7 @@ test('widening the response did not widen the leak: no private column appears an
380
378
  // The raw serialized text, not the parsed keys — a private value smuggled
381
379
  // under a different key still fails.
382
380
  assert.doesNotMatch(r.text, /SENTINEL-/, 'a private builder column reached the public profile body');
383
- assert.doesNotMatch(r.text, /monthly_budget_usd|anthropic_|system_role|box_blocked|deactivated_reason/);
381
+ assert.doesNotMatch(r.text, /monthly_budget_usd|anthropic_|system_role|deactivated_reason/);
384
382
  assert.deepEqual(Object.keys(r.data.builder).sort(), [
385
383
  'avatar_url', 'created_at', 'display_name', 'github_login', 'id', 'karma',
386
384
  'preferred_disciplines', 'rank', 'total_credits',
@@ -242,7 +242,10 @@ await test('NOBODY posts to /claims except through the shared action', () => {
242
242
  // own claim POST, this fails — which is what "a new claim surface needs no new
243
243
  // refusal handling" has to mean in practice.
244
244
  const offenders = [];
245
- for (const f of ['work.js', 'goals-page.js', 'goals-render.js', 'task.js']) {
245
+ // task 1003459 added two more claim surfaces (task.js's Claim it button,
246
+ // builders.js/hall-render.js's Home Next-up row) — both belong in this scan
247
+ // or a future hand-rolled POST on either trips nothing.
248
+ for (const f of ['work.js', 'goals-page.js', 'goals-render.js', 'task.js', 'builders.js', 'hall-render.js']) {
246
249
  const src = read('modules', 'hall-ui', 'public', f);
247
250
  if (/postJSON\(\s*`\$\{API\}\/claims/.test(src)) offenders.push(f);
248
251
  }
@@ -0,0 +1,127 @@
1
+ // tests/claim_from_task_and_home.mjs
2
+ //
3
+ // Task 1003459 — the two surfaces that say "ready to claim" gain the ability to
4
+ // claim: the task's own /task/:id page, and Home's "Next up" rows. Both wire
5
+ // onto the extracted claim action (task 1003458, claim-action.js) rather than
6
+ // hand-rolling a third POST /claims + refusal switch — claim_action.mjs already
7
+ // proves the switch itself is complete; this file proves these two call sites
8
+ // actually use it.
9
+ //
10
+ // Static-source assertions, matching claim_action.mjs's own idiom for these
11
+ // files: task.js and builders.js are browser IIFEs that reach for `window` at
12
+ // eval time, so they are read as text rather than executed in Node.
13
+ //
14
+ // Run: node tests/claim_from_task_and_home.mjs
15
+
16
+ import { strict as assert } from 'node:assert';
17
+ import { readFileSync } from 'node:fs';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { dirname, join } from 'node:path';
20
+ import { makeRunner } from './helpers.mjs';
21
+
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ const repoRoot = join(__dirname, '..');
24
+ const read = (...p) => readFileSync(join(repoRoot, ...p), 'utf8');
25
+ const { test, summary } = makeRunner();
26
+
27
+ const taskJs = read('modules', 'hall-ui', 'public', 'task.js');
28
+ const buildersJs = read('modules', 'hall-ui', 'public', 'builders.js');
29
+ const hallRenderJs = read('modules', 'hall-ui', 'public', 'hall-render.js');
30
+ const taskHtml = read('modules', 'hall-ui', 'public', 'task.html');
31
+ const indexHtml = read('modules', 'hall-ui', 'public', 'index.html');
32
+
33
+ // == /task/:id ================================================================
34
+
35
+ await test('task.js constructs the shared claim action', () => {
36
+ assert.match(taskJs, /makeClaimAction\(\{/, 'task.js must build the action, not hand-roll one');
37
+ assert.match(taskJs, /window\.OTBClaimAction/);
38
+ });
39
+
40
+ await test('task.js renders a Claim button only when the task is ready', () => {
41
+ const m = taskJs.match(/if \(t\.status === 'ready'\) \{\s*lifecycle = `([^`]*)`/);
42
+ assert.ok(m, 'no ready-status branch found in the lifecycle chain');
43
+ assert.match(m[1], /id="claim-btn"/);
44
+ });
45
+
46
+ await test('the Claim button click routes through claimAction.claim, not a raw POST', () => {
47
+ const m = taskJs.match(/const claimBtn = \$\('#claim-btn'\);[\s\S]*?\n {4}\}/);
48
+ assert.ok(m, 'claim-btn wiring block not found');
49
+ assert.match(m[0], /claimAction\.claim\(t\.id\)/);
50
+ });
51
+
52
+ await test('task.js does not hand-roll a POST to /claims', () => {
53
+ assert.doesNotMatch(taskJs, /postJSON\(\s*`\$\{API\}\/claims/);
54
+ });
55
+
56
+ await test('task.html loads claim-action.js before task.js', () => {
57
+ const shared = taskHtml.indexOf('claim-action.js');
58
+ const after = taskHtml.indexOf('task.js?v=');
59
+ assert.ok(shared > -1, 'task.html must load claim-action.js');
60
+ assert.ok(after > shared, 'task.js must load AFTER claim-action.js');
61
+ });
62
+
63
+ await test('task.html\'s cache-bust moved for task.js', () => {
64
+ assert.doesNotMatch(taskHtml, /task\.js\?v=2026-09-26-taskwhere/, 'the old bust is still there');
65
+ assert.match(taskHtml, /task\.js\?v=2026-09-28-taskclaim/);
66
+ });
67
+
68
+ // == Home's "Next up" rows ====================================================
69
+
70
+ await test('hall-render.js constructs the shared claim action for Home\'s Next-up row', () => {
71
+ // Lives in hall-render.js, not builders.js: builders.js sits at the
72
+ // 1,500-line fitness ratchet (hall-ui/CLAUDE.md — growth goes in the
73
+ // lib/renderer, not the page).
74
+ assert.match(hallRenderJs, /function wireNextUpClaim\(\)/);
75
+ assert.match(hallRenderJs, /makeClaimAction\(\{/);
76
+ assert.match(hallRenderJs, /window\.OTBClaimAction/);
77
+ });
78
+
79
+ await test('the shared postJSON carries { status, data } — the contract claim-action.js reads', () => {
80
+ const m = buildersJs.match(/async function postJSON\(path, body\) \{[\s\S]*?\n {2}\}/);
81
+ assert.ok(m, 'postJSON not found');
82
+ assert.match(m[0], /err\.data\s*=/, 'postJSON must set err.data or every refusal here falls through to the generic line');
83
+ });
84
+
85
+ await test('each Next-up row renders a Claim button carrying its task id', () => {
86
+ const m = buildersJs.match(/list\.innerHTML = rows\.map\(\(t\) => \{[\s\S]*?\n {6}\}\)\.join\(''\);/);
87
+ assert.ok(m, 'loadNextUp row template not found');
88
+ assert.match(m[0], /class="[^"]*next-up__claim[^"]*"/);
89
+ assert.match(m[0], /data-task-id="\$\{escapeHtml\(String\(t\.id\)\)\}"/);
90
+ });
91
+
92
+ await test('the Next-up Claim button routes through claimAction.claim, not a raw POST', () => {
93
+ const m = hallRenderJs.match(/function wireNextUpClaim\(\) \{[\s\S]*?\n {2}\}/);
94
+ assert.ok(m, 'wireNextUpClaim not found');
95
+ assert.match(m[0], /\$\('#home-next-list'\)\?\.addEventListener\('click'/);
96
+ assert.match(m[0], /claimAction\.claim\(taskId\)/);
97
+ });
98
+
99
+ await test('builders.js wires the Next-up claim action rather than hand-rolling it', () => {
100
+ assert.doesNotMatch(buildersJs, /postJSON\(\s*`\$\{API\}\/claims/);
101
+ assert.match(buildersJs, /wireNextUpClaim\(\)/, 'builders.js must call the renderer\'s wiring');
102
+ });
103
+
104
+ await test('the claim wiring landed in the renderer, not back over the 1,500-line ratchet', () => {
105
+ // Not a second copy of fitness.js's own gate (scripts/gds/fitness-ratchets.js)
106
+ // — this is the reason wireNextUpClaim moved to hall-render.js in the first
107
+ // place (see its own comment): a regression here explains WHY that file grew,
108
+ // not just THAT some file grew.
109
+ for (const [name, src] of [['builders.js', buildersJs], ['hall-render.js', hallRenderJs]]) {
110
+ const lines = src.split('\n').length;
111
+ assert.ok(lines <= 1500, `${name} is ${lines} lines — over the fitness ratchet`);
112
+ }
113
+ });
114
+
115
+ await test('index.html loads claim-action.js before builders.js', () => {
116
+ const shared = indexHtml.indexOf('claim-action.js');
117
+ const after = indexHtml.indexOf('builders.js?v=');
118
+ assert.ok(shared > -1, 'index.html must load claim-action.js');
119
+ assert.ok(after > shared, 'builders.js must load AFTER claim-action.js');
120
+ });
121
+
122
+ await test('index.html\'s cache-bust moved for builders.js', () => {
123
+ assert.doesNotMatch(indexHtml, /builders\.js\?v=2026-06-27-r75/, 'the old bust is still there');
124
+ assert.match(indexHtml, /builders\.js\?v=2026-09-28-nextupclaim/);
125
+ });
126
+
127
+ summary();
@@ -1,7 +1,7 @@
1
- // tests/box_ship_permissions.mjs
1
+ // tests/cli_allowlist_permissions.mjs
2
2
  //
3
3
  // task 1051 (D): the blessed methodology CLI scripts must stay pre-allowed in the
4
- // project .claude/settings.json so a dev-box Claude session (auto permission mode)
4
+ // project .claude/settings.json so a Claude session in auto permission mode
5
5
  // does not hard-DENY the very command it exists to run. The #1027 nightmare
6
6
  // dead-ended on `node scripts/gds/ship.js` at the auto classifier; this guards
7
7
  // against that allow-rule being silently dropped.
@@ -12,7 +12,7 @@
12
12
  // on every routine read. The list of tools is `.claude/hooks/shell-tools.js`, the same
13
13
  // one the guards read: a rule may only exist for a tool git-guard and secret-gate see.
14
14
  //
15
- // Run: node tests/box_ship_permissions.mjs
15
+ // Run: node tests/cli_allowlist_permissions.mjs
16
16
 
17
17
  import { strict as assert } from 'node:assert';
18
18
  import fs from 'node:fs';
@@ -62,7 +62,7 @@ perTool('permissions.allow pre-allows the generic GDS API channel api.js (auto-m
62
62
  // idea-triage promote, create-task, blocker-resolve, criterion ratify, goal
63
63
  // create, cost-via-API, security reports — rides the generic caller
64
64
  // `node scripts/gds/api.js <METHOD> /api/gds/...`. If it is not pre-allowed, a
65
- // dev-box (or laptop) session in AUTO permission mode has the classifier
65
+ // session in AUTO permission mode has the classifier
66
66
  // hard-DENY those writes as "[External System Writes] ... beyond the user's
67
67
  // request" — mistaking a sanctioned idea-triage session for scope creep. The
68
68
  // GDS server enforces builders.rank on every request (ADR 0016), so this rule
@@ -309,5 +309,5 @@ test('the instance starter mirrors this list in verb form (claude-materialize.js
309
309
  });
310
310
 
311
311
  console.log('');
312
- console.log(`box_ship_permissions: ${passed} passed, ${failed} failed`);
312
+ console.log(`cli_allowlist_permissions: ${passed} passed, ${failed} failed`);
313
313
  process.exit(failed === 0 ? 0 : 1);
@@ -201,7 +201,7 @@ test('signing into a second instance PRESERVES the first — including one writt
201
201
  });
202
202
 
203
203
  test('the ACTIVE pointer still lands exactly where every existing reader looks', () => {
204
- // In-repo skills, hooks and the dev box all read the active session file in the configured dir. The
204
+ // In-repo skills and hooks all read the active session file in the configured dir. The
205
205
  // store is additive; if this moved, every one of them would silently stop finding a session.
206
206
  const { out } = inSandbox(`
207
207
  await lib.saveSession({ token:'T', api_base:'https://a.example.com', builder:{github_login:'x'} });
@@ -351,6 +351,8 @@ async function main() {
351
351
  key: 'consumer-probe',
352
352
  title: 'Consumer Probe',
353
353
  description: 'A host module that ships only in the instance repo, never in @bongos/core.',
354
+ // Required since task 1003781: a module names its own exact version.
355
+ version: '1.0.0',
354
356
  coreVersion: CORE_VERSION_RANGE,
355
357
  contributes: {},
356
358
  }, null, 2)}\n`);