@bongos/core 1.19.1072 → 1.19.1074

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/.bongos-core.json +320 -465
  2. package/.claude/skills/feedback/SKILL.md +2 -2
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +4 -60
  5. package/clients/bongos-client/index.cjs +4 -60
  6. package/clients/bongos-client/index.d.ts +5 -90
  7. package/clients/bongos-client/index.mjs +4 -60
  8. package/config/branding.neutral.json +1 -5
  9. package/config/modules.neutral.json +2 -3
  10. package/config/scheduled-routines.json +0 -2
  11. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
  12. package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
  13. package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
  14. package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
  15. package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +6 -2
  16. package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
  17. package/docs/adr/README.md +2 -1
  18. package/docs/api/openapi.json +171 -1661
  19. package/docs/api-reference.md +9 -41
  20. package/docs/architecture.md +16 -1
  21. package/docs/branding-contract.md +0 -3
  22. package/docs/copy-inventory.md +454 -479
  23. package/docs/copy-registry.json +746 -978
  24. package/docs/file-map.md +5 -34
  25. package/docs/module-api-changelog.md +4 -0
  26. package/docs/modules-contract.md +13 -1
  27. package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
  28. package/docs/onboarding/diagrams/README.md +3 -3
  29. package/docs/onboarding/diagrams/assertions.json +2 -22
  30. package/docs/onboarding/primer.md +9 -28
  31. package/docs/page-inventory.json +4 -1
  32. package/docs/page-readings.json +1032 -1077
  33. package/docs/recipes/render-pilot.md +29 -10
  34. package/migrations/core_257_module_store_registry.sql +87 -0
  35. package/migrations/core_258_drop_dev_box_tables.sql +55 -0
  36. package/modules/agents/module.json +1 -0
  37. package/modules/autonomy/module.json +1 -0
  38. package/modules/builder-settings/module.json +1 -0
  39. package/modules/builder-settings/render-prefs.js +4 -5
  40. package/modules/copy-desk/docx.js +78 -5
  41. package/modules/copy-desk/module.json +1 -0
  42. package/modules/copy-desk/routes/copy-desk.js +9 -9
  43. package/modules/copy-desk/tests/copy_docx.mjs +107 -1
  44. package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
  45. package/modules/discord/module.json +1 -0
  46. package/modules/economy/module.json +1 -0
  47. package/modules/economy/routes/credits.js +1 -2
  48. package/modules/government/catalog.js +13 -17
  49. package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
  50. package/modules/government/module.json +1 -0
  51. package/modules/government/protected-surfaces.json +3 -11
  52. package/modules/government/resolver.js +3 -46
  53. package/modules/government/routes/government.js +0 -2
  54. package/modules/government/session-scopes.js +43 -83
  55. package/modules/government/session-scopes.json +8 -18
  56. package/modules/grading/module.json +1 -0
  57. package/modules/hall-ui/module.json +1 -0
  58. package/modules/hall-ui/public/board-lib.js +2 -2
  59. package/modules/hall-ui/public/builders.js +29 -40
  60. package/modules/hall-ui/public/collab.html +1 -1
  61. package/modules/hall-ui/public/collab.js +1 -1
  62. package/modules/hall-ui/public/diagrams.html +3 -3
  63. package/modules/hall-ui/public/dom-utils.js +1 -1
  64. package/modules/hall-ui/public/gate.js +2 -2
  65. package/modules/hall-ui/public/gate.states.json +1 -1
  66. package/modules/hall-ui/public/hall-render.js +43 -79
  67. package/modules/hall-ui/public/index.html +5 -1
  68. package/modules/hall-ui/public/modules.js +1 -0
  69. package/modules/hall-ui/public/oversight.css +5 -5
  70. package/modules/hall-ui/public/palette.js +0 -11
  71. package/modules/hall-ui/public/primer.js +1 -1
  72. package/modules/hall-ui/public/sessions.html +1 -1
  73. package/modules/hall-ui/public/settings-sessions.js +1 -1
  74. package/modules/hall-ui/public/settings.css +0 -1
  75. package/modules/hall-ui/public/settings.html +4 -33
  76. package/modules/hall-ui/public/settings.js +3 -33
  77. package/modules/hall-ui/public/settings.states.json +2 -2
  78. package/modules/hall-ui/public/studio.html +1 -1
  79. package/modules/hall-ui/public/style.css +10 -13
  80. package/modules/hall-ui/public/task.html +4 -1
  81. package/modules/hall-ui/public/task.js +36 -1
  82. package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
  83. package/modules/hall-ui/public/tweak-editor.css +16 -0
  84. package/modules/hall-ui/public/tweak-editor.html +26 -3
  85. package/modules/hall-ui/public/tweak-editor.js +32 -1
  86. package/modules/hall-ui/public/work.js +1 -1
  87. package/modules/hall-ui/records/tweak-editor.md +1 -0
  88. package/modules/ideas/module.json +1 -0
  89. package/modules/lifecycle/db-claim-reads.js +1 -58
  90. package/modules/lifecycle/db-ship.js +0 -5
  91. package/modules/lifecycle/db.js +0 -4
  92. package/modules/lifecycle/kickoff-checklist.js +0 -4
  93. package/modules/lifecycle/lifecycle.js +0 -8
  94. package/modules/lifecycle/module.json +1 -0
  95. package/modules/lifecycle/routes/tasks.js +5 -7
  96. package/modules/memory/module.json +1 -0
  97. package/modules/npm-release/module.json +1 -0
  98. package/modules/onboarding/module.json +1 -0
  99. package/modules/onboarding/onboarding-state.js +17 -36
  100. package/modules/onboarding/routes/access-requests.js +3 -3
  101. package/modules/platform-identity/module.json +1 -0
  102. package/modules/platform-identity/platform-identity.js +13 -5
  103. package/modules/platform-identity/routes/sso.js +2 -2
  104. package/modules/platform-identity/tests/platform-identity.mjs +17 -3
  105. package/modules/provisioning/module.json +1 -0
  106. package/modules/provisioning/provisioning.js +2 -2
  107. package/modules/provisioning/starter-bundles.js +5 -25
  108. package/modules/public-landing/module.json +1 -0
  109. package/modules/public-landing/public/assets/cosmos.css +1 -1
  110. package/modules/public-landing/public/contact.html +1 -1
  111. package/modules/public-landing/public/privacy.html +1 -1
  112. package/modules/public-landing/public/projects.html +101 -32
  113. package/modules/public-landing/public/projects.probes.json +2 -2
  114. package/modules/public-landing/public/projects.states.json +4 -3
  115. package/modules/public-landing/public/terms.html +1 -1
  116. package/modules/security/module.json +1 -0
  117. package/modules/sessions/module.json +1 -0
  118. package/modules/specialities/module.json +1 -0
  119. package/modules/status-ui/module.json +1 -0
  120. package/modules/ui-design/module.json +1 -0
  121. package/package-lock.json +2 -2
  122. package/package.json +1 -1
  123. package/release-notes.json +84 -0
  124. package/scripts/gds/artifact-format.js +203 -0
  125. package/scripts/gds/audit-authorship.sh +1 -13
  126. package/scripts/gds/claim.js +0 -15
  127. package/scripts/gds/cli-lib.js +8 -8
  128. package/scripts/gds/diagram-facts.js +12 -23
  129. package/scripts/gds/do-api.js +13 -80
  130. package/scripts/gds/doc-cli-guard.js +2 -0
  131. package/scripts/gds/doctor.js +1 -1
  132. package/scripts/gds/feedback-latest.js +139 -9
  133. package/scripts/gds/fitness-checks-identity.js +2 -7
  134. package/scripts/gds/fitness-checks-write-validation.js +4 -0
  135. package/scripts/gds/fitness-ratchets.js +4 -4
  136. package/scripts/gds/fitness.js +10 -10
  137. package/scripts/gds/gen-diagrams.js +49 -71
  138. package/scripts/gds/http-api-client.js +12 -19
  139. package/scripts/gds/init.js +1 -1
  140. package/scripts/gds/lint-infra-exec.js +1 -1
  141. package/scripts/gds/local-preview-lib.js +8 -39
  142. package/scripts/gds/local-preview.js +2 -15
  143. package/scripts/gds/migration-namespace.js +3 -3
  144. package/scripts/gds/module-artifact.js +212 -0
  145. package/scripts/gds/module.js +95 -6
  146. package/scripts/gds/package-core.js +5 -116
  147. package/scripts/gds/provision-config.js +2 -2
  148. package/scripts/gds/provision-net.js +2 -3
  149. package/scripts/gds/provision.js +9 -9
  150. package/scripts/gds/publish-manifest.js +1 -2
  151. package/scripts/gds/regen-instance-docs.js +1 -1
  152. package/scripts/gds/run-unit-tests.js +2 -16
  153. package/scripts/gds/sandbox-stage.js +39 -341
  154. package/scripts/gds/ship-flow.js +6 -6
  155. package/scripts/gds/ship-land.js +3 -4
  156. package/scripts/gds/ship-regen.js +10 -41
  157. package/scripts/gds/ship.js +4 -24
  158. package/scripts/gds/start.js +0 -15
  159. package/scripts/hall-preview/README.md +5 -5
  160. package/scripts/hall-preview/server.js +6 -6
  161. package/scripts/render-diagrams.sh +1 -1
  162. package/src/bongos/api-errors.js +1 -1
  163. package/src/bongos/auth-github.js +2 -3
  164. package/src/bongos/auth.js +51 -126
  165. package/src/bongos/db-kernel.js +2 -3
  166. package/src/bongos/db.js +5 -28
  167. package/src/bongos/module-scope-map.js +14 -29
  168. package/src/bongos/module-store.js +141 -0
  169. package/src/bongos/route-rank-check.js +3 -16
  170. package/src/bongos/routes/auth.js +0 -116
  171. package/src/bongos/routes/builders.js +5 -15
  172. package/src/bongos/routes/instance.js +8 -14
  173. package/src/bongos/routes/me.js +6 -22
  174. package/src/bongos/routes/modules.js +83 -0
  175. package/src/bongos/routes.js +5 -0
  176. package/src/module-api.js +13 -6
  177. package/src/module-loader/catalog.js +3 -0
  178. package/src/module-loader/loader.js +4 -4
  179. package/src/module-loader/manifest-schema.js +15 -7
  180. package/src/module-seams.js +2 -3
  181. package/src/modules.js +42 -14
  182. package/tests/agents_spend_guard.mjs +1 -1
  183. package/tests/api_client.mjs +0 -15
  184. package/tests/auth_page_gate.mjs +10 -10
  185. package/tests/canonical_profile_url.mjs +1 -3
  186. package/tests/claim_action.mjs +4 -1
  187. package/tests/claim_from_task_and_home.mjs +127 -0
  188. package/tests/consumer_layout_boot.mjs +2 -0
  189. package/tests/copy_desk_page_docx.mjs +33 -2
  190. package/tests/core_upgrade_runner.mjs +6 -6
  191. package/tests/deploy_divergence_line.mjs +11 -11
  192. package/tests/diagram_facts_offset.mjs +1 -1
  193. package/tests/do_api.mjs +106 -0
  194. package/tests/feedback_latest.mjs +122 -0
  195. package/tests/fitness.mjs +44 -42
  196. package/tests/gate_approvals.mjs +0 -1
  197. package/tests/go_live.mjs +3 -3
  198. package/tests/government_abuse_matrix.mjs +20 -19
  199. package/tests/government_ownership_scope.mjs +33 -35
  200. package/tests/government_parity.mjs +2 -2
  201. package/tests/government_protected_surfaces.mjs +56 -37
  202. package/tests/government_require_permission.mjs +3 -3
  203. package/tests/government_seed.mjs +37 -4
  204. package/tests/government_session_scope.mjs +92 -366
  205. package/tests/hall_error_envelope.mjs +4 -0
  206. package/tests/hall_landing_boot.mjs +4 -1
  207. package/tests/hall_palette.mjs +1 -16
  208. package/tests/hall_record_world.mjs +6 -0
  209. package/tests/hall_tweak_editor.mjs +52 -1
  210. package/tests/host_topology_skips.mjs +1 -2
  211. package/tests/html_comment_nesting.mjs +137 -0
  212. package/tests/infra_exec_paths.mjs +9 -10
  213. package/tests/init.mjs +4 -5
  214. package/tests/instance_manifest.mjs +18 -25
  215. package/tests/kickoff_checklist.mjs +1 -1
  216. package/tests/lib_sh_resolution.mjs +0 -221
  217. package/tests/local_preview.mjs +0 -12
  218. package/tests/module-scope-map.mjs +9 -9
  219. package/tests/module_catalog.mjs +1 -1
  220. package/tests/module_cli.mjs +15 -15
  221. package/tests/module_contributions.mjs +6 -6
  222. package/tests/module_loader.mjs +6 -6
  223. package/tests/module_manifest.mjs +13 -4
  224. package/tests/module_store_publish.mjs +307 -0
  225. package/tests/module_store_publish_route.mjs +121 -0
  226. package/tests/module_store_registry_migration.mjs +70 -0
  227. package/tests/modules.mjs +56 -26
  228. package/tests/onboarding_route_signals.mjs +21 -31
  229. package/tests/onboarding_state.mjs +35 -49
  230. package/tests/permission_path.mjs +0 -3
  231. package/tests/platform_boot.mjs +1 -2
  232. package/tests/profile_route.mjs +1 -6
  233. package/tests/projects_hub.mjs +5 -5
  234. package/tests/projects_hub_app_step.mjs +138 -0
  235. package/tests/projects_hub_module_picker.mjs +2 -2
  236. package/tests/projects_hub_pre_uat.mjs +4 -5
  237. package/tests/provision.mjs +7 -7
  238. package/tests/provisioning_starter_bundles.mjs +0 -22
  239. package/tests/publish_branch_route.mjs +10 -18
  240. package/tests/publish_manifest.mjs +1 -2
  241. package/tests/rank_tier_single_source.mjs +1 -13
  242. package/tests/repo_map.mjs +3 -4
  243. package/tests/runner_drift.mjs +3 -21
  244. package/tests/sandbox_stage.mjs +57 -390
  245. package/tests/session_rename_fallback.mjs +0 -21
  246. package/tests/ship_error_shape.mjs +1 -1
  247. package/tests/task_visual_slots.mjs +31 -0
  248. package/tests/terms_acceptance.mjs +1 -1
  249. package/tests/upgrade.mjs +4 -4
  250. package/tests/watch_sealed_floor.mjs +0 -1
  251. package/tests/wizard_draft_resume.mjs +2 -2
  252. package/tests/wizard_intent_resume.mjs +21 -10
  253. package/tests/wizard_preselect_why.mjs +18 -18
  254. package/docs/onboarding/browser-terminal-guide.md +0 -73
  255. package/docs/recipes/managed-settings-remote-control.md +0 -416
  256. package/modules/dev-box/CLAUDE.md +0 -15
  257. package/modules/dev-box/box-access.js +0 -623
  258. package/modules/dev-box/box-credential.js +0 -94
  259. package/modules/dev-box/box-onboard.js +0 -486
  260. package/modules/dev-box/boxes.js +0 -1038
  261. package/modules/dev-box/db.js +0 -39
  262. package/modules/dev-box/module.json +0 -19
  263. package/modules/dev-box/routes/box.js +0 -1223
  264. package/scripts/gds/box-auth-check.js +0 -160
  265. package/scripts/gds/box-infra.js +0 -321
  266. package/scripts/gds/box-sync.js +0 -216
  267. package/scripts/gds/box.js +0 -1332
  268. package/scripts/gds/cf-tunnel.js +0 -558
  269. package/scripts/gds/smoke-box.sh +0 -150
  270. package/scripts/gds/tree-preflight.js +0 -241
  271. package/src/bongos/app-pair.js +0 -172
  272. package/tests/app_pair.mjs +0 -168
  273. package/tests/box_access.mjs +0 -578
  274. package/tests/box_auth_check.mjs +0 -114
  275. package/tests/box_code_staleness.mjs +0 -129
  276. package/tests/box_connect_e2e.mjs +0 -196
  277. package/tests/box_cost.mjs +0 -70
  278. package/tests/box_credential.mjs +0 -125
  279. package/tests/box_dns_repoint.mjs +0 -153
  280. package/tests/box_env.mjs +0 -64
  281. package/tests/box_host_keys.mjs +0 -126
  282. package/tests/box_infra_resolve.mjs +0 -246
  283. package/tests/box_onboard.mjs +0 -317
  284. package/tests/box_scope_session.mjs +0 -119
  285. package/tests/box_sweep_docs.mjs +0 -119
  286. package/tests/box_sync.mjs +0 -55
  287. package/tests/box_sync_scope_report.mjs +0 -126
  288. package/tests/box_task_scope.mjs +0 -169
  289. package/tests/box_task_scope_pause.mjs +0 -66
  290. package/tests/box_terminal_ssrf.mjs +0 -96
  291. package/tests/boxes.mjs +0 -1585
  292. package/tests/cf_ruleset_preflight.mjs +0 -142
  293. package/tests/cf_tunnel.mjs +0 -496
  294. package/tests/docs_scrubber_damage.mjs +0 -111
  295. package/tests/module_scope_active_claims.mjs +0 -94
  296. package/tests/ship_api_client_pathspec.mjs +0 -100
  297. package/tests/tree_preflight.mjs +0 -243
@@ -0,0 +1,138 @@
1
+ // tests/projects_hub_app_step.mjs — the create wizard's "where your app runs" step,
2
+ // EXECUTED (task 1004351, ADR 0345).
3
+ //
4
+ // The step is a native radio group, so almost all of its behaviour is one rule: the
5
+ // review may only ever name a place the owner could actually pick. Render is listed
6
+ // but disabled until the connect flow lands (task 1004352), and "decide later" is the
7
+ // owner's ruling for the default. A static regex cannot tell a guard that works from
8
+ // one that is never consulted, so — as tests/projects_hub_module_picker.mjs does for
9
+ // the picker — this slices the block out of projects.html by its own sentinels and
10
+ // runs it in `vm` under a small DOM stub.
11
+ //
12
+ // Run: node --test --test-reporter=tap tests/projects_hub_app_step.mjs
13
+
14
+ import test from 'node:test';
15
+ import assert from 'node:assert/strict';
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import vm from 'node:vm';
19
+ import { fileURLToPath } from 'node:url';
20
+
21
+ const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
22
+ const HUB = fs.readFileSync(path.join(ROOT, 'modules', 'public-landing', 'public', 'projects.html'), 'utf8').replace(/\r\n/g, '\n');
23
+
24
+ const START = '/* the app step (task 1004351)';
25
+ const END = ' function renderReview() {';
26
+ const from = HUB.indexOf(START);
27
+ const to = HUB.indexOf(END, from);
28
+ assert.ok(from > -1, 'the app-step block opens with its sentinel comment');
29
+ assert.ok(to > from, 'the app-step block ends where renderReview begins');
30
+ const BLOCK = HUB.slice(from, to);
31
+
32
+ const PANEL = (HUB.match(/<section class="panel" id="panel-app"[\s\S]*?<\/section>/) || [''])[0];
33
+
34
+ // Two radios, built from the real markup so the harness cannot drift from the page.
35
+ function radiosFromMarkup(renderEnabled) {
36
+ return [...PANEL.matchAll(/<input type="radio" name="appPlatform" value="([a-z-]+)"([^>]*)>/g)].map((m) => ({
37
+ name: 'appPlatform',
38
+ value: m[1],
39
+ disabled: m[1] === 'render' ? !renderEnabled && /\sdisabled/.test(m[2]) : /\sdisabled/.test(m[2]),
40
+ checked: /\schecked/.test(m[2]),
41
+ }));
42
+ }
43
+
44
+ function load({ draft = {}, renderEnabled = false } = {}) {
45
+ const inputs = radiosFromMarkup(renderEnabled);
46
+ let changeHandler = null;
47
+ const saves = [];
48
+ const root = {
49
+ querySelector(sel) {
50
+ if (sel === '#appOpts') return { addEventListener: (ev, fn) => { if (ev === 'change') changeHandler = fn; } };
51
+ throw new Error('unexpected selector ' + sel);
52
+ },
53
+ querySelectorAll(sel) {
54
+ assert.equal(sel, '#appOpts input[name=appPlatform]');
55
+ return inputs;
56
+ },
57
+ };
58
+ const state = Object.assign({ appPlatform: 'later' }, draft);
59
+ const ctx = { root, state, saveDraft: () => saves.push(state.appPlatform) };
60
+ vm.createContext(ctx);
61
+ vm.runInContext(BLOCK + '\nthis.api = { appPlatformChoice, syncAppStep, appRow };', ctx);
62
+ return { ...ctx.api, inputs, state, saves, change: (value) => changeHandler({ target: inputs.find((i) => i.value === value) }) };
63
+ }
64
+
65
+ test('the panel is real markup: Render listed but not pickable yet, Decide later the default', () => {
66
+ assert.ok(PANEL, '#panel-app exists');
67
+ assert.match(PANEL, /<input type="radio" name="appPlatform" value="render" disabled/, 'Render cannot be picked until task 1004352');
68
+ assert.match(PANEL, /<span class="soonTag">Coming soon<\/span>/, 'and says so in words, not by colour alone');
69
+ assert.match(PANEL, /<input type="radio" name="appPlatform" value="later" checked>/, 'Decide later is the default');
70
+ assert.match(PANEL, /We host your Builders' hall/, 'the step says the hall is ours to host (ADR 0345 D1), in the page\'s one spelling of it');
71
+ assert.match(PANEL, /<form data-next="8">/, 'Next leads to the review');
72
+ assert.match(PANEL, /data-back="6"/, 'Back returns to the address step');
73
+ });
74
+
75
+ test('the step sits between Address and Review in the panels map and on the rail', () => {
76
+ assert.match(HUB, /6: 'panel-address', 7: 'panel-app', 8: 'panel-review', 9: 'panel-done'/);
77
+ assert.match(HUB, /<li data-step="7"><span class="snum">7<\/span><span class="slbl">App<\/span><\/li>/);
78
+ assert.match(HUB, /<li data-step="8"><span class="snum">8<\/span><span class="slbl">Create<\/span><\/li>/);
79
+ assert.match(HUB, /data-back="7">← Back<\/button>\n\s*<button type="button" class="btn big" id="doCreate">/, "the review's Back returns to the app step");
80
+ assert.match(HUB, /if \(step === 7\) syncAppStep\(\);\n\s*if \(step === 8\) renderReview\(\);/, 'entering the step repaints it from the draft');
81
+ });
82
+
83
+ test('a fresh draft reads "decide later"', () => {
84
+ const w = load();
85
+ assert.equal(w.appPlatformChoice(), 'later');
86
+ assert.equal(w.appRow(), 'Not chosen yet — you can decide later');
87
+ });
88
+
89
+ test('a draft that names Render while Render is not pickable still reads "decide later"', () => {
90
+ // The rule: the review never names a place the owner could not actually pick.
91
+ const w = load({ draft: { appPlatform: 'render' } });
92
+ assert.equal(w.appPlatformChoice(), 'later');
93
+ assert.equal(w.appRow(), 'Not chosen yet — you can decide later');
94
+ w.syncAppStep();
95
+ assert.deepEqual(w.inputs.filter((i) => i.checked).map((i) => i.value), ['later'], 'and the step shows Decide later checked');
96
+ });
97
+
98
+ test('once Render is pickable, choosing it is what the review names', () => {
99
+ const w = load({ renderEnabled: true });
100
+ w.change('render');
101
+ assert.equal(w.state.appPlatform, 'render');
102
+ assert.deepEqual(w.saves, ['render'], 'the choice is saved to the draft');
103
+ assert.equal(w.appRow(), 'Render, on your own account');
104
+ w.syncAppStep();
105
+ assert.deepEqual(w.inputs.filter((i) => i.checked).map((i) => i.value), ['render']);
106
+ });
107
+
108
+ test('a change on a disabled radio is ignored, and an unknown or missing value reads "decide later"', () => {
109
+ const w = load();
110
+ w.change('render');
111
+ assert.equal(w.state.appPlatform, 'later', 'a disabled option never reaches the draft');
112
+ assert.deepEqual(w.saves, []);
113
+ for (const v of [undefined, '', 'vercel', 'x"]']) {
114
+ assert.equal(load({ draft: { appPlatform: v } }).appPlatformChoice(), 'later', `appPlatform=${JSON.stringify(v)}`);
115
+ }
116
+ });
117
+
118
+ test('the review carries the hall row and the app row in place of the old Shape row', () => {
119
+ const render = HUB.slice(HUB.indexOf('function renderReview()'));
120
+ const rows = render.slice(0, render.indexOf("$('reviewList').innerHTML"));
121
+ assert.ok(rows.includes("['Builders\\' hall', 'Hosted by us, on the platform’s current release'],"), 'the hall row');
122
+ assert.match(rows, /\['App', appRow\(\)\]/);
123
+ assert.doesNotMatch(rows, /\['Shape',/, 'the Shape row is gone');
124
+ });
125
+
126
+ test('the rail grid has one column per step, so adding a step never wraps the rail', () => {
127
+ // Adding the app step made the rail eight beats in a seven-column grid: at 1440 the
128
+ // Create beat wrapped onto a second row. The count lives in the shared sheet, so the
129
+ // two must be checked against each other, not each on its own.
130
+ const css = fs.readFileSync(path.join(ROOT, 'modules', 'public-landing', 'public', 'assets', 'cosmos.css'), 'utf8');
131
+ const cols = Number((css.match(/#view-new \.steps\{display:grid;grid-template-columns:repeat\((\d+),1fr\)/) || [])[1]);
132
+ const beats = [...HUB.matchAll(/<li[^>]*data-step="\d+"/g)].length;
133
+ assert.equal(cols, beats, 'the #view-new rail grid carries exactly one column per rail step');
134
+ });
135
+
136
+ test('the hall is spelled one way on this page', () => {
137
+ assert.doesNotMatch(HUB, /Builders Hall/, 'no "Builders Hall" beside the page\'s own "Builders\' hall"');
138
+ });
@@ -414,7 +414,7 @@ test('the review names the set even when the picker was never opened (task 10023
414
414
  // and the module NAMES are not known. "None — just the core" would be a false
415
415
  // promise on the last screen before Create: the server is about to apply the
416
416
  // type's bundle. The row says that instead, then repaints when the read lands.
417
- const h = boot({ state: { projType: 'game', step: 7 } });
417
+ const h = boot({ state: { projType: 'game', step: 8 } }); // 8 = the review since task 1004351
418
418
 
419
419
  // Arrange + Act — the review asks for the value before the read can answer
420
420
  const first = h.extrasRow();
@@ -820,7 +820,7 @@ test('with nothing switched on the count says only what is shown', async () => {
820
820
  });
821
821
 
822
822
  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 } });
823
+ const h = boot({ state: { projType: 'game', step: 8 } }); // 8 = the review since task 1004351
824
824
  await h.open();
825
825
  h.click('agents');
826
826
  assert.deepEqual(h.forCreate(), ['agents', 'dev-box', 'discord']);
@@ -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', () => {
@@ -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`);
@@ -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
@@ -57,8 +57,7 @@ const PUBLISH = [
57
57
  'modules/lifecycle/db.js',
58
58
  'modules/grading/grader.js',
59
59
  'modules/hall-ui/public/work.js',
60
- 'modules/dev-box/boxes.js',
61
- 'modules/provisioning/provisioning.js', // instance-hosting control plane — CORE like its sibling dev-box (task 2135)
60
+ 'modules/provisioning/provisioning.js', // instance-hosting control plane — CORE (task 2135)
62
61
  // served-at-runtime surfaces a standalone must carry (task 2078) — their absence
63
62
  // from the packaged core 404'd the hall UI + /docs on demo.cloudbongos.com
64
63
  'clients/bongos-client/bongos-client.global.js', // /lib/bongos-client.js (window.BongosClient)
@@ -12,8 +12,7 @@
12
12
  // modules/discord/discord-channels.js): dependency-free substrate
13
13
  // (permission-path-check runs in a bare git hook), source-parsed literals
14
14
  // (src/bongos/db.js — gate-review.js + diagram-facts.js regex its text),
15
- // standalone CLIs (sequence), the require-free dev-box
16
- // module template (boxes.js), zero-dep catalog + its text-parsing auditor
15
+ // standalone CLIs (sequence), zero-dep catalog + its text-parsing auditor
17
16
  // (route-rank-check), and the hall browser statics.
18
17
  // 3. NO NEW parallel map can land: a repo scan flags any rank→number map or
19
18
  // rank-ladder array literal outside the allowlist below, both directions —
@@ -38,7 +37,6 @@ const lifecycleDb = require(path.join(ROOT, 'modules', 'lifecycle', 'db.js'));
38
37
  const ppc = require(path.join(ROOT, 'src', 'bongos', 'permission-path-check.js'));
39
38
  const rrc = require(path.join(ROOT, 'src', 'bongos', 'route-rank-check.js'));
40
39
  const sequence = require(path.join(ROOT, 'scripts', 'gds', 'sequence.js'));
41
- const boxes = require(path.join(ROOT, 'modules', 'dev-box', 'boxes.js'));
42
40
  const catalog = require(path.join(ROOT, 'modules', 'government', 'catalog.js'));
43
41
 
44
42
  const LADDER = kernel.LIVE_RANK_LADDER;
@@ -81,15 +79,6 @@ test('1-based local copies match: permission-path-check, sequence CLI', () => {
81
79
  assert.deepEqual(sequence.RANK_TIER, TIER, 'sequence.js CLI copy drifted');
82
80
  });
83
81
 
84
- test('0-based local copies match: dev-box boxes.js', () => {
85
- for (const [name, map] of [['boxes', boxes.RANK_ORDER]]) {
86
- assert.deepEqual(Object.keys(map).sort(), [...RANK_KEYS].sort(), `${name} keys drifted`);
87
- for (const r of RANK_KEYS) {
88
- assert.equal(map[r], TIER[r] - 1, `${name}.RANK_ORDER.${r} must be tier-1 (0-based)`);
89
- }
90
- }
91
- });
92
-
93
82
  test('ladder mirrors match: governance catalog, route-rank-check', () => {
94
83
  assert.deepEqual([...catalog.RANK_ORDER], [...LADDER]);
95
84
  assert.deepEqual([...rrc.PERMISSION_RANK_ORDER], [...LADDER]);
@@ -192,7 +181,6 @@ const MAP_ALLOWLIST = new Set([
192
181
  'src/bongos/db.js',
193
182
  'src/bongos/permission-path-check.js',
194
183
  'scripts/gds/sequence.js',
195
- 'modules/dev-box/boxes.js',
196
184
  'modules/hall-ui/public/builders.js',
197
185
  // task 1003206 carved the hall page scripts up. builders.js KEPT its map (the
198
186
  // renderers read it through the injected ctx), while goals-page.js's travelled
@@ -87,10 +87,9 @@ test('extractor parses functions, classes, arrow consts, and module.exports', ()
87
87
  // exercise the pure extractor against the real auth.js + a known class file
88
88
  const r = run(['--json']);
89
89
  const model = JSON.parse(r.stdout);
90
- // cf-tunnel.js moved modules/dev-box/ -> scripts/gds/ in task 1943 (ADR 0111 §2,
91
- // shared control-plane clients). Pin the extractor against its current home.
92
- const cfTunnel = model.allSyms.find((s) => s.name === 'CfTunnelApi' && s.file === 'scripts/gds/cf-tunnel.js');
93
- assert.ok(cfTunnel && cfTunnel.kind === 'class', 'class declarations are detected');
90
+ // DoApi is a plain `class X extends Y` declaration in a control-plane client.
91
+ const doApi = model.allSyms.find((s) => s.name === 'DoApi' && s.file === 'scripts/gds/do-api.js');
92
+ assert.ok(doApi && doApi.kind === 'class', 'class declarations are detected');
94
93
  const apiCall = model.allSyms.find((s) => s.name === 'apiCall' && s.file === 'scripts/gds/cli-lib.js');
95
94
  assert.ok(apiCall && apiCall.exported && apiCall.kind === 'fn', 'exported arrow/fn const detected with export flag');
96
95
  });
@@ -77,9 +77,9 @@ t('assessRunnerUnit: a drop-in that ADDS an ExecStart without clearing leaves th
77
77
  t('runnerScriptPath: node + a relative script resolves against WorkingDirectory; a wrapper resolves to null', () => {
78
78
  assert.equal(runnerScriptPath({ workingDirectory: INSTANCE, execStart: '/usr/bin/node node_modules/@bongos/core/scripts/gds/provision.js run-intents --apply' }),
79
79
  `${INSTANCE}/node_modules/@bongos/core/scripts/gds/provision.js`);
80
- assert.equal(runnerScriptPath({ workingDirectory: '/w', execStart: '/usr/bin/env node /abs/core/scripts/gds/box.js run-intents' }), '/abs/core/scripts/gds/box.js');
81
- assert.equal(runnerScriptPath({ workingDirectory: '/w', execStart: '-/usr/bin/node scripts/gds/box.js' }), '/w/scripts/gds/box.js', 'systemd exec prefixes are stripped');
82
- assert.equal(runnerScriptPath({ workingDirectory: '/w', execStart: '/usr/bin/env NODE_ENV=production node --max-old-space-size=512 scripts/gds/box.js' }), '/w/scripts/gds/box.js', 'env assignments and node flags are skipped');
80
+ assert.equal(runnerScriptPath({ workingDirectory: '/w', execStart: '/usr/bin/env node /abs/core/scripts/gds/provision.js run-intents' }), '/abs/core/scripts/gds/provision.js');
81
+ assert.equal(runnerScriptPath({ workingDirectory: '/w', execStart: '-/usr/bin/node scripts/gds/provision.js' }), '/w/scripts/gds/provision.js', 'systemd exec prefixes are stripped');
82
+ assert.equal(runnerScriptPath({ workingDirectory: '/w', execStart: '/usr/bin/env NODE_ENV=production node --max-old-space-size=512 scripts/gds/provision.js' }), '/w/scripts/gds/provision.js', 'env assignments and node flags are skipped');
83
83
  assert.equal(runnerScriptPath({ workingDirectory: '/w', execStart: '/usr/bin/env bash infra/core-node.sh scripts/gds/provision.js' }), null,
84
84
  'a shell wrapper decides at runtime which core it loads — the gate must not guess');
85
85
  });
@@ -130,24 +130,6 @@ t('runnerBanner: names the runner core + path, and flags a differing instance co
130
130
  assert.equal(runnerBanner({ runnerVersion: null, runnerRoot: '/r', instanceVersion: null, instanceRoot: '/i' }), 'runner core ? from /r (no installed core at /i)');
131
131
  });
132
132
 
133
- // box.js run-intents is the other runner the incident class covers — it opens with the same line.
134
- const { cmdRunIntents: boxRunIntents } = await import('../scripts/gds/box.js');
135
- {
136
- const lines = [];
137
- const origLog = console.log;
138
- const origRoot = process.env.BONGOS_INSTANCE_ROOT;
139
- process.env.BONGOS_INSTANCE_ROOT = '/no/such/instance';
140
- console.log = (l) => lines.push(String(l));
141
- try { await boxRunIntents({ query: async () => ({ rows: [] }) }, {}, null, { apply: false }); }
142
- finally {
143
- console.log = origLog;
144
- if (origRoot === undefined) delete process.env.BONGOS_INSTANCE_ROOT; else process.env.BONGOS_INSTANCE_ROOT = origRoot;
145
- }
146
- t('box.js run-intents opens with the runner core version line', () => {
147
- assert.match(lines[0], /^runner core \d+\.\d+\.\d+ from .+ \(no installed core at \/no\/such\/instance\)$/);
148
- });
149
- }
150
-
151
133
  const { runnerDriftRows } = await import('../scripts/gds/doctor.js');
152
134
 
153
135
  t('doctor maps each verdict to a row: ok → ok, drift → FAIL with the fix, unresolved → warn', () => {