@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
@@ -59,21 +59,22 @@ const PAYLOAD = {
59
59
  // `summary` is part of every real bundle row and is the why-line's FLOOR since task
60
60
  // 1003684 — the sentence shown when no rule moved anything. The fixture carried none
61
61
  // before that, which quietly made every "no adjustment, no sentence" case pass for the
62
- // wrong reason. These are the real strings from starter-bundles.js.
62
+ // wrong reason. Shaped like the real strings in starter-bundles.js (the live payload is pinned
63
+ // separately, at the bottom of the team-shape section).
63
64
  bundles: [
64
- { type: 'game', bundle: ['dev-box', 'discord'], summary: 'Somewhere for players to gather, and a place to walk a change through before anyone plays it.' },
65
+ { type: 'game', bundle: ['npm-release', 'discord'], summary: 'Somewhere for players to gather, and a way to publish each release of the game.' },
65
66
  { type: 'research', bundle: ['agents'], summary: 'Extra agents to run the reading and the experiments the question needs.' },
66
- { type: 'business', bundle: ['agents', 'dev-box'], summary: 'A place to try a change before customers see it, and agents to look it over first.' },
67
+ { type: 'business', bundle: ['agents', 'npm-release'], summary: 'A way to publish each release, and agents to look it over first.' },
67
68
  { type: 'non-profit', bundle: ['discord'], summary: 'Somewhere the community around the cause can gather and be heard.' },
68
69
  { type: 'not-sure', bundle: [], summary: 'Just the core, and nothing else to decide today. Add modules whenever you know what you need.' },
69
70
  ],
70
71
  // `aliases` — search words a row is findable under that its own copy never uses
71
- // (task 1003500, amending ADR 0244 §3). Never rendered, like the key. dev-box
72
+ // (task 1003500, amending ADR 0244 §3). Never rendered, like the key. npm-release
72
73
  // carries none on purpose, so the "a row without aliases still matches on its own
73
74
  // words" path is exercised by the same fixture rather than by a special case.
74
75
  optional: [
75
76
  { key: 'agents', label: 'Extra agents', summary: 'More AI agents alongside the ones every project gets.', aliases: ['bots', 'assistants', 'automation'] },
76
- { key: 'dev-box', label: 'Dev boxes', summary: 'A cloud workspace where a change can be tried out first.' },
77
+ { key: 'npm-release', label: 'npm releases', summary: 'Publishes each release of your package to the registry.' },
77
78
  { key: 'discord', label: 'Discord', summary: 'A Discord server that carries your build news.', aliases: ['chatroom', 'talk', 'community'] },
78
79
  ],
79
80
  };
@@ -92,18 +93,18 @@ const TAILORED = {
92
93
  bundles: PAYLOAD.bundles.map((b) => {
93
94
  if (b.type === 'game') {
94
95
  return { ...b, byTeamShape: {
95
- // solo: the shared environment comes off
96
- solo: { bundle: ['discord'], adjustments: [{ rule: 'building-alone-needs-no-shared-environment', effect: 'remove', module: 'dev-box', reason: 'You are building this alone, so the shared cloud environment is off.' }] },
97
- // small-team: the rule wants dev-box, the bundle already has it — a rule
96
+ // solo: the release pipeline comes off
97
+ solo: { bundle: ['discord'], adjustments: [{ rule: 'building-alone-needs-no-release-pipeline', effect: 'remove', module: 'npm-release', reason: 'You are building this alone, so the release pipeline is off.' }] },
98
+ // small-team: the rule wants npm-release, the bundle already has it — a rule
98
99
  // that changed nothing claims nothing
99
- 'small-team': { bundle: ['dev-box', 'discord'], adjustments: [] },
100
- community: { bundle: ['dev-box', 'discord'], adjustments: [] },
100
+ 'small-team': { bundle: ['npm-release', 'discord'], adjustments: [] },
101
+ community: { bundle: ['npm-release', 'discord'], adjustments: [] },
101
102
  } };
102
103
  }
103
104
  if (b.type === 'research') {
104
105
  return { ...b, byTeamShape: {
105
106
  solo: { bundle: ['agents'], adjustments: [] },
106
- 'small-team': { bundle: ['agents', 'dev-box'], adjustments: [{ rule: 'a-team-shares-one-environment', effect: 'add', module: 'dev-box', reason: 'A small team gets one shared environment to build in.' }] },
107
+ 'small-team': { bundle: ['agents', 'npm-release'], adjustments: [{ rule: 'a-team-shares-one-release-pipeline', effect: 'add', module: 'npm-release', reason: 'A small team gets one release pipeline to publish through.' }] },
107
108
  community: { bundle: ['agents', 'discord'], adjustments: [{ rule: 'a-community-needs-somewhere-to-gather', effect: 'add', module: 'discord', reason: 'You said an open community is building this, so somewhere for it to gather is switched on.' }] },
108
109
  } };
109
110
  }
@@ -232,16 +233,16 @@ test('the declared project type preselects its bundle, and the rest of the catal
232
233
 
233
234
  // Assert — every opt-in module is a row; the bundle's two are on
234
235
  const rows = h.rows();
235
- assert.deepEqual(rows.map((r) => r.key), ['agents', 'dev-box', 'discord'], 'the whole opt-in catalog is offered');
236
- assert.deepEqual(rows.filter((r) => r.pressed).map((r) => r.key), ['dev-box', 'discord'], 'the game bundle is preselected');
236
+ assert.deepEqual(rows.map((r) => r.key), ['agents', 'npm-release', 'discord'], 'the whole opt-in catalog is offered');
237
+ assert.deepEqual(rows.filter((r) => r.pressed).map((r) => r.key), ['npm-release', 'discord'], 'the game bundle is preselected');
237
238
  assert.equal(rows.find((r) => r.key === 'agents').pressed, false, 'and nothing else is');
238
239
  // each row carries the copy an owner can actually read
239
- assert.equal(rows[1].label, 'Dev boxes');
240
- assert.match(rows[1].summary, /cloud workspace/);
240
+ assert.equal(rows[1].label, 'npm releases');
241
+ assert.match(rows[1].summary, /package to the registry/);
241
242
  });
242
243
 
243
244
  test('a different type preselects a different bundle, and "not sure" preselects nothing', async () => {
244
- for (const [type, expected] of [['research', ['agents']], ['business', ['agents', 'dev-box']], ['non-profit', ['discord']], ['not-sure', []], [null, []]]) {
245
+ for (const [type, expected] of [['research', ['agents']], ['business', ['agents', 'npm-release']], ['non-profit', ['discord']], ['not-sure', []], [null, []]]) {
245
246
  const h = boot({ state: { projType: type } });
246
247
  await h.open();
247
248
  assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), expected, `type=${type}`);
@@ -272,8 +273,8 @@ test('a toggle flips one module and leaves the rest of the preselect standing',
272
273
  h.click('agents');
273
274
 
274
275
  // Assert
275
- assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['agents', 'dev-box']);
276
- assert.deepEqual(h.state.modules, ['agents', 'dev-box'], 'and the draft holds the real answer, in catalog order');
276
+ assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['agents', 'npm-release']);
277
+ assert.deepEqual(h.state.modules, ['agents', 'npm-release'], 'and the draft holds the real answer, in catalog order');
277
278
  assert.ok(h.saves() >= 2, 'each toggle is written to the draft');
278
279
  });
279
280
 
@@ -285,7 +286,7 @@ test('the first toggle is what turns "not answered" into an answer', async () =>
285
286
  assert.equal(h.forCreate(), undefined, 'so the create request carries no modules key');
286
287
 
287
288
  h.click('agents');
288
- assert.deepEqual(h.forCreate(), ['agents', 'dev-box', 'discord'], 'after a touch it carries the whole set');
289
+ assert.deepEqual(h.forCreate(), ['agents', 'npm-release', 'discord'], 'after a touch it carries the whole set');
289
290
  });
290
291
 
291
292
  test('switching every module off is a real answer, not a reset to the bundle', async () => {
@@ -293,7 +294,7 @@ test('switching every module off is a real answer, not a reset to the bundle', a
293
294
  // said "none", and Next must not hand them the bundle back.
294
295
  const h = boot({ state: { projType: 'game' } });
295
296
  await h.open();
296
- h.click('dev-box');
297
+ h.click('npm-release');
297
298
  h.click('discord');
298
299
 
299
300
  assert.deepEqual(h.state.modules, []);
@@ -314,7 +315,7 @@ test('Skip walks past WITHOUT answering, so the server applies the bundle the pa
314
315
  assert.deepEqual(h.shown, [5], 'it advances to the Home step');
315
316
  assert.equal(h.state.modules, null, 'and answers nothing');
316
317
  assert.equal(h.forCreate(), undefined);
317
- assert.deepEqual(h.picked(), ['dev-box', 'discord'], 'while the set they were shown is still the set they get');
318
+ assert.deepEqual(h.picked(), ['npm-release', 'discord'], 'while the set they were shown is still the set they get');
318
319
  });
319
320
 
320
321
  test('Skip does not erase a toggle already made', async () => {
@@ -325,7 +326,7 @@ test('Skip does not erase a toggle already made', async () => {
325
326
  h.click('agents');
326
327
  h.skip();
327
328
 
328
- assert.deepEqual(h.state.modules, ['agents', 'dev-box', 'discord']);
329
+ assert.deepEqual(h.state.modules, ['agents', 'npm-release', 'discord']);
329
330
  });
330
331
 
331
332
  test('Next CONFIRMS the set on screen — looking at it and accepting it is not the same as never being asked', async () => {
@@ -334,8 +335,8 @@ test('Next CONFIRMS the set on screen — looking at it and accepting it is not
334
335
 
335
336
  h.next();
336
337
 
337
- assert.deepEqual(h.state.modules, ['dev-box', 'discord'], 'the preselect becomes the answer');
338
- assert.deepEqual(h.forCreate(), ['dev-box', 'discord'], 'and rides the create request');
338
+ assert.deepEqual(h.state.modules, ['npm-release', 'discord'], 'the preselect becomes the answer');
339
+ assert.deepEqual(h.forCreate(), ['npm-release', 'discord'], 'and rides the create request');
339
340
  });
340
341
 
341
342
  test('changing the project type re-preselects while nothing has been said, and never after', async () => {
@@ -414,7 +415,7 @@ test('the review names the set even when the picker was never opened (task 10023
414
415
  // and the module NAMES are not known. "None — just the core" would be a false
415
416
  // promise on the last screen before Create: the server is about to apply the
416
417
  // type's bundle. The row says that instead, then repaints when the read lands.
417
- const h = boot({ state: { projType: 'game', step: 7 } });
418
+ const h = boot({ state: { projType: 'game', step: 8 } }); // 8 = the review since task 1004351
418
419
 
419
420
  // Arrange + Act — the review asks for the value before the read can answer
420
421
  const first = h.extrasRow();
@@ -425,7 +426,7 @@ test('the review names the set even when the picker was never opened (task 10023
425
426
 
426
427
  await h.settle();
427
428
  assert.equal(h.reviews(), 1, 'the review repaints itself once the names are known');
428
- assert.equal(h.extrasRow(), 'Dev boxes, Discord', 'and then it names them');
429
+ assert.equal(h.extrasRow(), 'npm releases, Discord', 'and then it names them');
429
430
  });
430
431
 
431
432
  test('the review reads an answered picker back, empty included', async () => {
@@ -454,26 +455,26 @@ test('the team shape adjusts the type bundle, and the panel says why in the owne
454
455
  // Act
455
456
  await h.open();
456
457
 
457
- // Assert — the shared environment came off, and the reason is on screen
458
+ // Assert — the release pipeline came off, and the reason is on screen
458
459
  assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['discord'],
459
- 'solo drops the dev box the game bundle would have preselected');
460
+ 'solo drops npm releases, which the game bundle would have preselected');
460
461
  assert.equal(h.whyShown(), true);
461
- assert.deepEqual(h.why(), ['You are building this alone, so the shared cloud environment is off.']);
462
+ assert.deepEqual(h.why(), ['You are building this alone, so the release pipeline is off.']);
462
463
  });
463
464
 
464
465
  test('a rule that changed nothing claims nothing', async () => {
465
- // small-team ADDS dev-box, and the game bundle already carries it. A reason
466
+ // small-team ADDS npm-release, and the game bundle already carries it. A reason
466
467
  // shown for a change that did not happen is a claim the owner cannot check.
467
468
  const h = boot({ state: { projType: 'game', teamShape: 'small-team' }, payload: TAILORED });
468
469
  await h.open();
469
470
 
470
- assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['dev-box', 'discord']);
471
+ assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['npm-release', 'discord']);
471
472
  // No adjustment, so no adjustment-sentence — the rule still claims nothing. But the
472
473
  // panel is not silent: the TYPE explains its own preselect (task 1003684), because two
473
474
  // extras switched on with no reason beside them is the state ADR 0237 calls worse than
474
475
  // no preselect at all. The rule's voice and the type's voice are different things.
475
- assert.deepEqual(h.why(), ['Somewhere for players to gather, and a place to walk a change through before anyone plays it.']);
476
- assert.ok(!h.why().some((s) => /shared cloud environment|somewhere for it to gather is switched on/.test(s)),
476
+ assert.deepEqual(h.why(), ['Somewhere for players to gather, and a way to publish each release of the game.']);
477
+ assert.ok(!h.why().some((s) => /release pipeline|somewhere for it to gather is switched on/.test(s)),
477
478
  'and no RULE sentence is invented for a change that did not happen');
478
479
  assert.equal(h.whyShown(), true, 'the block carries the type\'s reason');
479
480
  });
@@ -496,7 +497,7 @@ test('answering the About step later re-preselects, with no second read', async
496
497
  h.state.teamShape = 'small-team';
497
498
  h.render();
498
499
 
499
- assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['agents', 'dev-box']);
500
+ assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['agents', 'npm-release']);
500
501
  assert.equal(h.fetches(), 1, 'and the answer cost no round trip');
501
502
  });
502
503
 
@@ -520,9 +521,9 @@ test('once the owner has answered, the reasons go — the panel stops arguing',
520
521
  await h.open();
521
522
  assert.equal(h.why().length, 1);
522
523
 
523
- h.click('dev-box');
524
+ h.click('npm-release');
524
525
 
525
- assert.deepEqual(h.state.modules, ['dev-box', 'discord'], 'their toggle stands over the rule');
526
+ assert.deepEqual(h.state.modules, ['npm-release', 'discord'], 'their toggle stands over the rule');
526
527
  assert.deepEqual(h.why(), [], 'and the rule stops explaining a set that is no longer its own');
527
528
  assert.equal(h.whyShown(), false);
528
529
  });
@@ -533,10 +534,10 @@ test('a payload with no byTeamShape degrades to the untailored bundle, never to
533
534
  const h = boot({ state: { projType: 'game', teamShape: 'solo' }, payload: PAYLOAD });
534
535
  await h.open();
535
536
 
536
- assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['dev-box', 'discord']);
537
+ assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['npm-release', 'discord']);
537
538
  // Degraded, but still explained: with no byTeamShape there is no adjustment to report,
538
539
  // and the type's own summary is exactly the untailored reason (task 1003684).
539
- assert.deepEqual(h.why(), ['Somewhere for players to gather, and a place to walk a change through before anyone plays it.']);
540
+ assert.deepEqual(h.why(), ['Somewhere for players to gather, and a way to publish each release of the game.']);
540
541
  });
541
542
 
542
543
  test('an off-vocabulary team shape adjusts nothing', async () => {
@@ -545,10 +546,10 @@ test('an off-vocabulary team shape adjusts nothing', async () => {
545
546
  const h = boot({ state: { projType: 'game', teamShape: 'a-huge-corporation' }, payload: TAILORED });
546
547
  await h.open();
547
548
 
548
- assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['dev-box', 'discord']);
549
+ assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['npm-release', 'discord']);
549
550
  // No rule fires, so nothing is CLAIMED about the unknown shape — the sentence shown is
550
551
  // the type's, which is true regardless of who is building it (task 1003684).
551
- assert.deepEqual(h.why(), ['Somewhere for players to gather, and a place to walk a change through before anyone plays it.']);
552
+ assert.deepEqual(h.why(), ['Somewhere for players to gather, and a way to publish each release of the game.']);
552
553
  });
553
554
 
554
555
  test('a couldn\'t-load read clears any reasons it had already painted', async () => {
@@ -617,17 +618,17 @@ test('the picker and the server resolve the same set for every answer an owner c
617
618
  // not move.
618
619
  // ─────────────────────────────────────────────────────────────────────────────
619
620
 
620
- const DEV_BOX = PAYLOAD.optional[1];
621
+ const NPM_RELEASE = PAYLOAD.optional[1];
621
622
 
622
623
  test('the matcher reads a row\'s own words — its name, its summary and its key', () => {
623
624
  // Still lexical, never semantic: the only words it knows are the ones the owner
624
625
  // can see, plus the two it cannot — the key, and the declared aliases below.
625
626
  const h = boot({ state: { projType: 'game' } });
626
627
 
627
- assert.equal(h.matches(DEV_BOX, 'boxes'), true, 'by a word in the name');
628
- assert.equal(h.matches(DEV_BOX, 'workspace'), true, 'by a word in the summary');
629
- assert.equal(h.matches(DEV_BOX, 'dev-box'), true, 'by the key');
630
- assert.equal(h.matches(DEV_BOX, 'discord'), false, 'and never by a word that is in none of them');
628
+ assert.equal(h.matches(NPM_RELEASE, 'releases'), true, 'by a word in the name');
629
+ assert.equal(h.matches(NPM_RELEASE, 'package'), true, 'by a word in the summary');
630
+ assert.equal(h.matches(NPM_RELEASE, 'npm-release'), true, 'by the key');
631
+ assert.equal(h.matches(NPM_RELEASE, 'discord'), false, 'and never by a word that is in none of them');
631
632
  });
632
633
 
633
634
  // ── task 1003500: the aliases, and the gap they close ────────────────────────
@@ -686,9 +687,9 @@ test('aliases narrow like any other word, and a row without them still matches o
686
687
  const discord = PAYLOAD.optional[2];
687
688
 
688
689
  assert.equal(h.matches(discord, 'chat news'), true, 'an alias AND a summary word still narrows to the row that has both');
689
- assert.equal(h.matches(discord, 'chat workspace'), false, 'an alias does not rescue a term the row cannot answer');
690
- assert.equal(h.matches(DEV_BOX, 'chat'), false, 'and a row declaring no aliases is not matched by another row\'s');
691
- assert.equal(h.matches(DEV_BOX, 'workspace'), true, 'while its own words keep working untouched');
690
+ assert.equal(h.matches(discord, 'chat package'), false, 'an alias does not rescue a term the row cannot answer');
691
+ assert.equal(h.matches(NPM_RELEASE, 'chat'), false, 'and a row declaring no aliases is not matched by another row\'s');
692
+ assert.equal(h.matches(NPM_RELEASE, 'package'), true, 'while its own words keep working untouched');
692
693
  });
693
694
 
694
695
  test('typing an alias filters the band to that row, and the count says so', async () => {
@@ -704,14 +705,14 @@ test('typing an alias filters the band to that row, and the count says so', asyn
704
705
  test('a term matches as a SUBSTRING, so a half-typed word still finds its module', () => {
705
706
  const h = boot({ state: { projType: 'game' } });
706
707
 
707
- assert.equal(h.matches(DEV_BOX, 'work'), true, 'the owner is still typing');
708
- assert.equal(h.matches(DEV_BOX, 'ace'), true, 'even mid-word — a dumb filter beats a clever one that finds nothing');
708
+ assert.equal(h.matches(NPM_RELEASE, 'pack'), true, 'the owner is still typing');
709
+ assert.equal(h.matches(NPM_RELEASE, 'kag'), true, 'even mid-word — a dumb filter beats a clever one that finds nothing');
709
710
  });
710
711
 
711
712
  test('matching is case-insensitive in both directions', () => {
712
713
  const h = boot({ state: { projType: 'game' } });
713
714
 
714
- assert.equal(h.matches(DEV_BOX, 'DEV BOXES'), true, 'a shouted query finds a title-cased row');
715
+ assert.equal(h.matches(NPM_RELEASE, 'NPM RELEASES'), true, 'a shouted query finds a title-cased row');
715
716
  assert.equal(h.matches(PAYLOAD.optional[0], 'ai'), true, 'and a quiet one finds a shouted row');
716
717
  });
717
718
 
@@ -719,15 +720,15 @@ test('every term must match — typing more words narrows, it never widens', ()
719
720
  // AND, not OR. An owner adding a word is telling us the list is still too long.
720
721
  const h = boot({ state: { projType: 'game' } });
721
722
 
722
- assert.equal(h.matches(DEV_BOX, 'cloud change'), true, 'both words are in the row');
723
- assert.equal(h.matches(DEV_BOX, 'cloud discord'), false, 'one of them is not, so the row is out');
723
+ assert.equal(h.matches(NPM_RELEASE, 'package registry'), true, 'both words are in the row');
724
+ assert.equal(h.matches(NPM_RELEASE, 'package discord'), false, 'one of them is not, so the row is out');
724
725
  });
725
726
 
726
727
  test('an empty search is not a search — every row stays', () => {
727
728
  const h = boot({ state: { projType: 'game' } });
728
729
 
729
- assert.equal(h.matches(DEV_BOX, ''), true);
730
- assert.equal(h.matches(DEV_BOX, ' '), true, 'and a box holding only spaces is the same as an empty one');
730
+ assert.equal(h.matches(NPM_RELEASE, ''), true);
731
+ assert.equal(h.matches(NPM_RELEASE, ' '), true, 'and a box holding only spaces is the same as an empty one');
731
732
  });
732
733
 
733
734
  test('typing filters the band to the rows that answer, in catalog order', async () => {
@@ -737,21 +738,21 @@ test('typing filters the band to the rows that answer, in catalog order', async
737
738
  assert.equal(h.rows().length, 3, 'the whole catalog before anything is typed');
738
739
 
739
740
  // Act — a word from one row's NAME
740
- h.type('boxes');
741
+ h.type('releases');
741
742
 
742
743
  // Assert
743
- assert.deepEqual(h.rows().map((r) => r.key), ['dev-box']);
744
+ assert.deepEqual(h.rows().map((r) => r.key), ['npm-release']);
744
745
  });
745
746
 
746
747
  test('a summary word and a key find their row just as a name word does', async () => {
747
748
  const h = boot({ state: { projType: 'game' } });
748
749
  await h.open();
749
750
 
750
- h.type('workspace');
751
- assert.deepEqual(h.rows().map((r) => r.key), ['dev-box'], 'by summary');
751
+ h.type('package');
752
+ assert.deepEqual(h.rows().map((r) => r.key), ['npm-release'], 'by summary');
752
753
 
753
- h.type('dev-box');
754
- assert.deepEqual(h.rows().map((r) => r.key), ['dev-box'], 'by key');
754
+ h.type('npm-release');
755
+ assert.deepEqual(h.rows().map((r) => r.key), ['npm-release'], 'by key');
755
756
 
756
757
  h.type('DISCORD');
757
758
  assert.deepEqual(h.rows().map((r) => r.key), ['discord'], 'and case is not part of the question');
@@ -761,10 +762,10 @@ test('two terms narrow to the rows that carry both', async () => {
761
762
  const h = boot({ state: { projType: 'game' } });
762
763
  await h.open();
763
764
 
764
- h.type('cloud change');
765
- assert.deepEqual(h.rows().map((r) => r.key), ['dev-box']);
765
+ h.type('package registry');
766
+ assert.deepEqual(h.rows().map((r) => r.key), ['npm-release']);
766
767
 
767
- h.type('cloud discord');
768
+ h.type('package discord');
768
769
  assert.deepEqual(h.rows(), [], 'and no row carries both of these');
769
770
  });
770
771
 
@@ -785,7 +786,7 @@ test('a switched-on module the search is HIDING is still switched on', async ()
785
786
  // toggles, and a view must not be able to edit it.
786
787
  const h = boot({ state: { projType: 'game' } });
787
788
  await h.open();
788
- assert.deepEqual(h.picked(), ['dev-box', 'discord'], 'the game bundle is on');
789
+ assert.deepEqual(h.picked(), ['npm-release', 'discord'], 'the game bundle is on');
789
790
 
790
791
  // Act — search for the one module that is OFF, hiding both that are on
791
792
  h.type('agents');
@@ -793,7 +794,7 @@ test('a switched-on module the search is HIDING is still switched on', async ()
793
794
  // Assert
794
795
  assert.deepEqual(h.rows().map((r) => r.key), ['agents'], 'only the match is on screen');
795
796
  assert.equal(h.rows()[0].pressed, false, 'and it is off, as it was');
796
- assert.deepEqual(h.picked(), ['dev-box', 'discord'], 'the two hidden ones are still switched on');
797
+ assert.deepEqual(h.picked(), ['npm-release', 'discord'], 'the two hidden ones are still switched on');
797
798
  assert.equal(h.state.modules, null, 'and typing answered nothing on the owner\'s behalf');
798
799
  assert.match(h.filterNote(), /2 extras are switched on/, 'the count under the field says so out loud');
799
800
  assert.match(h.filterNote(), /including any this search is hiding/, 'and that some of them are hidden');
@@ -820,16 +821,16 @@ test('with nothing switched on the count says only what is shown', async () => {
820
821
  });
821
822
 
822
823
  test('the review names the hidden picks too — a filter never reaches the create request', async () => {
823
- const h = boot({ state: { projType: 'game', step: 7 } });
824
+ const h = boot({ state: { projType: 'game', step: 8 } }); // 8 = the review since task 1004351
824
825
  await h.open();
825
826
  h.click('agents');
826
- assert.deepEqual(h.forCreate(), ['agents', 'dev-box', 'discord']);
827
+ assert.deepEqual(h.forCreate(), ['agents', 'npm-release', 'discord']);
827
828
 
828
829
  h.type('discord');
829
830
 
830
831
  assert.deepEqual(h.rows().map((r) => r.key), ['discord'], 'one row on screen');
831
- assert.deepEqual(h.forCreate(), ['agents', 'dev-box', 'discord'], 'three modules in the answer');
832
- assert.equal(h.extrasRow(), 'Extra agents, Dev boxes, Discord', 'and the review names all three');
832
+ assert.deepEqual(h.forCreate(), ['agents', 'npm-release', 'discord'], 'three modules in the answer');
833
+ assert.equal(h.extrasRow(), 'Extra agents, npm releases, Discord', 'and the review names all three');
833
834
  });
834
835
 
835
836
  test('clearing the search brings every row back, untouched', async () => {
@@ -840,8 +841,8 @@ test('clearing the search brings every row back, untouched', async () => {
840
841
 
841
842
  h.type('');
842
843
 
843
- assert.deepEqual(h.rows().map((r) => r.key), ['agents', 'dev-box', 'discord']);
844
- assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['dev-box', 'discord'],
844
+ assert.deepEqual(h.rows().map((r) => r.key), ['agents', 'npm-release', 'discord']);
845
+ assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['npm-release', 'discord'],
845
846
  'with the preselect exactly as it was before the search');
846
847
  assert.equal(h.filterNote(), '', 'and the count goes quiet again');
847
848
  });
@@ -851,11 +852,11 @@ test('a toggle made while filtered lands in the answer in catalog order, and the
851
852
  const h = boot({ state: { projType: 'non-profit' } });
852
853
  await h.open();
853
854
 
854
- h.type('workspace');
855
- h.click('dev-box');
855
+ h.type('package');
856
+ h.click('npm-release');
856
857
 
857
- assert.deepEqual(h.state.modules, ['dev-box', 'discord'], 'catalog order, not click order');
858
- assert.deepEqual(h.rows().map((r) => r.key), ['dev-box'], 'and the search is still the search');
858
+ assert.deepEqual(h.state.modules, ['npm-release', 'discord'], 'catalog order, not click order');
859
+ assert.deepEqual(h.rows().map((r) => r.key), ['npm-release'], 'and the search is still the search');
859
860
  assert.equal(h.rows()[0].pressed, true, 'the row they just switched on shows it');
860
861
  });
861
862
 
@@ -241,11 +241,10 @@ test('no wall-clock promises, no operator vocabulary on the stranger’s path',
241
241
  assert.doesNotMatch(HUB, /about five minutes/, 'the empty state');
242
242
  assert.doesNotMatch(HUB, /a runner does the real work/, 'the mine fine print');
243
243
  assert.doesNotMatch(SCRIPT, /pinned platform core/, 'the review Shape row');
244
- /* The wizard creates hosting_shape 'cloud-host', whose owner-facing name became
245
- "cloud host" at ADR 0323 — "its own server" was ambiguous between that shape and
246
- a dedicated one, and said nothing about WHO runs it. The assertion still guards the
247
- same property this test is about: owner vocabulary, and no wall-clock promise. */
248
- assert.match(SCRIPT, /\['Shape', 'Cloud host — we stand it up and run it for you, on the platform’s current release'\]/);
244
+ /* Since ADR 0345 (task 1004351) the review names the HALL as hosted by us and gives the
245
+ APP its own row. The assertion still guards the same property this test is about:
246
+ owner vocabulary, and no wall-clock promise. */
247
+ assert.ok(SCRIPT.includes("['Builders\\' hall', 'Hosted by us, on the platform’s current release'],"), 'the review names the hall as hosted by us');
249
248
  assert.doesNotMatch(HUB, /\/new-project<\/code> assistant/, 'the done panel’s closing line');
250
249
  assert.match(HUB, /Sign-in switches on after the GitHub steps|switch sign-in on/, 'the address step does not promise sign-in "just works"');
251
250
  assert.doesNotMatch(HUB, /Sign-in, sharing, everything just works/);
@@ -130,25 +130,25 @@ t('planCorePinRefresh: a trailing file: pin → refresh; a current one → no-op
130
130
  // The W3 shape: repo pins 1.19.2, control plane runs 1.19.196 → refresh (heals
131
131
  // the silent-rollback hazard before the next repo-driven deploy).
132
132
  assert.deepEqual(
133
- P.planCorePinRefresh({ pinnedDep: 'file:vendor/bongos-core-1.19.2.tgz', coreVersion: '1.19.196' }),
133
+ P.planCorePinRefresh({ pinnedDep: 'file:vendor/bongos-core-1.19.2.tgz', version: '1.0.0', coreVersion: '1.19.196' }),
134
134
  { action: 'refresh', pinned: '1.19.2' });
135
135
  // Pin already matches the running core → nothing to commit.
136
136
  assert.deepEqual(
137
- P.planCorePinRefresh({ pinnedDep: 'file:vendor/bongos-core-1.19.196.tgz', coreVersion: '1.19.196' }),
137
+ P.planCorePinRefresh({ pinnedDep: 'file:vendor/bongos-core-1.19.196.tgz', version: '1.0.0', coreVersion: '1.19.196' }),
138
138
  { action: 'current', pinned: '1.19.196' });
139
139
  // A non-canonical file: pin can't be compared → refresh heals it to canonical.
140
140
  assert.equal(
141
- P.planCorePinRefresh({ pinnedDep: 'file:vendor/core.tgz', coreVersion: '1.19.196' }).action,
141
+ P.planCorePinRefresh({ pinnedDep: 'file:vendor/core.tgz', version: '1.0.0', coreVersion: '1.19.196' }).action,
142
142
  'refresh');
143
143
  });
144
144
 
145
145
  t('planCorePinRefresh: registry pins and absent deps are skipped, never rewritten', () => {
146
146
  // Registry pin (ADR 0134): npm resolves it within its range — not ours to vendor.
147
- assert.equal(P.planCorePinRefresh({ pinnedDep: '^1.19.100', coreVersion: '1.19.196' }).action, 'skip');
148
- assert.equal(P.planCorePinRefresh({ pinnedDep: '1.19.196', coreVersion: '1.19.196' }).action, 'skip');
147
+ assert.equal(P.planCorePinRefresh({ pinnedDep: '^1.19.100', version: '1.0.0', coreVersion: '1.19.196' }).action, 'skip');
148
+ assert.equal(P.planCorePinRefresh({ pinnedDep: '1.19.196', version: '1.0.0', coreVersion: '1.19.196' }).action, 'skip');
149
149
  // No @bongos/core dependency at all → not ours to invent.
150
- assert.equal(P.planCorePinRefresh({ pinnedDep: undefined, coreVersion: '1.19.196' }).action, 'skip');
151
- assert.equal(P.planCorePinRefresh({ pinnedDep: '', coreVersion: '1.19.196' }).action, 'skip');
150
+ assert.equal(P.planCorePinRefresh({ pinnedDep: undefined, version: '1.0.0', coreVersion: '1.19.196' }).action, 'skip');
151
+ assert.equal(P.planCorePinRefresh({ pinnedDep: '', version: '1.0.0', coreVersion: '1.19.196' }).action, 'skip');
152
152
  });
153
153
 
154
154
  t('safeVersionLabel: passes clean semver, rejects any shell metacharacter', () => {
@@ -248,7 +248,7 @@ await ta('dry-run measures nothing and refuses nothing, even on a full box; an u
248
248
 
249
249
  await ta('statusCapacityLine prints "box at N of M" for the CLI', async () => {
250
250
  delete process.env.PROVISION_BOX_CEILING;
251
- // fleetCapacity reads THIS host's /proc/meminfo (absent on a Windows dev box → default);
251
+ // fleetCapacity reads THIS host's /proc/meminfo (absent on a Windows machine → default);
252
252
  // the line must carry the count and the source whichever it is.
253
253
  const line = await statusCapacityLine(fakeDb(12));
254
254
  assert.match(line, /control-plane box: 12 of \d+ project slots taken \((measured|default)/);
@@ -147,7 +147,8 @@ test('a rule whose module the bundle already carries records no adjustment', ()
147
147
  });
148
148
 
149
149
  test('a removal rule whose module is absent records no adjustment', () => {
150
- // Arrange — `non-profit` is `discord` alone, so solo\'s dev-box removal is a no-op.
150
+ // Arrange — `non-profit` is `discord` alone, and `solo` names no live rule (the removal rule for
151
+ // it left with the dev-box module, task 1003886), so there is nothing to remove or to claim.
151
152
  const answers = { type: 'non-profit', team_shape: 'solo' };
152
153
 
153
154
  // Act
@@ -37,7 +37,6 @@ import {
37
37
  OPTIONAL_MODULES,
38
38
  STARTER_BUNDLES,
39
39
  HUB_ONLY_MODULES,
40
- RETIRED_MODULES,
41
40
  RULED_TEAM_SHAPES,
42
41
  bundleForProjectType,
43
42
  allStarterBundles,
@@ -336,9 +335,6 @@ test('the opt-in catalog declared for the picker IS the on-disk opt-in set', ()
336
335
  const onDisk = manifestsOnDisk();
337
336
  const optInOnDisk = [...onDisk.values()]
338
337
  .filter((m) => m.default !== true && !HUB_ONLY_MODULES.includes(m.key))
339
- // A RETIRED module is still on disk while its removal lands, and must already
340
- // be gone from the catalog — so it is the one permitted absence (task 1003886).
341
- .filter((m) => !RETIRED_MODULES.includes(m.key))
342
338
  .map((m) => m.key)
343
339
  .sort();
344
340
 
@@ -350,24 +346,6 @@ test('the opt-in catalog declared for the picker IS the on-disk opt-in set', ()
350
346
  'the picker\'s catalog has drifted from the manifests — a module was added, removed, or flipped its default');
351
347
  });
352
348
 
353
- test('a RETIRED module is withdrawn everywhere an owner could still reach it', () => {
354
- // The retirement is only real if it holds at every offer point at once. A key
355
- // left in any one of them is still a module a new project can end up with,
356
- // which is exactly what the withdrawal exists to stop (task 1003886).
357
- for (const key of RETIRED_MODULES) {
358
- assert.ok(!OPTIONAL_MODULES.some((m) => m.key === key),
359
- `retired module "${key}" is still a row the creation picker offers`);
360
- for (const [type, bundle] of Object.entries(STARTER_BUNDLES)) {
361
- assert.ok(!bundle.modules.includes(key),
362
- `retired module "${key}" is still preselected by the "${type}" bundle`);
363
- }
364
- // Posting it directly is a real answer that resolves to nothing, not a
365
- // non-answer: the key is DROPPED, exactly as an unknown one would be.
366
- assert.deepEqual(normalizeModuleSelection([key]), [],
367
- `retired module "${key}" is still accepted when an owner posts it directly`);
368
- }
369
- });
370
-
371
349
  test('every opt-in module carries owner-facing copy, and no module jargon rides along', () => {
372
350
  for (const row of OPTIONAL_MODULES) {
373
351
  assert.ok(row.label && row.label.length <= 40, `"${row.key}" needs a short label`);
@@ -1,7 +1,7 @@
1
1
  // tests/publish_branch_route.mjs
2
2
  //
3
3
  // Coverage for server-mediated branch publish (ADR 0055 / task 1025) — the path
4
- // that lets a dev-box builder (no GitHub push credential) ship: the box uploads a
4
+ // that lets a builder on a checkout with no GitHub push credential ship: the client uploads a
5
5
  // thin git bundle, the server pushes it + opens the PR + auto-merges with the
6
6
  // server-side GITHUB_PUSH_TOKEN.
7
7
  //
@@ -9,12 +9,10 @@
9
9
  // 1. src/bongos/github-push.js — the push/PR/status logic, exercised through the
10
10
  // injectable exec + fetch seams (deps.exec / deps.fetchImpl / deps.token /
11
11
  // deps.repoInfo / deps.mkdtemp / deps.rmtmp).
12
- // 2. The route wiring — a STRUCTURAL guard that POST/GET publish* are
13
- // requireBuilder and DO NOT carry auth.allowBoxScope. That absence is what
14
- // makes a box-scoped session 403 (#919/ADR 0053). box_scope_session.mjs
15
- // proves requireBuilder's box gate; this proves the publish routes never opt
16
- // out of it — a future edit that added allowBoxScope would reintroduce the
17
- // "a leaked box token can ship arbitrary code" hole, and this test fails first.
12
+ // 2. The route wiring — a STRUCTURAL guard that POST/GET publish* and the
13
+ // attest route are requireBuilder-gated, so an unauthenticated caller (or a
14
+ // session of an undeclared source — tests/government_session_scope.mjs) can
15
+ // never publish.
18
16
  //
19
17
  // Run: node tests/publish_branch_route.mjs
20
18
 
@@ -407,8 +405,7 @@ await t('getPublishStatus: merged, workflow EXISTS but no successful run yet →
407
405
  });
408
406
 
409
407
  // ===========================================================================
410
- // Layer 2 — STRUCTURAL guard: the publish routes are requireBuilder and carry
411
- // NO allowBoxScope (so a box-scoped session is denied — #919 regression guard).
408
+ // Layer 2 — STRUCTURAL guard: the publish routes are requireBuilder-gated.
412
409
  // ===========================================================================
413
410
  function findRouteLayer(router, routePath, method) {
414
411
  return router.stack.find(
@@ -419,36 +416,31 @@ function handlesOf(layer) {
419
416
  return layer.route.stack.map((s) => s.handle);
420
417
  }
421
418
 
422
- await t('POST /tasks/:id/publish-branch: requireBuilder present, allowBoxScope ABSENT', async () => {
419
+ await t('POST /tasks/:id/publish-branch: requireBuilder present', async () => {
423
420
  const router = buildTasksRouter();
424
421
  const layer = findRouteLayer(router, '/tasks/:id/publish-branch', 'post');
425
422
  assert.ok(layer, 'the publish-branch POST route must be registered');
426
423
  const handles = handlesOf(layer);
427
424
  assert.ok(handles.includes(auth.requireBuilder), 'must be requireBuilder-gated');
428
- assert.ok(!handles.includes(auth.allowBoxScope), 'must NOT carry allowBoxScope — a box session must be denied (#919)');
429
425
  });
430
426
 
431
- await t('GET /tasks/:id/publish-status: requireBuilder present, allowBoxScope ABSENT', async () => {
427
+ await t('GET /tasks/:id/publish-status: requireBuilder present', async () => {
432
428
  const router = buildTasksRouter();
433
429
  const layer = findRouteLayer(router, '/tasks/:id/publish-status', 'get');
434
430
  assert.ok(layer, 'the publish-status GET route must be registered');
435
431
  const handles = handlesOf(layer);
436
432
  assert.ok(handles.includes(auth.requireBuilder), 'must be requireBuilder-gated');
437
- assert.ok(!handles.includes(auth.allowBoxScope), 'must NOT carry allowBoxScope');
438
433
  });
439
434
 
440
435
  // task 1169 (ADR 0058): the laptop ci-land attest endpoint. It posts the trustless
441
- // gate-author-trust commit status for a Metic+ author, so the SAME #919 box-scope
442
- // guard the publish routes carry applies here — a box session must reauth, never
443
- // self-attest. The structural guard proves it never opts out of requireBuilder's
444
- // box gate (no allowBoxScope), matching publish-branch / publish-status exactly.
445
- await t('POST /tasks/:id/attest-gate: requireBuilder present, allowBoxScope ABSENT', async () => {
436
+ // gate-author-trust commit status for a Metic+ author, so it carries the same
437
+ // requireBuilder gate as publish-branch / publish-status.
438
+ await t('POST /tasks/:id/attest-gate: requireBuilder present', async () => {
446
439
  const router = buildTasksRouter();
447
440
  const layer = findRouteLayer(router, '/tasks/:id/attest-gate', 'post');
448
441
  assert.ok(layer, 'the attest-gate POST route must be registered');
449
442
  const handles = handlesOf(layer);
450
443
  assert.ok(handles.includes(auth.requireBuilder), 'must be requireBuilder-gated');
451
- assert.ok(!handles.includes(auth.allowBoxScope), 'must NOT carry allowBoxScope — a box session must reauth, never self-attest (#919)');
452
444
  });
453
445
 
454
446
  // task 1002645: the attest call is the ONE moment the laptop ci-land tells the