@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
@@ -286,6 +286,11 @@ const TASK_SRC = read('task.js');
286
286
  // task.html loads md-reader.js ahead of task.js (task 1003465), so the sandbox
287
287
  // does the same: the description renders through it.
288
288
  const MD_SRC = read('md-reader.js');
289
+ // task 1003459: task.js now destructures window.OTBClaimAction at eval time
290
+ // (its Claim it button) — task.html loads claim-action.js ahead of task.js,
291
+ // so the sandbox does too. Real file, not a stub: it is dependency-free and
292
+ // DOM-free, so running it is exactly as cheap as stubbing it.
293
+ const CLAIM_ACTION_SRC = read('claim-action.js');
289
294
 
290
295
  // grade / sessionLogs / claims (task 1003437): left undefined, the stub answers
291
296
  // like a client that never asked (the key is absent); null or an array answers
@@ -400,6 +405,7 @@ function renderTask(task, { lastClaim = null, viewer = null, goal = null, goalsL
400
405
  sandbox.globalThis = sandbox;
401
406
  vm.createContext(sandbox);
402
407
  if (withReader) vm.runInContext(MD_SRC, sandbox, { filename: 'md-reader.js' });
408
+ vm.runInContext(CLAIM_ACTION_SRC, sandbox, { filename: 'claim-action.js' });
403
409
  vm.runInContext(TASK_SRC, sandbox, { filename: 'task.js' });
404
410
  assert.ok(onReady, 'task.js registers its DOMContentLoaded boot');
405
411
  return {
@@ -326,6 +326,57 @@ try {
326
326
  assert.match(html, /no longer match the page/);
327
327
  assert.match(html, /hero: “<b>new<\/b> line” \(added\)/);
328
328
  assert.equal(b.el('tw-notice').querySelectorAll('b').length, 0);
329
+ // The panel names the same lines, escaped too.
330
+ assert.equal(b.el('tw-refused').hidden, false);
331
+ assert.match(b.el('tw-refused-list').innerHTML, /hero: “<b>new<\/b> line” \(added\)/);
332
+ assert.equal(b.el('tw-refused-list').querySelectorAll('b').length, 0);
333
+ });
334
+
335
+ // Task 1004354: the owner's Google Docs upload was refused and nothing on
336
+ // the screen said so where they were looking.
337
+ await test('a refused upload opens a panel over the page: what happened, the next step, and a way on', async () => {
338
+ const b = bootEditor({ answers: {
339
+ 'GET /copy-desk/pages/landing:index/draft': ok(editorRead()),
340
+ 'FETCH POST /copy-desk/pages/landing:index/draft.docx': { ok: false, data: { error: { code: 'wrong_page', details: { page_details: false, mismatched: ['title', 'headings'], what_this_means: 'This file is not the download of this page: its title or its section headings are not this page\'s. Nothing was saved.' } } } },
341
+ } });
342
+ await b.settle();
343
+ assert.equal(b.el('tw-refused').hidden, true, 'closed until a refusal');
344
+ b.el('tw-file').fire('change', { target: { files: [{ type: '' }], value: 'x' } });
345
+ await b.settle();
346
+ assert.equal(b.el('tw-refused').hidden, false);
347
+ assert.equal(b.el('tw-refused-title').textContent, 'This file is a different page.');
348
+ assert.equal(boot.focused, b.el('tw-refused-title'), 'focus moves to the panel');
349
+ assert.match(b.el('tw-refused-what').textContent, /not the download of this page/);
350
+ assert.match(b.el('tw-refused-next').textContent, /Pick the file you downloaded from this page/);
351
+ assert.equal(b.el('tw-refused-list').hidden, true, 'no lines to name');
352
+ assert.equal(b.el('tw-refused-download').hidden, false);
353
+ assert.equal(b.el('tw-bar').inert, true);
354
+ assert.equal(b.el('tw-paper').inert, true);
355
+ assert.equal(b.el('tw-upload').textContent, 'upload .docx');
356
+ // Close: the page is back, and the paper still says what happened.
357
+ await b.click('tw-refused-close');
358
+ assert.equal(b.el('tw-refused').hidden, true);
359
+ assert.equal(b.el('tw-bar').inert, false);
360
+ assert.equal(b.el('tw-paper').inert, false);
361
+ assert.equal(boot.focused, b.el('tw-upload'));
362
+ assert.match(b.el('tw-notice').innerHTML, /not the download of this page/);
363
+ // Download again from the panel: it closes, and the page downloads.
364
+ b.el('tw-file').fire('change', { target: { files: [{ type: '' }], value: 'x' } });
365
+ await b.settle();
366
+ await b.click('tw-refused-download');
367
+ await b.settle();
368
+ assert.equal(b.el('tw-refused').hidden, true);
369
+ assert.equal(b.el('tw-download').textContent, 'downloaded .docx');
370
+ });
371
+
372
+ await test('each refusal the upload can answer has its own headline and next step', () => {
373
+ for (const code of ['wrong_page', 'reading_moved', 'structure_changed', 'not_a_docx', 'upload_too_large', 'page_not_held', 'page_in_flight']) {
374
+ const r = lib.uploadRefusal(code);
375
+ assert.ok(r.title && r.title !== 'Your file was not saved.', code);
376
+ assert.ok(r.next.length > 20, code);
377
+ }
378
+ assert.equal(lib.uploadRefusal('something_new').title, 'Your file was not saved.');
379
+ assert.match(lib.uploadRefusal('not_a_docx').next, /Google Docs/);
329
380
  });
330
381
 
331
382
  await test('submit: saves first, then "Home is in.", next up, and take next opens the next page', async () => {
@@ -409,7 +460,7 @@ try {
409
460
  assert.ok(HTML.indexOf('tweak-editor-lib.js') < HTML.indexOf('tweak-editor.js?'), 'the lib loads first');
410
461
  assert.match(HTML, /shell\.js\?v=2026-09-28-archive/);
411
462
  assert.equal(/fonts\.googleapis\.com\/css/.test(HTML), false);
412
- for (const id of ['tw-bar', 'tw-counter', 'tw-open', 'tw-download', 'tw-upload', 'tw-file', 'tw-submit', 'tw-paper', 'tw-doc', 'tw-notice', 'tw-done', 'tw-take', 'tw-pick', 'tw-picks']) {
463
+ for (const id of ['tw-bar', 'tw-counter', 'tw-open', 'tw-download', 'tw-upload', 'tw-file', 'tw-submit', 'tw-paper', 'tw-doc', 'tw-notice', 'tw-done', 'tw-take', 'tw-pick', 'tw-picks', 'tw-refused', 'tw-refused-title', 'tw-refused-list', 'tw-refused-next', 'tw-refused-download', 'tw-refused-close']) {
413
464
  assert.ok(new RegExp(`id="${id}"`).test(HTML), `#${id} is in the markup`);
414
465
  }
415
466
  assert.match(HTML, />← studio</);
package/tests/helpers.mjs CHANGED
@@ -58,7 +58,7 @@ export function makeSqlAwareClient(handler) {
58
58
  // tests/ is PUBLISHABLE, but the paths some tests assert are on
59
59
  // publish-manifest.js's PROTECTED_TOPOLOGY_EXCLUDE list (`.claude/hooks/`,
60
60
  // `.claude/settings.json`, `infra/`, `.github/`, `scripts/deploy/`) or are
61
- // instance-only content (`devbox-app/`, `config/<instance>.json`). A neutral
61
+ // instance-only content (`config/<instance>.json`). A neutral
62
62
  // core checkout carries none of it, so an unconditional load CRASHED the file
63
63
  // at import and red-X'd the unit gate for every consumer.
64
64
  //
@@ -3,7 +3,7 @@
3
3
  // tests/ is publishable, but some tests assert paths the neutral core deliberately does
4
4
  // not carry: publish-manifest.js's PROTECTED_TOPOLOGY_EXCLUDE (`.claude/hooks/`,
5
5
  // `.claude/settings.json`, `infra/`, `.github/`, `scripts/deploy/`) plus instance-only
6
- // content (`devbox-app/`, `config/<instance>.json`, an uninstalled module). Those files
6
+ // content (`config/<instance>.json`, an uninstalled module). Those files
7
7
  // used to CRASH at import and red-X the unit gate for every consumer; they now skip
8
8
  // cleanly via the helpers in tests/helpers.mjs and run in full wherever the topology is
9
9
  // materialized.
@@ -33,8 +33,7 @@ const ALLOWED = {
33
33
  allow_rule_guard: '.claude/hooks/allow-rule-guard.js (package-excluded)',
34
34
  art_stage: 'scripts/gds/art-stage.js (art-pipeline tooling, not in the core)',
35
35
  bongos_downloads: 'config/bongos-cli.json (instance content)',
36
- box_dns_repoint: 'infra/box-dns-cloudflare.sh (host topology)',
37
- box_ship_permissions: '.claude/settings.json (package-excluded)',
36
+ cli_allowlist_permissions: '.claude/settings.json (package-excluded)',
38
37
  conductor: '.claude/hooks/conductor.js (package-excluded)',
39
38
  deploy_instance: 'scripts/deploy/deploy-instance.sh (host topology)',
40
39
  eviction_policy: '.claude/hooks/eviction-policy.js (package-excluded)',
@@ -94,5 +93,5 @@ test('the ratchet is non-vacuous and actually detects a skip', () => {
94
93
  assert.ok(actual.length >= 15, `expected the known skip set, found ${actual.length}`);
95
94
  assert.ok(actual.includes('stop_refcheck'), 'the hand-rolled precedent must still be detected');
96
95
  assert.ok(actual.includes('conductor'), 'a requireHostModule caller must be detected');
97
- assert.ok(actual.includes('box_dns_repoint'), 'a skipUnlessPresent caller must be detected');
96
+ assert.ok(actual.includes('deploy_instance'), 'a skipUnlessPresent caller must be detected');
98
97
  });
@@ -0,0 +1,137 @@
1
+ // tests/html_comment_nesting.mjs — no served page's comment may quote comment
2
+ // syntax (task 1004359, BV2.TW24).
3
+ //
4
+ // An HTML comment ends at the FIRST closer, whatever its author meant. The head
5
+ // note of contact, privacy and terms quoted the apex-only sentinel verbatim, so
6
+ // the sentinel's closer ended the note early and the rest of the developer
7
+ // prose rendered as text at the top of all three pages — and the page reader
8
+ // filed it as page copy, so it reached Tweak Mode's editor too.
9
+ //
10
+ // The guard: in every served .html (each modules/<key>/public tree, plus
11
+ // public-docs), no comment's body holds another opener, and no closer stands
12
+ // outside a comment. Raw-text elements (script, style, textarea) are skipped:
13
+ // the browser never parses comments inside them.
14
+ //
15
+ // The file list is walked from disk, not git, so a new untracked page is
16
+ // scanned too, and a floor on the count keeps an emptied list from passing.
17
+ //
18
+ // Run: node --test tests/html_comment_nesting.mjs
19
+
20
+ import assert from 'node:assert/strict';
21
+ import { test } from 'node:test';
22
+ import fs from 'node:fs';
23
+ import path from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+
26
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
27
+ const OPEN = '<!' + '--';
28
+ const CLOSE = '--' + '>';
29
+
30
+ /** Every .html under a served tree: modules/<key>/public/** and public-docs/**. */
31
+ function servedHtml() {
32
+ const roots = [path.join(ROOT, 'public-docs')];
33
+ for (const key of fs.readdirSync(path.join(ROOT, 'modules'))) {
34
+ roots.push(path.join(ROOT, 'modules', key, 'public'));
35
+ }
36
+ const out = [];
37
+ const walk = (dir) => {
38
+ if (!fs.existsSync(dir)) return;
39
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
40
+ const p = path.join(dir, e.name);
41
+ if (e.isDirectory()) { if (e.name !== 'node_modules') walk(p); }
42
+ else if (e.name.endsWith('.html')) out.push(p);
43
+ }
44
+ };
45
+ roots.forEach(walk);
46
+ return out;
47
+ }
48
+
49
+ /** Blank raw-text elements, keeping newlines so line numbers still point true. */
50
+ function blankRawText(html) {
51
+ return html.replace(/<(script|style|textarea)\b[^>]*>[\s\S]*?<\/\1\s*>/gi,
52
+ (m) => m.replace(/[^\n]/g, ' '));
53
+ }
54
+
55
+ const lineAt = (s, i) => s.slice(0, i).split('\n').length;
56
+
57
+ /**
58
+ * The comment traps in one page, as "line N: why" strings (empty when clean).
59
+ * A comment runs from an opener to the FIRST closer after it, as the parser reads it.
60
+ */
61
+ function commentTraps(html) {
62
+ const s = blankRawText(html);
63
+ const traps = [];
64
+ let i = 0;
65
+ for (;;) {
66
+ const o = s.indexOf(OPEN, i);
67
+ const c = s.indexOf(CLOSE, i);
68
+ if (c !== -1 && (o === -1 || c < o)) {
69
+ traps.push(`line ${lineAt(s, c)}: a comment closer outside any comment (text before it has leaked onto the page)`);
70
+ i = c + CLOSE.length;
71
+ continue;
72
+ }
73
+ if (o === -1) break;
74
+ const end = s.indexOf(CLOSE, o + OPEN.length);
75
+ if (end === -1) { traps.push(`line ${lineAt(s, o)}: a comment that never closes`); break; }
76
+ const inner = s.slice(o + OPEN.length, end).indexOf(OPEN);
77
+ if (inner !== -1) {
78
+ traps.push(`line ${lineAt(s, o + OPEN.length + inner)}: a comment that quotes a comment opener (its own closer ends the outer comment early)`);
79
+ }
80
+ i = end + CLOSE.length;
81
+ }
82
+ return traps;
83
+ }
84
+
85
+ // ── the detector, at unit altitude ──────────────────────────────────────────
86
+
87
+ test('detector: the exact TW24 head note is caught', () => {
88
+ const html = `<head>\n${OPEN} Server-stamped: every ${OPEN}apex-only${CLOSE} region is REMOVED off\n the hub. ${CLOSE}\n<title>x</title>`;
89
+ const traps = commentTraps(html);
90
+ assert.equal(traps.length, 2, traps.join('\n'));
91
+ assert.match(traps[0], /^line 2: .*quotes a comment opener/);
92
+ assert.match(traps[1], /^line 3: .*closer outside any comment/);
93
+ });
94
+
95
+ test('detector: the fixed wording and the real sentinels are clean', () => {
96
+ const html = `${OPEN} every apex-only:start/end region is REMOVED off the hub ${CLOSE}\n`
97
+ + `<p>a</p>${OPEN}apex-only:start${CLOSE}<p>b</p>${OPEN}apex-only:end${CLOSE}`;
98
+ assert.deepEqual(commentTraps(html), []);
99
+ });
100
+
101
+ test('detector: a lone opener inside a comment is caught even with no stray closer', () => {
102
+ assert.equal(commentTraps(`${OPEN} see ${OPEN} here ${CLOSE}`).length, 1);
103
+ });
104
+
105
+ test('detector: an unclosed comment is caught', () => {
106
+ assert.match(commentTraps(`<p>a</p>${OPEN} never ends`)[0], /never closes/);
107
+ });
108
+
109
+ test('detector: comment syntax inside script, style and textarea is ignored', () => {
110
+ const html = `<script>const re = /${OPEN}x${CLOSE}/; if (a ${CLOSE} b) {}</script>`
111
+ + `<style>/* ${CLOSE} */</style><textarea>${OPEN}</textarea>`;
112
+ assert.deepEqual(commentTraps(html), []);
113
+ });
114
+
115
+ test('detector: line numbers survive a blanked script above the trap', () => {
116
+ const html = `<script>\n1\n2\n</script>\n${OPEN} a ${OPEN} b ${CLOSE}`;
117
+ assert.match(commentTraps(html)[0], /^line 5: /);
118
+ });
119
+
120
+ // ── every served page ───────────────────────────────────────────────────────
121
+
122
+ test('every served .html page is free of comment traps', () => {
123
+ const files = servedHtml();
124
+ // 39 pages on 2026-09-28 (hall 31, landing 5, status 2, public-docs 1); a
125
+ // floor well under that keeps a broken walk from passing on an empty list.
126
+ assert.ok(files.length >= 30, `expected at least 30 served pages, found ${files.length}`);
127
+ const names = files.map((f) => path.relative(ROOT, f).split(path.sep).join('/'));
128
+ for (const must of ['modules/public-landing/public/contact.html',
129
+ 'modules/public-landing/public/privacy.html', 'modules/public-landing/public/terms.html']) {
130
+ assert.ok(names.includes(must), `${must} is in the scanned set`);
131
+ }
132
+ const bad = [];
133
+ files.forEach((f, k) => {
134
+ for (const t of commentTraps(fs.readFileSync(f, 'utf8'))) bad.push(`${names[k]} ${t}`);
135
+ });
136
+ assert.deepEqual(bad, [], `comment traps:\n${bad.join('\n')}`);
137
+ });
@@ -31,11 +31,10 @@ function test(name, fn) {
31
31
  catch (err) { console.error(` FAIL ${name}`); console.error(' ', err.message); failed++; }
32
32
  }
33
33
 
34
- // Host topology means systemd UNITS, not the bare infra/ directory: the neutral core
35
- // carries no units and no launcher (task 1002470), but it DOES ship the four ON-BOX
36
- // reporter scripts in infra/ (task 1002699 — dev boxes clone this repo and wire them
37
- // as crons). So the real-tree unit lint is gated on *.service files being present,
38
- // and the launcher checks on core-node.sh — the synthetic counterfactuals always run.
34
+ // Host topology means systemd UNITS: the neutral core carries no units, no launcher
35
+ // and no infra/ directory (task 1002470), so the real-tree unit lint is gated on
36
+ // *.service files being present, and the launcher checks on core-node.sh — the
37
+ // synthetic counterfactuals always run.
39
38
  const INFRA_DIR = path.join(REPO, 'infra');
40
39
  const HAS_UNITS = fs.existsSync(INFRA_DIR) &&
41
40
  fs.readdirSync(INFRA_DIR).some((f) => f.endsWith('.service'));
@@ -50,10 +49,10 @@ hostTest('lint-infra-exec: every core-script unit resolves in a consumer layout
50
49
  const r = lintInfraExec({ instanceRoot: REPO, coreRoot: REPO });
51
50
  assert.deepEqual(r.violations, [], `expected no violations, got: ${JSON.stringify(r.violations)}`);
52
51
  assert.ok(r.ok, 'lint should pass');
53
- assert.equal(r.checked.length, 11, `expected all 11 core-script units repointed + resolving, got ${r.checked.length}`);
54
- // The three core CLIs the units invoke are all covered.
52
+ assert.ok(r.checked.length >= 2, `expected the core-script units repointed + resolving, got ${r.checked.length}`);
53
+ // The core CLIs the units invoke are all covered.
55
54
  const rels = new Set(r.checked.map((c) => c.rel));
56
- for (const rel of ['scripts/gds/box.js', 'scripts/gds/provision.js', 'scripts/gds/search-ingest-db.js']) {
55
+ for (const rel of ['scripts/gds/provision.js', 'scripts/gds/search-ingest-db.js']) {
57
56
  assert.ok(rels.has(rel), `expected a unit routing through ${rel}`);
58
57
  }
59
58
  });
@@ -64,9 +63,9 @@ test('lint-infra-exec: a bare repo-relative core ExecStart is flagged (counterfa
64
63
  try {
65
64
  fs.mkdirSync(path.join(tmp, 'infra'), { recursive: true });
66
65
  fs.mkdirSync(path.join(tmp, 'scripts', 'gds'), { recursive: true });
67
- fs.writeFileSync(path.join(tmp, 'scripts', 'gds', 'box.js'), '// probe\n');
66
+ fs.writeFileSync(path.join(tmp, 'scripts', 'gds', 'provision.js'), '// probe\n');
68
67
  fs.writeFileSync(path.join(tmp, 'infra', 'legacy.service'),
69
- '[Service]\nExecStart=/usr/bin/node scripts/gds/box.js run-intents --apply\n');
68
+ '[Service]\nExecStart=/usr/bin/node scripts/gds/provision.js run-intents --apply\n');
70
69
  const r = lintInfraExec({ instanceRoot: tmp, coreRoot: tmp });
71
70
  assert.ok(!r.ok, 'a bare repo-relative core ExecStart must fail the lint');
72
71
  assert.equal(r.violations.length, 1);
package/tests/init.mjs CHANGED
@@ -88,7 +88,6 @@ test('buildModulesConfig: every known module present, enabled ones true', () =>
88
88
  assert.equal(m['art-pipeline'], true);
89
89
  assert.equal(m.game, false);
90
90
  assert.equal(m.discord, false);
91
- assert.equal(m['dev-box'], false);
92
91
  });
93
92
 
94
93
  test('buildModulesConfig: vanilla (no modules) = all off', () => {
@@ -122,8 +121,8 @@ test('kickoffTasks: 7 base steps + 1 per enabled module', () => {
122
121
  const withArt = init.kickoffTasks(fullSpec());
123
122
  assert.equal(withArt.length, 8); // +1 Gemini key task
124
123
  assert.ok(withArt.some((t) => /Gemini/i.test(t.title)));
125
- const all = init.kickoffTasks(init.normalizeSpec({ modules: ['art-pipeline', 'discord', 'dev-box'], firstAdmin: 'x', repo: { owner: 'o', name: 'n' } }));
126
- assert.equal(all.length, 10);
124
+ const all = init.kickoffTasks(init.normalizeSpec({ modules: ['art-pipeline', 'discord'], firstAdmin: 'x', repo: { owner: 'o', name: 'n' } }));
125
+ assert.equal(all.length, 9);
127
126
  });
128
127
 
129
128
  test('kickoffTasks: includes the load-bearing owner-manual steps', () => {
@@ -141,7 +140,7 @@ test('kickoffTasks: includes the load-bearing owner-manual steps', () => {
141
140
  test('kickoffTasks: every kind/discipline satisfies the DB CHECK vocabulary', () => {
142
141
  const { VALID_KINDS, VALID_DISCIPLINES } = require('../modules/lifecycle/task-classifier.js');
143
142
  const all = [
144
- ...init.kickoffTasks(init.normalizeSpec({ modules: ['art-pipeline', 'discord', 'dev-box', 'game'],
143
+ ...init.kickoffTasks(init.normalizeSpec({ modules: ['art-pipeline', 'discord', 'game'],
145
144
  firstAdmin: 'x', repo: { owner: 'o', name: 'n' } })),
146
145
  init.productionTopologyKickoff(),
147
146
  ];
@@ -429,7 +428,7 @@ test('enabledModuleDeps: merges only the ENABLED modules’ declared deps', () =
429
428
  'art-pipeline': { sharp: '^0.34.5' },
430
429
  }[k] || {});
431
430
  const merged = init.enabledModuleDeps(
432
- { modules: { discord: true, game: false, 'art-pipeline': true, 'dev-box': false } },
431
+ { modules: { discord: true, game: false, 'art-pipeline': true } },
433
432
  { readDeps }
434
433
  );
435
434
  assert.deepEqual(merged, { 'discord.js': '^14.26.4', sharp: '^0.34.5' }); // game excluded (off)
@@ -30,17 +30,17 @@ const BRAND_VANILLA = {
30
30
  rankLabels: {},
31
31
  };
32
32
  const MODULES_OTB = {
33
- enabled: { game: true, 'art-pipeline': true, discord: true, 'dev-box': true },
33
+ enabled: { game: true, 'art-pipeline': true, discord: true },
34
34
  disciplines: ['engineer', 'artist'],
35
35
  };
36
36
  const MODULES_VANILLA = {
37
- enabled: { game: false, 'art-pipeline': false, discord: false, 'dev-box': false },
37
+ enabled: { game: false, 'art-pipeline': false, discord: false },
38
38
  disciplines: ['engineer'],
39
39
  };
40
40
  const REPO = { owner: 'example-owner', repo: 'example', branch: 'main', blob_base: 'x', tree_base: 'y' };
41
41
 
42
42
  test('manifest: schema id + headline name + embedded brand', () => {
43
- const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false, devBoxEnabled: true });
43
+ const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
44
44
  assert.equal(m.schema, MANIFEST_SCHEMA);
45
45
  assert.equal(m.name, 'Example World');
46
46
  assert.equal(m.brand, BRAND_OTB);
@@ -48,13 +48,13 @@ test('manifest: schema id + headline name + embedded brand', () => {
48
48
  });
49
49
 
50
50
  test('manifest: brand.project rides the manifest — the settings read-back a manage page needs (task 1003140 / ADR 0190)', () => {
51
- const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false, devBoxEnabled: true });
51
+ const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
52
52
  assert.deepEqual(m.brand.project, { platformVisibility: 'gated', joinability: 'invite_only' },
53
53
  'a client reads what the instance RESOLVED from here, never from the platform PATCH response');
54
54
  });
55
55
 
56
56
  test('manifest: rank ladder ordered entry→top, labelled from brand', () => {
57
- const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false, devBoxEnabled: true });
57
+ const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
58
58
  assert.deepEqual(m.ranks.map((r) => r.key), ['xenos', 'thetes', 'metic', 'archon']);
59
59
  assert.deepEqual(m.ranks.map((r) => r.tier), [1, 2, 3, 4]);
60
60
  assert.equal(m.ranks[0].label, 'Xenos');
@@ -62,46 +62,39 @@ test('manifest: rank ladder ordered entry→top, labelled from brand', () => {
62
62
  });
63
63
 
64
64
  test('manifest: rank label falls back to the key when unlabelled', () => {
65
- const m = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'no_repo_info' }, authConfigured: false, openEnrollment: false, devBoxEnabled: false });
65
+ const m = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'no_repo_info' }, authConfigured: false, openEnrollment: false });
66
66
  assert.equal(m.ranks[0].label, 'xenos');
67
67
  assert.equal(m.ranks[2].label, 'metic');
68
68
  });
69
69
 
70
70
  test('manifest: repo projects to public clone coords; null on error', () => {
71
- const ok = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false, devBoxEnabled: true });
71
+ const ok = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
72
72
  assert.deepEqual(ok.repo, { owner: 'example-owner', repo: 'example', branch: 'main', url: 'https://github.com/example-owner/example' });
73
- const none = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'no_repo_info' }, authConfigured: false, openEnrollment: false, devBoxEnabled: false });
73
+ const none = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'no_repo_info' }, authConfigured: false, openEnrollment: false });
74
74
  assert.equal(none.repo, null);
75
75
  });
76
76
 
77
77
  test('manifest: auth methods gated on configured', () => {
78
- const on = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false, devBoxEnabled: true });
78
+ const on = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
79
79
  assert.deepEqual(on.auth.methods, AUTH_METHODS);
80
80
  assert.equal(on.auth.configured, true);
81
- const off = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: false, devBoxEnabled: false });
81
+ const off = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: false });
82
82
  assert.deepEqual(off.auth.methods, []);
83
83
  assert.equal(off.auth.configured, false);
84
84
  });
85
85
 
86
86
  test('manifest: admission policy reflects open-enrollment flag', () => {
87
- const invite = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false, devBoxEnabled: true });
87
+ const invite = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
88
88
  assert.equal(invite.admission.policy, 'invite-only');
89
- const open = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: true, devBoxEnabled: true });
89
+ const open = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: true });
90
90
  assert.equal(open.admission.policy, 'open');
91
91
  });
92
92
 
93
- test('manifest: dev-box availability mirrors the module flag', () => {
94
- const on = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false, devBoxEnabled: true });
95
- assert.equal(on.devBox.available, true);
96
- const off = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: false, devBoxEnabled: false });
97
- assert.equal(off.devBox.available, false);
98
- });
99
-
100
93
  test('manifest: no server-only secrets/topology leak into the body', () => {
101
94
  // The assembler only ever reads the client-safe projections it is handed, so
102
95
  // even if a caller passed a fuller object, these server-only keys must not
103
96
  // appear. We assert on the serialized body (deep, key-name based).
104
- const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false, devBoxEnabled: true });
97
+ const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
105
98
  const json = JSON.stringify(m);
106
99
  for (const forbidden of ['cookieDomain', 'oauthOrigin', 'publicOrigin', 'envPrefix', 'client_secret', 'dbName', 'instanceDbName', 'token']) {
107
100
  assert.ok(!json.includes(forbidden), `manifest leaked "${forbidden}"`);
@@ -109,8 +102,8 @@ test('manifest: no server-only secrets/topology leak into the body', () => {
109
102
  });
110
103
 
111
104
  test('manifest: vanilla instance is fully populated (every field present)', () => {
112
- const m = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: false, devBoxEnabled: false });
113
- for (const k of ['schema', 'name', 'brand', 'modules', 'ranks', 'repo', 'auth', 'admission', 'devBox']) {
105
+ const m = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: false });
106
+ for (const k of ['schema', 'name', 'brand', 'modules', 'ranks', 'repo', 'auth', 'admission']) {
114
107
  assert.ok(k in m, `missing field ${k}`);
115
108
  }
116
109
  assert.equal(m.name, 'Cloud Bongos');
@@ -124,7 +117,7 @@ const buildInstanceRouter = instanceMod;
124
117
  import { strangerManifest } from '../src/bongos/routes/instance.js';
125
118
 
126
119
  const IDP = { origin: 'https://hub.example.com', client_id: 'proj-1' };
127
- const FULL = () => buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: false, openEnrollment: false, devBoxEnabled: true, idp: IDP });
120
+ const FULL = () => buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: false, openEnrollment: false, idp: IDP });
128
121
 
129
122
  test('stranger manifest: schema, name, brand.project, auth (idp kept), admission — and nothing else', () => {
130
123
  const s = strangerManifest(FULL());
@@ -136,13 +129,13 @@ test('stranger manifest: schema, name, brand.project, auth (idp kept), admission
136
129
  assert.deepEqual(s.auth, { methods: AUTH_METHODS, configured: true, idp: IDP });
137
130
  assert.deepEqual(s.admission, { policy: 'invite-only' });
138
131
  const json = JSON.stringify(s);
139
- for (const gone of ['example-owner', 'github.com', 'Straight example world', 'Hermeslines', 'drachmae', 'art-pipeline', 'Archon', 'devBox']) {
132
+ for (const gone of ['example-owner', 'github.com', 'Straight example world', 'Hermeslines', 'drachmae', 'art-pipeline', 'Archon']) {
140
133
  assert.ok(!json.includes(gone), `a stranger must not read "${gone}"`);
141
134
  }
142
135
  });
143
136
 
144
137
  test('stranger manifest: a brand with no project policy projects none', () => {
145
- const s = strangerManifest(buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: true, devBoxEnabled: false }));
138
+ const s = strangerManifest(buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: true }));
146
139
  assert.deepEqual(JSON.parse(JSON.stringify(s.brand)), { identity: { productName: 'Cloud Bongos' } });
147
140
  assert.equal(s.admission.policy, 'open');
148
141
  });
@@ -76,7 +76,7 @@ test('firstSignIn: never reads spec.repo or spec.firstAdmin (the steps that need
76
76
  });
77
77
 
78
78
  test('every variant satisfies the DB CHECK vocabulary (tasks_kind_check / tasks_discipline_check)', () => {
79
- const full = { ...spec(), modules: ['art-pipeline', 'discord', 'dev-box'] };
79
+ const full = { ...spec(), modules: ['art-pipeline', 'discord'] };
80
80
  const all = [
81
81
  ...kickoffChecklist(full),
82
82
  ...kickoffChecklist(full, { provisioned: true }),