@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
package/tests/modules.mjs CHANGED
@@ -14,12 +14,11 @@ const {
14
14
  } = modulesMod;
15
15
 
16
16
  const VANILLA = {}; // no config at all
17
- const OTB = { modules: { game: true, 'art-pipeline': true, discord: true, 'dev-box': true } };
18
- // The post-extraction registry: built-ins (game, art-pipeline, discord) + every
19
- // discovered module. dev-box now lives in modules/dev-box/ (BV1.R43), so it is a
20
- // DISCOVERED key, not a built-in MODULE_REGISTRY one. The pure resolver tests
21
- // below pass this effective registry so 'dev-box' is a known key — exactly what
22
- // loadModules() does in production. Membership across the move is preserved here.
17
+ const OTB = { modules: { game: true, 'art-pipeline': true, discord: true, 'npm-release': true } };
18
+ // The post-extraction registry: every discovered module (npm-release lives in
19
+ // modules/npm-release/, so it is a DISCOVERED key, not a built-in MODULE_REGISTRY
20
+ // one). The pure resolver tests below pass this effective registry so 'npm-release'
21
+ // is a known key — exactly what loadModules() does in production.
23
22
  const EFFECTIVE = modulesMod.effectiveRegistry();
24
23
 
25
24
  // Sample feature-module keys for the PURE resolver tests below. `game` and
@@ -43,7 +42,7 @@ test('every registry module has an explicit default of false', () => {
43
42
  test('instance config turns modules on', () => {
44
43
  const m = resolveModules({ neutral: VANILLA, instance: OTB, registry: SAMPLE_REG });
45
44
  assert.equal(m.game, true);
46
- assert.equal(m['dev-box'], true);
45
+ assert.equal(m['npm-release'], true);
47
46
  assert.equal(isModuleEnabled('discord', m), true);
48
47
  // memory (BV1.R72), grading (BV1.R73), economy (BV1.R77), ideas (BV1.R78),
49
48
  // security (BV1.R79), builder-settings (BV1.R80), onboarding (BV1.R81),
@@ -70,7 +69,7 @@ test('instance config turns modules on', () => {
70
69
  });
71
70
 
72
71
  test('instance config overrides the neutral starter per-flag (deep merge of the modules map)', () => {
73
- const neutral = { modules: { game: false, discord: false, 'dev-box': false, 'art-pipeline': false } };
72
+ const neutral = { modules: { game: false, discord: false, 'npm-release': false, 'art-pipeline': false } };
74
73
  const instance = { modules: { discord: true } }; // only flip one
75
74
  const m = resolveModules({ neutral, instance, registry: SAMPLE_REG });
76
75
  assert.equal(m.discord, true);
@@ -78,8 +77,8 @@ test('instance config overrides the neutral starter per-flag (deep merge of the
78
77
  });
79
78
 
80
79
  test('env override beats instance config (precedence), with prefix + snake-casing', () => {
81
- const m = resolveModules({ instance: OTB, env: { OTB_MODULE_DEV_BOX: '0' }, envPrefix: 'OTB', registry: SAMPLE_REG });
82
- assert.equal(m['dev-box'], false); // env off beat instance on
80
+ const m = resolveModules({ instance: OTB, env: { OTB_MODULE_NPM_RELEASE: '0' }, envPrefix: 'OTB', registry: SAMPLE_REG });
81
+ assert.equal(m['npm-release'], false); // env off beat instance on
83
82
  assert.equal(m.game, true); // others untouched
84
83
  });
85
84
 
@@ -106,14 +105,14 @@ test('an unknown module key in config is rejected when ENABLED (typo guard)', ()
106
105
  assert.throws(() => resolveModules({ instance: { modules: { gaem: true } } }), /unknown module "gaem"/);
107
106
  });
108
107
 
109
- // task 1824 — a stripped CORE has no game/art-pipeline/discord/dev-box, but the
108
+ // task 1824 — a stripped CORE has no game/art-pipeline/discord, but the
110
109
  // config/modules.neutral.json that SHIPS with it names them (so the same file is valid
111
110
  // in the full repo). The loader must TOLERATE an unknown module when it is DISABLED, or
112
111
  // a vanilla self-host crashes on `unknown module "game"`.
113
112
  test('an unknown module key that is DISABLED is tolerated (a config may name a not-yet-installed module)', () => {
114
113
  const reg = { memory: { default: true }, grading: { default: true } }; // core-only registry (no feature modules)
115
114
  // The exact vanilla-core scenario: neutral names four absent feature modules, all off.
116
- const neutral = { modules: { game: false, 'art-pipeline': false, discord: false, 'dev-box': false } };
115
+ const neutral = { modules: { game: false, 'art-pipeline': false, discord: false, 'npm-release': false } };
117
116
  let m;
118
117
  assert.doesNotThrow(() => { m = resolveModules({ neutral, registry: reg }); });
119
118
  assert.deepEqual(m, { memory: true, grading: true }); // core modules resolve; absent-disabled dropped
@@ -202,7 +201,38 @@ test('the committed config files resolve: OTB enables all four, on disk', () =>
202
201
  assert.equal(m.game, true);
203
202
  assert.equal(m['art-pipeline'], true);
204
203
  assert.equal(m.discord, true);
205
- assert.equal(m['dev-box'], true);
204
+ assert.equal(m['npm-release'], true);
205
+ });
206
+
207
+ // task 1003892 — a RETIRED key (the module was deleted from the core) that a
208
+ // committed instance pack still switches ON must NOT stop the process booting.
209
+ // cloudbongos.com carried `"dev-box": true` when the module went; the typo guard
210
+ // would have thrown `cannot enable unknown module` at the next restart.
211
+ test('1003892: a retired module enabled in the instance pack is dropped with a warning, not thrown on', () => {
212
+ const warned = [];
213
+ const m = resolveModules({
214
+ instance: { modules: { 'dev-box': true, discord: true } },
215
+ registry: SAMPLE_REG,
216
+ warnRetired: (key, source) => warned.push(`${key}@${source}`),
217
+ });
218
+ assert.equal('dev-box' in m, false, 'a retired key never enters the resolved set');
219
+ assert.equal(m.discord, true, 'the rest of the pack still resolves');
220
+ assert.deepEqual(warned, ['dev-box@instance'], 'the stale line is named once, with its layer');
221
+ // disabled is the quiet no-op it always was
222
+ const quiet = [];
223
+ resolveModules({ neutral: { modules: { 'dev-box': false } }, registry: SAMPLE_REG, warnRetired: (k) => quiet.push(k) });
224
+ assert.deepEqual(quiet, [], 'a retired key switched OFF says nothing');
225
+ });
226
+
227
+ test('1003892: the retired list does not weaken the typo guard for any other unknown key', () => {
228
+ assert.throws(
229
+ () => resolveModules({ instance: { modules: { 'dev-boxes': true } }, registry: SAMPLE_REG, warnRetired: () => {} }),
230
+ /cannot enable unknown module "dev-boxes"/,
231
+ );
232
+ for (const [key, why] of Object.entries(modulesMod.RETIRED_MODULE_KEYS)) {
233
+ assert.ok(!(key in modulesMod.effectiveRegistry()), `${key} is retired, so no installed module may carry it`);
234
+ assert.ok(why && why.length > 20, `${key} must say why it left`);
235
+ }
206
236
  });
207
237
 
208
238
  test('GDS_MODULES_FILE selects the instance modules pack on disk (mirrors GDS_BRANDING_FILE)', () => {
@@ -235,8 +265,8 @@ test('a branded sibling (GDS_BRANDING_FILE set, no GDS_MODULES_FILE) defaults to
235
265
  test('1002857: an override outranks the committed instance pack', () => {
236
266
  // The whole point: the pack is what a deploy restores, so an override that sat
237
267
  // below it would be erased by the very event it exists to survive.
238
- const m = resolveModules({ instance: OTB, override: { 'dev-box': false }, registry: SAMPLE_REG });
239
- assert.equal(m['dev-box'], false, 'the override wins over the pack');
268
+ const m = resolveModules({ instance: OTB, override: { 'npm-release': false }, registry: SAMPLE_REG });
269
+ assert.equal(m['npm-release'], false, 'the override wins over the pack');
240
270
  assert.equal(m.discord, true, 'a module with no override still reads the pack');
241
271
  });
242
272
 
@@ -248,12 +278,12 @@ test('1002857: an override turns a module ON as well as off', () => {
248
278
  test('1002857: env still outranks an override — the incident knob needs no database', () => {
249
279
  const m = resolveModules({
250
280
  instance: VANILLA,
251
- override: { 'dev-box': true },
252
- env: { OTB_MODULE_DEV_BOX: '0' },
281
+ override: { 'npm-release': true },
282
+ env: { OTB_MODULE_NPM_RELEASE: '0' },
253
283
  envPrefix: 'OTB',
254
284
  registry: SAMPLE_REG,
255
285
  });
256
- assert.equal(m['dev-box'], false, 'env must be able to kill a module the DB turned on');
286
+ assert.equal(m['npm-release'], false, 'env must be able to kill a module the DB turned on');
257
287
  });
258
288
 
259
289
  test('1002857: an override for a module this build does not carry is inert, not fatal', () => {
@@ -279,12 +309,12 @@ test('1002857: each module reports the layer that produced its state', () => {
279
309
  const m = resolveModules({
280
310
  neutral: { modules: { discord: false } },
281
311
  instance: OTB,
282
- override: { 'dev-box': false },
312
+ override: { 'npm-release': false },
283
313
  env: { OTB_MODULE_GAME: '0' },
284
314
  envPrefix: 'OTB',
285
315
  registry: SAMPLE_REG,
286
316
  });
287
- assert.equal(m.sources['dev-box'], 'override');
317
+ assert.equal(m.sources['npm-release'], 'override');
288
318
  assert.equal(m.sources.game, 'env');
289
319
  assert.equal(m.sources.discord, 'instance', 'the instance pack outranks the neutral one');
290
320
  assert.equal(m.sources['art-pipeline'], 'instance');
@@ -316,18 +346,18 @@ test('1002857: setModuleOverrides installs the snapshot AND invalidates the cach
316
346
  // the exact class of bug this feature exists to remove.
317
347
  try {
318
348
  const before = modulesMod.modules();
319
- assert.equal(before['dev-box'], false, 'precondition: off on a vanilla resolve');
320
- const after = modulesMod.setModuleOverrides({ 'dev-box': true });
321
- assert.equal(after['dev-box'], true, 'the returned set reflects the override');
322
- assert.equal(modulesMod.modules()['dev-box'], true, 'and so does the live singleton');
323
- assert.equal(moduleOverrides()['dev-box'], true);
349
+ assert.equal(before['npm-release'], false, 'precondition: off on a vanilla resolve');
350
+ const after = modulesMod.setModuleOverrides({ 'npm-release': true });
351
+ assert.equal(after['npm-release'], true, 'the returned set reflects the override');
352
+ assert.equal(modulesMod.modules()['npm-release'], true, 'and so does the live singleton');
353
+ assert.equal(moduleOverrides()['npm-release'], true);
324
354
  } finally {
325
355
  modulesMod._resetForTests();
326
356
  }
327
357
  });
328
358
 
329
359
  test('1002857: _resetForTests clears the override snapshot too', () => {
330
- modulesMod.setModuleOverrides({ 'dev-box': true });
360
+ modulesMod.setModuleOverrides({ 'npm-release': true });
331
361
  modulesMod._resetForTests();
332
362
  assert.deepEqual(moduleOverrides(), {}, 'a leaked snapshot would cross-contaminate tests');
333
363
  });
@@ -79,7 +79,7 @@ test('1002857: POST /modules/:key/enable rejects a non-boolean before touching t
79
79
  // is why the next assertion matters more than this one.
80
80
  const router = buildModulesRouter();
81
81
  const res = await invoke(handlerFor(router, 'post', '/modules/:key/enable'), {
82
- params: { key: 'dev-box' }, query: {}, body: { enabled: 'yes-please' }, builder: { id: 1 },
82
+ params: { key: 'discord' }, query: {}, body: { enabled: 'yes-please' }, builder: { id: 1 },
83
83
  });
84
84
  assert.equal(res.body.error, 'invalid_body');
85
85
  });
@@ -3,16 +3,15 @@
3
3
  // Regression for task 1444 (scootermack's bug).
4
4
  //
5
5
  // GET /builders/:id/onboarding shapes the SAME displayed checklist as GET /me,
6
- // but three of its steps clear from LIVE signals rather than the onboarding_state
7
- // row: box_connect (boxes.hasEverConnected), join_discord (db.getDiscordLink),
8
- // cli_setup (db.hasCliSession). The route originally called
9
- // shapeForApi(state, { shipCount }) and omitted those signals — so they defaulted
10
- // to false and the route rendered all three steps perpetually UNCHECKED even after
11
- // the builder cleared them. This locks in that the route passes the live signals
12
- // through, exactly as /me does.
6
+ // but two of its steps clear from LIVE signals rather than the onboarding_state
7
+ // row: join_discord (the discord.isLinked port) and cli_setup (db.hasCliSession).
8
+ // The route originally called shapeForApi(state, { shipCount }) and omitted those
9
+ // signals — so they defaulted to false and the route rendered the steps
10
+ // perpetually UNCHECKED even after the builder cleared them. This locks in that
11
+ // the route passes the live signals through, exactly as /me does.
13
12
  //
14
- // We stub auth + db and patch onboarding-state.getState / boxes.hasEverConnected,
15
- // but exercise the REAL shapeForApi. No network, no Postgres.
13
+ // We stub auth + db and patch onboarding-state.getState, but exercise the REAL
14
+ // shapeForApi. No network, no Postgres.
16
15
  //
17
16
  // Run: node tests/onboarding_route_signals.mjs
18
17
 
@@ -66,16 +65,12 @@ const onboardingState = require('../modules/onboarding/onboarding-state.js');
66
65
  let fakeState = null;
67
66
  onboardingState.getState = async () => fakeState;
68
67
 
69
- // box_connect's live signal now reaches builders.js through the kernel seam (the
70
- // `box.hasEverConnected` port the dev-box module provides, BV1.R43) — not a direct
71
- // boxes import. join_discord's signal reaches it the same way through the
72
- // `discord.isLinked` port the discord module provides (BV1.R47) — not db.getDiscordLink.
73
- // So we register providers instead of monkey-patching; the route resolves them via
74
- // resolveOptional exactly as in production.
68
+ // join_discord's live signal reaches builders.js through the kernel seam (the
69
+ // `discord.isLinked` port the discord module provides, BV1.R47) — not
70
+ // db.getDiscordLink. So we register a provider instead of monkey-patching; the
71
+ // route resolves it via resolveOptional exactly as in production.
75
72
  const seams = require('../src/module-seams.js');
76
- let fakeBoxConnected = false;
77
73
  seams._reset();
78
- seams.registerProvider('box.hasEverConnected', async () => fakeBoxConnected);
79
74
  seams.registerProvider('discord.isLinked', async () => dbSignals.discordLinked);
80
75
 
81
76
  // The onboarding state machine carved into modules/onboarding/ (BV1.R81): the
@@ -83,7 +78,7 @@ seams.registerProvider('discord.isLinked', async () => dbSignals.discordLinked);
83
78
  // (resolveOptional('onboarding')), not a direct require. Register the real port
84
79
  // provider — its getState delegates to the (patched-above) onboarding-state, so
85
80
  // the genuine shapeForApi is still exercised. (Mirrors how this test already
86
- // registers the box/discord providers instead of monkey-patching.)
81
+ // registers the discord provider instead of monkey-patching.)
87
82
  seams.registerProvider('onboarding', require('../modules/onboarding/port.js'));
88
83
 
89
84
  // The lifecycle (tasks · claims · ship) carved to modules/lifecycle/ (BV1.R86):
@@ -129,45 +124,40 @@ const stepCleared = (body, id) => {
129
124
  };
130
125
 
131
126
  // A mid-onboarding row (mirrors a real newcomer who's signed in, picked a craft,
132
- // and run /builder-start) — so the box/discord/cli steps are decided purely by
127
+ // and run /builder-start) — so the discord/cli steps are decided purely by
133
128
  // the LIVE signals, which is exactly what regressed.
134
129
  const MID_STATE = { current_stage: 'first_claim', completed_stages: ['auth_complete', 'pick_disciplines', 'first_start'] };
135
130
 
136
131
  const { server, port } = await listen(buildApp());
137
132
 
138
- await t('no live signals → box/discord/cli all uncleared', async () => {
133
+ await t('no live signals → discord/cli both uncleared, and no retired box step is rendered', async () => {
139
134
  fakeState = MID_STATE;
140
- fakeBoxConnected = false;
141
135
  dbSignals.discordLinked = false; dbSignals.cliAuthed = false;
142
136
  const r = await get(port, '/api/gds/builders/94/onboarding');
143
137
  assert.equal(r.status, 200);
144
- assert.equal(stepCleared(r.json, 'box_connect'), false);
145
138
  assert.equal(stepCleared(r.json, 'join_discord'), false);
146
139
  assert.equal(stepCleared(r.json, 'cli_setup'), false);
140
+ assert.equal(stepCleared(r.json, 'box_connect'), undefined, 'the retired box_connect step is no longer on the checklist (task 1004310)');
147
141
  });
148
142
 
149
- await t('box connected (first_connected_at set) → box_connect clears [the scootermack regression]', async () => {
143
+ await t('discord linked → join_discord clears from ITS OWN signal [the scootermack regression]', async () => {
150
144
  fakeState = MID_STATE;
151
- fakeBoxConnected = true;
152
- dbSignals.discordLinked = false; dbSignals.cliAuthed = false;
145
+ dbSignals.discordLinked = true; dbSignals.cliAuthed = false;
153
146
  const r = await get(port, '/api/gds/builders/94/onboarding');
154
147
  assert.equal(r.status, 200);
155
- assert.equal(stepCleared(r.json, 'box_connect'), true, 'box_connect must clear once the box has connected');
156
- // The other two stay false — proving box_connect cleared from ITS OWN signal,
148
+ assert.equal(stepCleared(r.json, 'join_discord'), true, 'join_discord must clear once the account is linked');
149
+ // cli_setup stays false — proving join_discord cleared from its own signal,
157
150
  // not because every step got force-cleared.
158
- assert.equal(stepCleared(r.json, 'join_discord'), false);
159
151
  assert.equal(stepCleared(r.json, 'cli_setup'), false);
160
152
  });
161
153
 
162
- await t('discord linked + cli authed → those two clear independently', async () => {
154
+ await t('discord linked + cli authed → both clear', async () => {
163
155
  fakeState = MID_STATE;
164
- fakeBoxConnected = false;
165
156
  dbSignals.discordLinked = true; dbSignals.cliAuthed = true;
166
157
  const r = await get(port, '/api/gds/builders/94/onboarding');
167
158
  assert.equal(r.status, 200);
168
159
  assert.equal(stepCleared(r.json, 'join_discord'), true);
169
160
  assert.equal(stepCleared(r.json, 'cli_setup'), true);
170
- assert.equal(stepCleared(r.json, 'box_connect'), false);
171
161
  });
172
162
 
173
163
  server.close();
@@ -77,29 +77,27 @@ test('isKnownStage: typo rejected', () => {
77
77
 
78
78
  // ---- shapeForApi (displayed checklist, #777) ------------------------------
79
79
  //
80
- // shapeForApi returns the DISPLAYED 5-step "Path into the City"
81
- // (box_connect, join_discord, pick_disciplines, cli_setup, ship_three) — split
82
- // from the engine STAGES so NONE of the displayed steps affect graduation.
83
- // `graduated` and `current_stage` still reflect the engine. box_connect/
84
- // join_discord/cli_setup are auto-derived from `boxConnected`/`discordLinked`/
85
- // `cliAuthed` (no manual ticks, #823/#777).
80
+ // shapeForApi returns the DISPLAYED 4-step "Path into the City"
81
+ // (join_discord, pick_disciplines, cli_setup, ship_three) — split from the
82
+ // engine STAGES so NONE of the displayed steps affect graduation. `graduated`
83
+ // and `current_stage` still reflect the engine. join_discord/cli_setup are
84
+ // auto-derived from `discordLinked`/`cliAuthed` (no manual ticks, #777).
86
85
 
87
- const DISPLAY_ORDER = ['box_connect', 'join_discord', 'pick_disciplines', 'cli_setup', 'ship_three'];
86
+ const DISPLAY_ORDER = ['join_discord', 'pick_disciplines', 'cli_setup', 'ship_three'];
88
87
 
89
- test('shapeForApi: null row → 5-step displayed shape in order', () => {
88
+ test('shapeForApi: null row → 4-step displayed shape in order', () => {
90
89
  const out = onboarding.shapeForApi(null);
91
90
  assert.equal(out.current_stage, 'auth_complete'); // engine stage still surfaced
92
91
  assert.deepEqual(out.completed_stages, []);
93
92
  assert.equal(out.graduated, false);
94
93
  assert.equal(out.progress.cleared, 0);
95
- assert.equal(out.progress.total, 5);
94
+ assert.equal(out.progress.total, 4);
96
95
  assert.deepEqual(out.stages.map((s) => s.id), DISPLAY_ORDER);
97
- assert.equal(out.stages[0].is_current, true); // box_connect, first gap
96
+ assert.equal(out.stages[0].is_current, true); // join_discord, first gap
98
97
  assert.equal(out.stages[0].cleared, false);
99
- assert.equal(out.stages[0].source, 'box');
100
- assert.equal(out.stages[1].source, 'discord'); // join_discord
101
- assert.equal(out.stages[2].source, 'stage'); // pick_disciplines is a real stage
102
- assert.equal(out.stages[3].source, 'cli'); // cli_setup
98
+ assert.equal(out.stages[0].source, 'discord'); // join_discord
99
+ assert.equal(out.stages[1].source, 'stage'); // pick_disciplines is a real stage
100
+ assert.equal(out.stages[2].source, 'cli'); // cli_setup
103
101
  });
104
102
 
105
103
  test('shapeForApi: partial row → cleared flags + is_current points at first displayed gap', () => {
@@ -113,62 +111,50 @@ test('shapeForApi: partial row → cleared flags + is_current points at first di
113
111
  const byId = Object.fromEntries(out.stages.map((s) => [s.id, s]));
114
112
  // pick_disciplines is the only displayed step the engine has cleared.
115
113
  assert.equal(byId.pick_disciplines.cleared, true);
116
- assert.equal(byId.box_connect.cleared, false); // no box connection
117
114
  assert.equal(byId.cli_setup.cleared, false); // no cli auth
118
115
  assert.equal(byId.join_discord.cleared, false); // no link
119
116
  assert.equal(byId.ship_three.cleared, false);
120
- // First displayed gap is box_connect (it sorts before the cleared craft step).
121
- assert.equal(byId.box_connect.is_current, true);
117
+ // First displayed gap is join_discord (it sorts before the cleared craft step).
118
+ assert.equal(byId.join_discord.is_current, true);
122
119
  assert.equal(byId.pick_disciplines.is_current, false);
123
120
  assert.equal(out.progress.cleared, 1);
124
121
  });
125
122
 
126
- test('shapeForApi: box_connect clears from boxConnected; is_current advances to join_discord', () => {
123
+ test('shapeForApi: a legacy caller passing boxConnected changes nothing (the step is gone)', () => {
124
+ // Removed with the dev box (task 1004310): an unknown option must be inert,
125
+ // never a fifth step or a thrown error.
127
126
  const out = onboarding.shapeForApi(
128
127
  { current_stage: 'auth_complete', completed_stages: ['auth_complete'] },
129
128
  { boxConnected: true }
130
129
  );
131
- const byId = Object.fromEntries(out.stages.map((s) => [s.id, s]));
132
- assert.equal(byId.box_connect.cleared, true);
133
- // Next gap is now join_discord (no link supplied).
134
- assert.equal(byId.join_discord.is_current, true);
130
+ assert.deepEqual(out.stages.map((s) => s.id), DISPLAY_ORDER);
131
+ assert.equal(out.progress.cleared, 0);
135
132
  });
136
133
 
137
- test('shapeForApi: cli_setup (step 4) clears from cliAuthed; box_connect stays first gap without a connection', () => {
134
+ test('shapeForApi: cli_setup (step 3) clears from cliAuthed; join_discord stays first gap without a link', () => {
138
135
  const out = onboarding.shapeForApi(
139
136
  { current_stage: 'auth_complete', completed_stages: ['auth_complete'] },
140
137
  { cliAuthed: true }
141
138
  );
142
139
  const byId = Object.fromEntries(out.stages.map((s) => [s.id, s]));
143
140
  assert.equal(byId.cli_setup.cleared, true);
144
- assert.equal(byId.box_connect.cleared, false);
145
- // box_connect is still the first gap (it precedes the cleared cli step).
146
- assert.equal(byId.box_connect.is_current, true);
147
- });
148
-
149
- test('shapeForApi: box_connect and cli_setup are independent signals', () => {
150
- // Connected to the box but CLI not yet authenticated — only box_connect ticks.
151
- const out = onboarding.shapeForApi(
152
- { current_stage: 'auth_complete', completed_stages: ['auth_complete'] },
153
- { boxConnected: true, cliAuthed: false }
154
- );
155
- const byId = Object.fromEntries(out.stages.map((s) => [s.id, s]));
156
- assert.equal(byId.box_connect.cleared, true);
157
- assert.equal(byId.cli_setup.cleared, false);
158
- // box_connect clears, but cli_setup (step 4) stays open on its own signal.
159
- assert.equal(byId.box_connect.is_current, false);
141
+ assert.equal(byId.join_discord.cleared, false);
142
+ // join_discord is still the first gap (it precedes the cleared cli step).
143
+ assert.equal(byId.join_discord.is_current, true);
160
144
  });
161
145
 
162
- test('shapeForApi: join_discord clears from a live Discord link; box_connect stays first gap without a connection', () => {
146
+ test('shapeForApi: join_discord and cli_setup are independent signals', () => {
147
+ // Discord linked but CLI not yet authenticated — only join_discord ticks.
163
148
  const out = onboarding.shapeForApi(
164
149
  { current_stage: 'auth_complete', completed_stages: ['auth_complete'] },
165
- { discordLinked: true }
150
+ { discordLinked: true, cliAuthed: false }
166
151
  );
167
152
  const byId = Object.fromEntries(out.stages.map((s) => [s.id, s]));
168
153
  assert.equal(byId.join_discord.cleared, true);
169
- assert.equal(byId.box_connect.cleared, false);
170
- // box_connect is still the first gap (it precedes the cleared discord step).
171
- assert.equal(byId.box_connect.is_current, true);
154
+ assert.equal(byId.cli_setup.cleared, false);
155
+ // join_discord clears, and the next gap is the craft picker.
156
+ assert.equal(byId.join_discord.is_current, false);
157
+ assert.equal(byId.pick_disciplines.is_current, true);
172
158
  });
173
159
 
174
160
  test('shapeForApi: graduated row → all displayed steps cleared, none current (regardless of derived signals)', () => {
@@ -177,10 +163,10 @@ test('shapeForApi: graduated row → all displayed steps cleared, none current (
177
163
  completed_stages: onboarding.STAGES,
178
164
  started_at: '2026-05-01T00:00:00Z',
179
165
  completed_at: '2026-05-05T00:00:00Z',
180
- }, { discordLinked: false, boxConnected: false, cliAuthed: false }); // proves display steps don't gate graduation
166
+ }, { discordLinked: false, cliAuthed: false }); // proves display steps don't gate graduation
181
167
  assert.equal(out.graduated, true);
182
- assert.equal(out.progress.cleared, 5);
183
- assert.equal(out.progress.total, 5);
168
+ assert.equal(out.progress.cleared, 4);
169
+ assert.equal(out.progress.total, 4);
184
170
  assert.ok(out.stages.every((s) => s.cleared === true));
185
171
  assert.ok(out.stages.every((s) => s.is_current === false));
186
172
  });
@@ -329,9 +315,9 @@ test('graduation is engine-only (display signals never gate it)', async () => {
329
315
  }
330
316
  const state = await onboarding.getState(c, 60);
331
317
  assert.equal(state.current_stage, 'graduated');
332
- // graduated:true even with discordLinked/boxConnected/cliAuthed false — proves
318
+ // graduated:true even with discordLinked/cliAuthed false — proves
333
319
  // the display-only steps never gate the xenos→thetes promotion (#692).
334
- const shaped = onboarding.shapeForApi(state, { discordLinked: false, boxConnected: false, cliAuthed: false });
320
+ const shaped = onboarding.shapeForApi(state, { discordLinked: false, cliAuthed: false });
335
321
  assert.equal(shaped.graduated, true);
336
322
  assert.ok(shaped.stages.every((s) => s.cleared === true));
337
323
  });
@@ -139,7 +139,7 @@ test('pasting a token for instance B PRESERVES instance A — the bug this close
139
139
  assert.equal(stored.token, 'B'.repeat(64));
140
140
 
141
141
  // 3. The active pointer still follows the paste, in the same place every existing
142
- // reader (skills, hooks, the dev box) looks. The fix is additive or it is a
142
+ // reader (skills, hooks) looks. The fix is additive or it is a
143
143
  // different, quieter outage.
144
144
  const pointer = readJson(active);
145
145
  assert.equal(pointer.api_base, base, 'the active pointer must follow the paste');
@@ -73,9 +73,6 @@ t('#863-followup: newly-added protected surfaces are covered (findings #3/#9/#10
73
73
  '.husky/pre-push',
74
74
  '.husky/pre-commit',
75
75
  'modules/grading/grader-workers/hacker.js',
76
- 'modules/dev-box/boxes.js',
77
- 'modules/dev-box/box-access.js',
78
- 'modules/dev-box/box-credential.js',
79
76
  ];
80
77
  for (const f of newlyProtected) {
81
78
  const hits = ppc.matchProtected([f]);
@@ -178,7 +178,7 @@ test('R56: the served hall injects window.__BRANDING__ synchronously', async ()
178
178
  });
179
179
 
180
180
  test('R60: the served hall injects window.__MODULES__ (feature-module flags) synchronously', async () => {
181
- // The hall's dev-box/discord UI gating (R60, task 1199) reads
181
+ // The hall's discord UI gating (R60, task 1199) reads
182
182
  // window.__MODULES__.enabled[...]; serve-internal must inject it the same
183
183
  // synchronous way as __BRANDING__, or the frontend gate has nothing to read.
184
184
  const res = await request('/', 'builders.test.local');
@@ -189,7 +189,6 @@ test('R60: the served hall injects window.__MODULES__ (feature-module flags) syn
189
189
  assert.ok(m, 'a __MODULES__ assignment is present');
190
190
  const parsed = JSON.parse(m[1]);
191
191
  assert.equal(typeof parsed.enabled, 'object');
192
- assert.equal(typeof parsed.enabled['dev-box'], 'boolean');
193
192
  assert.equal(typeof parsed.enabled.discord, 'boolean');
194
193
  });
195
194
 
@@ -162,7 +162,7 @@ test('the exempt list: the door, the manifest, the probes, the downloads — on
162
162
  '/api/gds/instance', '/api/bongos/instance', '/api/gds/healthz',
163
163
  '/api/bongos/provisioning/tls-check', '/api/gds/provisioning/health',
164
164
  '/healthz', '/version', '/metrics', '/__stealth',
165
- '/downloads/bongos/install.sh', '/downloads/devbox/latest.json',
165
+ '/downloads/bongos/install.sh', '/downloads/bongos/latest.json',
166
166
  // Express routes case-insensitively and with a trailing slash — the list sees what the router sees
167
167
  '/API/GDS/INSTANCE', '/api/gds/instance/', '/HEALTHZ', '/api/bongos/auth/',
168
168
  ]) assert.equal(isExempt(p), true, `${p} must answer a stranger`);
@@ -4,7 +4,7 @@
4
4
  // goal 25 "bx-identity"). The route is intentionally UNAUTHENTICATED, so its one
5
5
  // load-bearing invariant is that it must NEVER leak the private columns that
6
6
  // db.getBuilderById also returns (anthropic_email / anthropic_user_id /
7
- // monthly_budget_usd / system_role / box_blocked* / deactivated_reason). It
7
+ // monthly_budget_usd / system_role / deactivated_reason). It
8
8
  // whitelists a public subset off the row; this test proves the whitelist holds,
9
9
  // that the shape is right, and that unknown/invalid ids answer 404/400.
10
10
  //
@@ -74,17 +74,12 @@ const FULL_ROW = {
74
74
  anthropic_email: SECRET_EMAIL,
75
75
  anthropic_user_id: SECRET_USER,
76
76
  monthly_budget_usd: 500,
77
- box_blocked: true,
78
- box_blocked_reason: SECRET_REASON,
79
- box_blocked_at: '2026-01-01T00:00:00.000Z',
80
- box_blocked_by: 7,
81
77
  };
82
78
 
83
79
  // The columns that must NEVER appear on the returned builder object.
84
80
  const PRIVATE_KEYS = [
85
81
  'github_id', 'status', 'deactivated_at', 'reactivated_at', 'deactivated_reason',
86
82
  'system_role', 'anthropic_email', 'anthropic_user_id', 'monthly_budget_usd',
87
- 'box_blocked', 'box_blocked_reason', 'box_blocked_at', 'box_blocked_by',
88
83
  ];
89
84
  // The public subset the route DOES return.
90
85
  const PUBLIC_KEYS = [