@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
@@ -8,7 +8,6 @@
8
8
 
9
9
  'use strict';
10
10
 
11
- const fs = require('node:fs');
12
11
  const path = require('node:path');
13
12
  const { gitOk, shellOk } = require('./ship-git.js');
14
13
  const { pushVia } = require('./ship-deploy-target.js');
@@ -130,27 +129,17 @@ function regenerateApiDocs() {
130
129
  }
131
130
 
132
131
  // Refresh the generated typed client (clients/bongos-client) FROM the just-regenerated
133
- // spec, then keep the devbox vendored CJS copy in lockstep (task 2051). Runs right
134
- // after regenerateApiDocs so the client reflects the same route tree. Automatic here
132
+ // spec (task 2051). Runs right after regenerateApiDocs so the client reflects the
133
+ // same route tree. Automatic here
135
134
  // (not fail-if-forgotten): before this, ship regenerated the spec but not the client,
136
135
  // so a route change left the client stale until the api_client test caught it in CI.
137
136
  // Non-fatal + safe on every ship path; the api_client freshness test backstops main.
138
137
 
139
- // Where the devbox Electron app vendors the CJS client (ADR 0045). It lives inside the
140
- // dev-box MODULE (ADR 0145) — an instance that ships without that module has no copy.
141
- const VENDORED_CLIENT_REL = 'modules/dev-box/app/src/vendor/bongos-client.cjs';
142
-
143
- // The git pathspec for the regenerated client. Pure + exported so the rule is unit-tested
144
- // with no git/fs (task 1002417). `git status` tolerates an unmatched pathspec (exit 0) but
145
- // `git add` aborts the ENTIRE staging step (exit 128), which leaves the regenerated client
146
- // unstaged and the worktree dirty — and a dirty worktree makes the ship's auto-merge bail,
147
- // stranding the task at `confirmed` with no PR. So the vendored copy is named ONLY when it
148
- // is actually on disk.
149
- function apiClientPathspec({ worktreeRoot, existsSync = fs.existsSync } = {}) {
150
- const paths = ['clients/bongos-client'];
151
- if (existsSync(path.join(worktreeRoot, VENDORED_CLIENT_REL))) paths.push(VENDORED_CLIENT_REL);
152
- return paths;
153
- }
138
+ // The regenerated client's git pathspec. It used to carry a second, conditional path —
139
+ // a vendored copy inside a desktop app, named only when on disk because `git add` of an
140
+ // unmatched pathspec aborts the whole stage (task 1002417). The app is gone (task
141
+ // 1003891), so the pathspec is the one directory the generator writes.
142
+ const API_CLIENT_PATHSPEC = 'clients/bongos-client';
154
143
 
155
144
  function regenerateApiClient() {
156
145
  console.log(' regenerating typed API client (clients/bongos-client) from the OpenAPI spec...');
@@ -158,31 +147,13 @@ function regenerateApiClient() {
158
147
  console.log(' api-client regen failed (non-fatal) — CI api_client freshness test backstops staleness');
159
148
  return 'regen-failed';
160
149
  }
161
- // The devbox Electron app can't require() clients/ at runtime, so it vendors the CJS
162
- // build (ADR 0045); keep the copy byte-identical (the api_client test asserts it).
163
- // Present only where the dev-box module ships — an instance without it has no vendored
164
- // copy, so both the vendor step and the git pathspec below drop out (task 1002417: the
165
- // app moved from top-level devbox-app/ to modules/dev-box/app/ per ADR 0145, and these
166
- // paths were not updated, so the guard below silently never fired).
167
- // Resolve it WORKTREE-LOCALLY (like the generator above, __dirname=scripts/gds), NOT
168
- // via REPO_ROOT: on a ship from a linked worktree REPO_ROOT is the MAIN checkout, so a
169
- // REPO_ROOT path would vendor the main tree while gen-api-client just wrote THIS tree,
170
- // leaving this worktree's vendored copy stale → the api_client byte-identity test reds
171
- // the PR (task 2060 fix). __dirname keeps the generator, the vendored file, and the
172
- // git add all on the same tree.
173
- const worktreeRoot = path.join(__dirname, '..', '..');
174
- const vendorScript = path.join(worktreeRoot, 'modules', 'dev-box', 'app', 'scripts', 'vendor-client.js');
175
- if (fs.existsSync(vendorScript)) {
176
- shellOk(`"${process.execPath}" ${JSON.stringify(vendorScript)}`);
177
- }
178
- const clientPathspec = apiClientPathspec({ worktreeRoot }).join(' ');
179
- const dirty = gitOk(`git status --porcelain ${clientPathspec}`);
150
+ const dirty = gitOk(`git status --porcelain ${API_CLIENT_PATHSPEC}`);
180
151
  if (!dirty) {
181
152
  console.log(' API client already current — nothing to commit');
182
153
  return 'current';
183
154
  }
184
- shellOk(`git add ${clientPathspec}`);
185
- if (shellOk('git commit -m "chore(api-client): auto-regenerate typed client + devbox vendor (task 2051)"')) {
155
+ shellOk(`git add ${API_CLIENT_PATHSPEC}`);
156
+ if (shellOk('git commit -m "chore(api-client): auto-regenerate typed client (task 2051)"')) {
186
157
  console.log(' committed auto-regenerated API client');
187
158
  return 'committed';
188
159
  }
@@ -333,8 +304,6 @@ function mainWorktreeBusy() {
333
304
  // (uncommitted work / wrong branch / empty touches[]); refuse.
334
305
 
335
306
  module.exports = {
336
- VENDORED_CLIENT_REL,
337
- apiClientPathspec,
338
307
  currentBranch,
339
308
  describeMainWorktreeBusy,
340
309
  mainWorktreeBusy,
@@ -46,10 +46,10 @@
46
46
  // # Archon). The cheap default after fixing grader
47
47
  // # findings — closes the task-595-regrade-gap (#678).
48
48
  // node scripts/gds/ship.js <task-id> --skip-stage # skip the sandbox-first review gate (#927,
49
- // # ADR 0046). On a dev box, game-surface diffs are
50
- // # otherwise staged to the builder's own live
51
- // # preview (sandbox-<login>, ADR 0044) for a
52
- // # browser review BEFORE the claim resolves.
49
+ // # ADR 0046). On a clone with the game preview,
50
+ // # game-surface diffs are otherwise staged to
51
+ // # the builder's own local preview (ADR 0044) for
52
+ // # a browser review BEFORE the claim resolves.
53
53
  // node scripts/gds/ship.js <task-id> --approved # record your sandbox sign-off (#1048): re-run with
54
54
  // # this AFTER reviewing the staged change at the
55
55
  // # sandbox URL. Persists a sandbox_review row on the
@@ -77,21 +77,6 @@
77
77
 
78
78
  'use strict';
79
79
 
80
- // TREE PREFLIGHT (task 1003098). A task-scoped dev box carries scripts/ and
81
- // src/bongos/ but not every modules/ directory, so the requires below can fail —
82
- // and Node's loader names only the FIRST missing one, which turned recovery into
83
- // guess-read-fix-repeat. This enumerates every missing requirement in one message
84
- // and exits before the first such require runs. It must stay ABOVE them.
85
- //
86
- // A PRE-REQUEST hard exit, like the usage banner and worktree guard: nothing has
87
- // been sent yet, so there is no in-flight request for cliExit to drain.
88
- const { action: _surfaceAction } = require('./surface.js');
89
- const { checkTree: _checkTree } = require('./tree-preflight.js');
90
- {
91
- const _fault = _checkTree(__filename, { command: _surfaceAction("ship") });
92
- if (_fault) { console.error(_fault); process.exit(1); }
93
- }
94
-
95
80
  const { buildMergeArgv, gitArgvOk, sanitizeGitHubRefs } = require('./ship-git.js');
96
81
  const { apiErrorLine, normalizeApiError, postLandShipOutcome } = require('./ship-io.js');
97
82
  const { deriveTagsFromPaths } = require('./ship-surprise.js');
@@ -137,8 +122,6 @@ const {
137
122
  strandNextStep,
138
123
  } = require('./ship-land.js');
139
124
  const {
140
- VENDORED_CLIENT_REL,
141
- apiClientPathspec,
142
125
  regenerateDiagrams,
143
126
  regenerateFileMap,
144
127
  regenerateRepoMap,
@@ -192,9 +175,6 @@ module.exports = {
192
175
  // resolveTranscriptPath itself runs path.resolve, which can't carry a Windows
193
176
  // path through a Linux CI run.
194
177
  encodeProjectPath,
195
- // task 1002417: the regenerated-client pathspec rule, exported pure so the
196
- // "never name a path git add cannot match" invariant is tested without git/fs.
197
- apiClientPathspec, VENDORED_CLIENT_REL,
198
178
  // task 1003: transcript-grade scrubber internals, exported pure so the planted-
199
179
  // secret corpus test (tests/scrubber_corpus.mjs) can exercise each layer + tune.
200
180
  shannonEntropy, looksHighEntropySecret, redactHighEntropy, collectEnvSecrets,
@@ -52,21 +52,6 @@
52
52
  //
53
53
  // Returns JSON if --json is passed (for the slash command to parse cleanly).
54
54
 
55
- // TREE PREFLIGHT (task 1003098). A task-scoped dev box carries scripts/ and
56
- // src/bongos/ but not every modules/ directory, so the requires below can fail —
57
- // and Node's loader names only the FIRST missing one, which turned recovery into
58
- // guess-read-fix-repeat. This enumerates every missing requirement in one message
59
- // and exits before the first such require runs. It must stay ABOVE them.
60
- //
61
- // A PRE-REQUEST hard exit, like the usage banner and worktree guard: nothing has
62
- // been sent yet, so there is no in-flight request for cliExit to drain.
63
- const { action: _surfaceAction } = require('./surface.js');
64
- const { checkTree: _checkTree } = require('./tree-preflight.js');
65
- {
66
- const _fault = _checkTree(__filename, { command: _surfaceAction("start") });
67
- if (_fault) { console.error(_fault); process.exit(1); }
68
- }
69
-
70
55
  const { cliClient, errCode, errDetails, requireSession, fmtUsd, printOnboardingNudge, printCostWarning, printNeedsNudge, hallBase, API_BASE, rewardPolicy, costPlusOnlyNote, isDispatcherIncompatError, cardFlagsFromMe } = require('./cli-lib');
71
56
  const { plainCardLines } = require('./plain-cards');
72
57
  const { contextPack, formatContextPackCompact } = require('./context-pack');
@@ -1,6 +1,6 @@
1
1
  # Hall preview harness
2
2
 
3
- The dev-box sandbox previews the **game only** — `/builders` 404s there (idea 490) — so
3
+ `/builder-stage` previews the **game only** — `/builders` 404s there (idea 490) — so
4
4
  hall-ui changes are reviewed here: a DB-less fixture server that serves the **real**
5
5
  `modules/hall-ui/public` files through the **real** serving seams (boot-time `?v=`
6
6
  content-hash stamping, `{{token}}` replacement, `window.__BRANDING__`/`__MODULES__`
@@ -19,7 +19,7 @@ node scripts/hall-preview/server.js # neutral pack, port 4600
19
19
  |---|---|
20
20
  | `--port 4600` | listen port |
21
21
  | `--brand config/branding.json` | serve the instance pack (default: `config/branding.neutral.json`, the vanilla skin) |
22
- | `--modules-off dev-box,provisioning` | report those modules disabled in `window.__MODULES__` — verifies `moduleOn()` gating (Harbor nav item, Instances/Start-a-project widgets must vanish) |
22
+ | `--modules-off discord,provisioning` | report those modules disabled in `window.__MODULES__` — verifies `moduleOn()` gating (the Discord links, Instances/Start-a-project widgets must vanish) |
23
23
  | `--empty` | drain every array in every fixture — the empty-state screenshot pass |
24
24
  | `--fixture-me` | don't read your own account live — serve the checked-in `me.json` snapshot (see below) |
25
25
 
@@ -32,7 +32,7 @@ A GET with no fixture answers 404 and logs
32
32
  The fixtures are one particular builder's snapshot, so the harness used to greet
33
33
  everyone as that builder — their name, credits, achievements, goals and **rank**.
34
34
  A metic snapshot also sealed the Archon-gated pages, so a UI change to Watch /
35
- Harbor / Gate / Permissions could not be reviewed at all.
35
+ Gate / Permissions could not be reviewed at all.
36
36
 
37
37
  So at boot the harness reads **your** account: if this machine holds a GDS CLI
38
38
  session, the me-scoped endpoints (`/me`, `/me/*`, `/builders/<you>/…`,
@@ -136,8 +136,8 @@ The hard seams every redesign must keep. Run on each changed page:
136
136
  // after connect): watch the network panel, or count via a fixture hit log line.
137
137
  ```
138
138
 
139
- Module gating: rerun the page with `--modules-off dev-box,provisioning` — the
140
- Harbor nav item and the Instances/Start-a-project widgets must be absent, with
139
+ Module gating: rerun the page with `--modules-off discord,provisioning` — the
140
+ Discord links and the Instances/Start-a-project widgets must be absent, with
141
141
  zero console errors.
142
142
 
143
143
  ## Gotchas
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // scripts/hall-preview/server.js — the hall preview harness (task 2167).
3
3
  //
4
- // The dev-box sandbox previews the GAME only (/builders 404s there, idea 490),
4
+ // /builder-stage previews the GAME only (/builders 404s there, idea 490),
5
5
  // so hall-ui changes are reviewed HERE: a tiny express app that serves the real
6
6
  // modules/hall-ui/public files through the REAL serving seams (boot-time ?v=
7
7
  // stamping, {{token}} replacement, __BRANDING__/__MODULES__ injection, theme
@@ -10,7 +10,7 @@
10
10
  // finally checked in.
11
11
  //
12
12
  // node scripts/hall-preview/server.js [--port 4600] [--brand <file>]
13
- // [--modules-off dev-box,provisioning] [--empty]
13
+ // [--modules-off discord,provisioning] [--empty]
14
14
  //
15
15
  // --port listen port (default 4600)
16
16
  // --brand branding pack file — default config/branding.neutral.json
@@ -20,7 +20,7 @@
20
20
  // --empty serve every fixture with its arrays emptied — the
21
21
  // empty-state screenshot pass
22
22
  // --rank archon override the served /me builder.rank (live or fixture) so the
23
- // Archon-gated pages (watch, harbor, gate) render in preview
23
+ // Archon-gated pages (watch, gate) render in preview
24
24
  // --fixture-me do NOT read your own account live; serve the checked-in
25
25
  // fixtures/me.json snapshot (deterministic screenshot passes,
26
26
  // and the offline/signed-out path) — task 1003315
@@ -50,15 +50,15 @@ function arg(name) {
50
50
  const PORT = Number(arg('--port')) || 4600;
51
51
  const EMPTY = process.argv.includes('--empty');
52
52
  // --rank: view the hall as a different rank — the served /me builder.rank is
53
- // overridden in the response, so the Archon-gated pages (watch, harbor, gate)
53
+ // overridden in the response, so the Archon-gated pages (watch, gate)
54
54
  // render their real content instead of the sealed notice. Since task 1003315 the
55
55
  // served /me is normally your OWN account, so this is now only needed to view
56
56
  // the hall as a rank you are not. Preview-only theatre:
57
57
  // the real pages are server-gated (requireGovernmentPage / ADR 0016).
58
58
  const RANK_OVERRIDE = arg('--rank');
59
59
  const MODULES_OFF = String(arg('--modules-off') || '').split(',').map((s) => s.trim()).filter(Boolean);
60
- // --modules-on: force-report modules as enabled — a dev box's local config
61
- // often lacks prod's instance modules (provisioning, dev-box), and their
60
+ // --modules-on: force-report modules as enabled — a local checkout's config
61
+ // often lacks prod's instance modules (provisioning, discord), and their
62
62
  // widgets can't be reviewed without it (task 2169).
63
63
  const MODULES_ON = String(arg('--modules-on') || '').split(',').map((s) => s.trim()).filter(Boolean);
64
64
  // --fixture-me: pin the checked-in /me snapshot instead of reading the caller's
@@ -19,7 +19,7 @@
19
19
  # already-installed browser and forbids the download when it finds one; it only
20
20
  # falls back to puppeteer's own download where nothing is installed (a dev
21
21
  # laptop, where that fetch works fine). This is what makes render-at-deploy
22
- # viable (deploy-prod.sh + infra/box-source-fetch.sh call this).
22
+ # viable (deploy-prod.sh calls this).
23
23
  set -euo pipefail
24
24
 
25
25
  REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
@@ -61,7 +61,7 @@ const OVERRIDES = Object.freeze({
61
61
  unauthenticated: 'unauthenticated',
62
62
  cookie_required: 'unauthenticated',
63
63
  unknown_or_expired: 'unauthenticated',
64
- box_scope: 'forbidden',
64
+ session_scope: 'forbidden',
65
65
 
66
66
  // --- forbidden: 403 (rank/ownership, not "missing" or "bad input") ---
67
67
  forbidden: 'forbidden',
@@ -253,9 +253,8 @@ async function completeWebFlow({ code, redirectUri, userAgent, captureRepoToken
253
253
  // Approval gate (2026-05-30): the web hall sign-in no longer admits anyone
254
254
  // with a GitHub account. A first-time visitor must have been approved by an
255
255
  // Archon (their access_request marked 'invited') before a builder row is
256
- // created. This Archon approval is the ONLY gate — newcomers build on hosted
257
- // dev boxes, so there is no GitHub repo-access / collaborator check anywhere
258
- // (2026-06-07). Established builders (a row already exists for this github_id)
256
+ // created. This Archon approval is the ONLY gate sign-in applies — there is
257
+ // no GitHub repo-access / collaborator check in it (2026-06-07). Established builders (a row already exists for this github_id)
259
258
  // always pass — this only blocks NEW first-time entries.
260
259
  //
261
260
  // SR-19 / task #1000 (2026-06-13): the CLI device flow (pollDeviceFlow,
@@ -128,47 +128,35 @@ async function resolveSession(req) {
128
128
  return resolved;
129
129
  }
130
130
 
131
- // ── SESSION-SCOPES — capability attached to the TOKEN (BV1.R99, ADR 0151 §3) ──
132
- //
133
- // A session's SCOPE is orthogonal to its builder's roles: a dev-box session
134
- // (source='box') is baked onto a machine OUTSIDE the ADR 0016 trust boundary
135
- // (#919/SR#2, ADR 0053), so it must stay limited even when its builder is an
136
- // Archon. Before R99 that was an ad-hoc boolean (`req.allowBoxScope`) plus a
137
- // hardcoded `source === 'box'` test; it is now an explicit, declared model.
138
- //
139
- // A SESSION-SCOPE CAN ONLY EVER NARROW. Effective authority is (what the builder's
140
- // roles confer) ∩ (what this session's scope allows), with the allow-list
141
- // DENY-BY-DEFAULT. The governance resolver COMPOSES that intersection
142
- // (resolveSessionPermissions), but nothing on the request path reads it — it is
143
- // exported on the `government` port and exercised by
144
- // tests/government_session_scope.mjs, and that is the whole of its live use. The
145
- // ENFORCEMENT is the kernel's own route-reachability projection below: "is this
146
- // route inside the session's scope at all", run by requireBuilder and by
147
- // runPageGate. This comment used to say the permission gates read the resolver's
148
- // composed answer; they never have, and the next reviewer should not lose an hour
149
- // to that ghost (task 1003197 (B)). The projection is deliberately the ROLE-FREE
150
- // half — folding the builder's roles in here would change who can reach the
151
- // bootstrap routes (a builder on a rank with no seeded governance role resolves to
152
- // zero permissions by design — see tests/government_parity.mjs Part D), and R99 is
153
- // a behaviour-identical remodelling of the #919 gate, not a re-gating of it.
154
- //
155
- // GOVERNANCE OWNS THE POLICY, THE KERNEL STAYS THE ENFORCER. The declaration is
156
- // governance-owned data in modules/government/session-scopes.json, validated
157
- // against the permission catalog at load by modules/government/session-scopes.js.
158
- // Core reads it as DATA with fs — never `require` (ADR 0083's one-way rule: a
159
- // module's code and enable/disable state must never be something the kernel
160
- // depends on). The sibling trust primitive permission-path-check.js reads the
161
- // protected-surface registry exactly this way (R101). Reading it statically rather
162
- // than through the `governance` kernel port is the deliberate call: a request must
163
- // not need a module to be mounted to stay deny-by-default, and the alternative
164
- // fails badly in both directions — denying on an absent port would take every
165
- // cli/web session down, admitting would hand a box token full authority.
131
+ // ── SESSION SOURCES — which kinds of token may authenticate at all (BV1.R99) ──
132
+ //
133
+ // Every builder_sessions row carries a `source` saying where it was minted. The
134
+ // session-scope registry (modules/government/session-scopes.json) DECLARES the
135
+ // sources this instance accepts; a session whose source it does not declare is
136
+ // DENIED — by requireBuilder on every route and by runPageGate on every page —
137
+ // rather than defaulting to its builder's full authority. A null/absent source is
138
+ // a DECLARED row (sessions minted before migration 090 added the column), not an
139
+ // implicit default.
140
+ //
141
+ // Every declared source is UNSCOPED: it carries its builder's full rank authority.
142
+ // The registry used to be able to declare a NARROWING scope too — a
143
+ // deny-by-default allow-list for a token living outside the ADR 0016 trust
144
+ // boundary — and its only user was a machine-baked token (ADR 0053) that goal
145
+ // 1000120 retired. With no other narrowing scope the allow-list machinery went
146
+ // with it (task 1003894). The parser therefore REFUSES a row that declares an
147
+ // allow-list: without the enforcement behind it, such a row would read as a
148
+ // restriction while granting full authority. A session row still carrying a
149
+ // retired source simply fails the "declared?" test and is denied.
150
+ //
151
+ // GOVERNANCE OWNS THE LIST, THE KERNEL STAYS THE ENFORCER. Core reads the registry
152
+ // as DATA with fs — never `require` (ADR 0083's one-way rule: a module's code and
153
+ // enable/disable state must never be something the kernel depends on), exactly as
154
+ // permission-path-check.js reads the protected-surface registry (R101). A request
155
+ // must not need a module mounted to stay fail-closed: denying on an absent port
156
+ // would take every cli/web session down.
166
157
  //
167
158
  // FAIL-CLOSED both ways: an unreadable or malformed registry THROWS (it never
168
- // degrades to "every session is unscoped"), and a session whose `source` the
169
- // registry does not declare is DENIED rather than defaulting to full authority. A
170
- // null/absent source is a DECLARED row (rows minted before migration 090 added the
171
- // column, which predate the 'box' source existing), not an implicit default.
159
+ // degrades to "every source is accepted"), and an undeclared source is denied.
172
160
  const SESSION_SCOPE_REGISTRY_PATH = path.join(__dirname, '..', '..', 'modules', 'government', 'session-scopes.json');
173
161
 
174
162
  // Pure: validate + normalize the parsed registry. Split from the file read so the
@@ -177,31 +165,21 @@ const SESSION_SCOPE_REGISTRY_PATH = path.join(__dirname, '..', '..', 'modules',
177
165
  function parseSessionScopes(raw, label = 'session-scope registry') {
178
166
  const rows = Array.isArray(raw && raw.scopes) ? raw.scopes : null;
179
167
  if (!rows || rows.length === 0) {
180
- throw new Error(`${label} declares no session scopes. Refusing to run with no session-scope model (ADR 0053/0151).`);
168
+ throw new Error(`${label} declares no session sources. Refusing to run with no session-source model (ADR 0151).`);
181
169
  }
182
170
  const bySource = new Map();
183
171
  for (const row of rows) {
184
172
  const source = row && row.source !== undefined ? row.source : null;
185
173
  if (source !== null && (typeof source !== 'string' || !source)) {
186
- throw new Error(`${label}: every scope needs a non-empty session source string, or null for the legacy sourceless row`);
174
+ throw new Error(`${label}: every row needs a non-empty session source string, or null for the legacy sourceless row`);
187
175
  }
188
176
  if (bySource.has(source)) throw new Error(`${label}: duplicate session source "${source}"`);
189
- const unscoped = !!(row && row.unscoped === true);
190
- const permissions = row && Array.isArray(row.permissions) ? [...row.permissions] : [];
191
- // unscoped XOR an allow-list. Both would be ambiguous (does it narrow?);
192
- // neither would be a scope that can never admit anything — an authoring slip
193
- // either way, and both must fail loud rather than pick a default.
194
- if (unscoped === (permissions.length > 0)) {
195
- throw new Error(`${label}: "${source}" must declare EITHER unscoped:true OR a non-empty permissions allow-list, never both/neither`);
196
- }
197
- if (permissions.some((key) => typeof key !== 'string' || !key)) {
198
- throw new Error(`${label}: "${source}" lists a non-string permission key`);
177
+ if (row && (row.permissions !== undefined || row.unscoped !== undefined)) {
178
+ throw new Error(`${label}: "${source}" declares a scope (permissions/unscoped). Narrowing session scopes were removed (task 1003894); a row may only declare a source that carries its builder's full authority.`);
199
179
  }
200
180
  bySource.set(source, Object.freeze({
201
181
  source,
202
182
  label: row && typeof row.label === 'string' ? row.label : null,
203
- unscoped,
204
- permissions: Object.freeze(permissions),
205
183
  }));
206
184
  }
207
185
  return bySource;
@@ -212,7 +190,7 @@ function loadSessionScopes(file) {
212
190
  try {
213
191
  raw = JSON.parse(fs.readFileSync(file, 'utf8'));
214
192
  } catch (e) {
215
- throw new Error(`session-scope registry unreadable at ${file}: ${e.message}. Refusing to run with no session-scope model (ADR 0053/0151).`);
193
+ throw new Error(`session-scope registry unreadable at ${file}: ${e.message}. Refusing to run with no session-source model (ADR 0151).`);
216
194
  }
217
195
  return parseSessionScopes(raw, `session-scope registry ${file}`);
218
196
  }
@@ -220,7 +198,7 @@ function loadSessionScopes(file) {
220
198
  const _sessionScopes = loadSessionScopes(SESSION_SCOPE_REGISTRY_PATH);
221
199
  const SESSION_SCOPES = Object.freeze([..._sessionScopes.values()]);
222
200
 
223
- // The scope a session's `source` declares, or null when the registry does not
201
+ // The declared row for a session's `source`, or null when the registry does not
224
202
  // declare it (⇒ deny; never "full").
225
203
  function sessionScopeFor(source) {
226
204
  const key = source === undefined ? null : source;
@@ -228,39 +206,6 @@ function sessionScopeFor(source) {
228
206
  return _sessionScopes.get(key) || null;
229
207
  }
230
208
 
231
- // Route reachability, deny-by-default. An unscoped session reaches everything its
232
- // builder's gates admit; a SCOPED session reaches only a route that declared
233
- // capability its allow-list names — so a route that declares nothing (every route
234
- // that has not opted in, including any added later) is out of reach.
235
- function scopeCovers(scope, declared) {
236
- if (!scope) return false; // unrecognised source
237
- if (scope.unscoped) return true;
238
- const needs = Array.isArray(declared) ? declared : [];
239
- return needs.length > 0 && needs.every((key) => scope.permissions.includes(key));
240
- }
241
- function sessionScopeAdmits(source, declared) {
242
- return scopeCovers(sessionScopeFor(source), declared);
243
- }
244
-
245
- // The route-side DECLARATION: "reaching this route needs these capabilities from
246
- // the session's scope." Mount it BEFORE requireBuilder. Inert for an unscoped
247
- // session — requireBuilder compares the declaration only against a narrowing scope.
248
- function sessionScope(...keys) {
249
- const needs = Object.freeze([...keys]);
250
- return function declareSessionScope(req, _res, next) {
251
- req.sessionScopeNeeds = needs;
252
- next();
253
- };
254
- }
255
-
256
- // The dev-box bootstrap declaration (#919) — the name modules/dev-box mounts
257
- // through the plugin API (docs/modules-contract.md 1.2.0), kept as the module-
258
- // facing spelling. It is no longer a bare boolean: it declares the box scope's OWN
259
- // allow-list, read from the registry, so the declaration cannot drift from the
260
- // scope it declares. If the registry stopped declaring a box scope it would
261
- // declare nothing and every box session would be denied — fail closed.
262
- const allowBoxScope = sessionScope(...((sessionScopeFor('box') || { permissions: [] }).permissions));
263
-
264
209
  async function requireBuilder(req, res, next) {
265
210
  let session;
266
211
  try {
@@ -295,24 +240,13 @@ async function requireBuilder(req, res, next) {
295
240
  res.fail('builder_inactive', 403);
296
241
  return;
297
242
  }
298
- // SECURITY (#919, SR#2): SESSION-SCOPE, deny-by-default. The dev-box provisioner
299
- // (scripts/gds/box.js) bakes a session into cloud-init so the box can bootstrap
300
- // (#818 — box-source-fetch clones the rank-scoped repo before /builder-setup
301
- // exists). That token lives ON the box, which is OUTSIDE the ADR 0016 trust
302
- // boundary, so it must NOT carry the builder's full authority. Since R99 the rule
303
- // is DATA (the session-scope registry above), not a hardcoded source test: a
304
- // narrowing session reaches ONLY a route that declared capability its allow-list
305
- // names, so EVERY other route (all of /tasks, /me, the rest of /box …) fails
306
- // closed here, including any route added later; and a source the registry does not
307
- // declare at all is denied rather than treated as unscoped. A builder who wants to
308
- // do real work FROM the box runs /builder-reauth, minting a normal cli/web session.
309
- const scope = sessionScopeFor(session.source);
310
- if (!scopeCovers(scope, req.sessionScopeNeeds)) {
311
- res.fail('box_scope', {
243
+ // SECURITY: an UNDECLARED session source is denied (see the registry notes
244
+ // above). This is what keeps a token of a retired source out of every route, and
245
+ // any source added later out until the registry declares it.
246
+ if (!sessionScopeFor(session.source)) {
247
+ res.fail('session_scope', {
312
248
  status: 403,
313
- message: scope
314
- ? 'This is a box-scoped session; it may only call dev-box bootstrap endpoints.'
315
- : 'This session carries an unrecognised scope and may not call authenticated endpoints.',
249
+ message: 'This session carries an unrecognised source and may not call authenticated endpoints. Sign in again to mint a normal session.',
316
250
  });
317
251
  return;
318
252
  }
@@ -382,9 +316,9 @@ function signInWithReturn(req) {
382
316
  // 1. IDENTITY — is there a live session? (token from the gds_session cookie /
383
317
  // legacy pms_session / Bearer, looked up fresh against builder_sessions).
384
318
  // No → 302 into the web sign-in flow.
385
- // 2. SCOPE — is a page inside this SESSION's scope at all? A narrowing scope
386
- // (a box-baked token) reaches no page, whatever its builder's rank, exactly as
387
- // requireBuilder denies it every route that declared nothing (task 1003197).
319
+ // 2. SOURCE — does the session-scope registry declare this session's source?
320
+ // An undeclared one reaches no page, whatever its builder's rank, exactly as
321
+ // requireBuilder denies it every route (task 1003197).
388
322
  // No → 302 into the web sign-in flow: the remedy is a different session.
389
323
  // 3. AUTHORITY — does the builder HOLD the page's catalog permission, resolved
390
324
  // per request through the `governance` port exactly as requirePermission
@@ -557,21 +491,15 @@ async function runPageGate(name, req, res, next) {
557
491
  res.redirect(302, signInWithReturn(req));
558
492
  return;
559
493
  }
560
- // SECURITY (task 1003197 (A)): SESSION-SCOPE, the same deny-by-default check
561
- // requireBuilder runs above — the two auth paths must not diverge. resolveSession
562
- // accepts a Bearer token with NO source filter, so before this a box-baked token
563
- // could fetch a gated page SHELL (/builders, /watch, the tab shells) whenever its
564
- // builder's rank held the page's permission. Every data endpoint underneath
565
- // re-gates through requireBuilder and 403s box_scope, so the exposure was shells
566
- // only — but a shell is still a surface a token living outside the ADR 0016 trust
567
- // boundary was never meant to reach. Page routes declare no capability, so
568
- // scopeCovers refuses EVERY narrowing scope here (and an unrecognised source with
569
- // it), while an unscoped cli/web session passes untouched.
494
+ // SECURITY (task 1003197 (A)): the same undeclared-source check requireBuilder
495
+ // runs above — the two auth paths must not diverge. resolveSession accepts a
496
+ // Bearer token with NO source filter, so without this a token of an undeclared
497
+ // source could fetch a gated page SHELL whenever its builder's rank held the
498
+ // page's permission.
570
499
  //
571
500
  // Sign-in, not `deniedTo`: unlike the authority branch below, the remedy is a
572
- // DIFFERENT SESSION — /builder-reauth mints a normal one — not a different page,
573
- // and a box session has no browser cookie to loop the sign-in flow with.
574
- if (!scopeCovers(sessionScopeFor(session.source), req.sessionScopeNeeds)) {
501
+ // DIFFERENT SESSION, not a different page.
502
+ if (!sessionScopeFor(session.source)) {
575
503
  res.redirect(302, signInWithReturn(req));
576
504
  return;
577
505
  }
@@ -598,14 +526,14 @@ async function runPageGate(name, req, res, next) {
598
526
  }
599
527
 
600
528
  // The authenticated /builders HTML surface — the Work Board, Roadmap, Goals, the
601
- // task-detail page, the Repo Atlas, the Dev Box pair page and Settings. A Xenos IS
529
+ // task-detail page, the Repo Atlas and Settings. A Xenos IS
602
530
  // allowed (page.view.public floors at xenos); an unauthenticated visitor is bounced
603
531
  // into sign-in rather than served the page shell.
604
532
  async function requireBuilderPage(req, res, next) {
605
533
  return runPageGate('requireBuilderPage', req, res, next);
606
534
  }
607
535
 
608
- // The /watch monitoring surface, the /harbor dev-box page and the /gate page
536
+ // The /watch monitoring surface and the /gate page
609
537
  // (#726, idea 257, task 1179) — Metic and up, per `page.view.government`'s catalog
610
538
  // floor. Named `requireArchonPage` until ADR 0157 lowered that floor archon→metic
611
539
  // (older ADRs still use the old name; this line is the trail). Defense-in-depth:
@@ -917,10 +845,7 @@ module.exports = {
917
845
  extractTokens,
918
846
  SESSION_COOKIE_NAMES,
919
847
  resolveSession,
920
- allowBoxScope,
921
- sessionScope,
922
848
  sessionScopeFor,
923
- sessionScopeAdmits,
924
849
  parseSessionScopes,
925
850
  SESSION_SCOPES,
926
851
  requireBuilder,
@@ -45,7 +45,7 @@ async function withTx(fn, { pool: p = pool } = {}) {
45
45
  }
46
46
 
47
47
  // getBuilderById — the canonical builder-identity read (id, rank, status, the
48
- // economy counters, box-block state). The kernel's identity primitive; auth and
48
+ // economy counters). The kernel's identity primitive; auth and
49
49
  // every rank gate ultimately resolve a builder through this shape.
50
50
  async function getBuilderById(id) {
51
51
  // karma was added in migration 040 (V3.R24 / #244) as a separate
@@ -55,8 +55,7 @@ async function getBuilderById(id) {
55
55
  const { rows } = await pool.query(
56
56
  `SELECT id, github_id, github_login, display_name, avatar_url, total_credits, karma, created_at, rank,
57
57
  status, deactivated_at, reactivated_at, deactivated_reason, preferred_disciplines, system_role,
58
- anthropic_email, anthropic_user_id, monthly_budget_usd,
59
- box_blocked, box_blocked_reason, box_blocked_at, box_blocked_by
58
+ anthropic_email, anthropic_user_id, monthly_budget_usd
60
59
  FROM builders WHERE id = $1`,
61
60
  [id]
62
61
  );