@bongos/core 1.19.1073 → 1.19.1075

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/.bongos-core.json +404 -549
  2. package/.claude/skills/feedback/SKILL.md +2 -2
  3. package/bin/bongos.js +1 -3
  4. package/clients/bongos-client/README.md +1 -1
  5. package/clients/bongos-client/bongos-client.global.js +4 -60
  6. package/clients/bongos-client/index.cjs +4 -60
  7. package/clients/bongos-client/index.d.ts +5 -90
  8. package/clients/bongos-client/index.mjs +4 -60
  9. package/config/branding.neutral.json +1 -5
  10. package/config/modules.neutral.json +2 -3
  11. package/config/scheduled-routines.json +0 -2
  12. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
  13. package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
  14. package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
  15. package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
  16. package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
  17. package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
  18. package/docs/adr/README.md +1 -0
  19. package/docs/api/openapi.json +175 -1665
  20. package/docs/api-reference.md +9 -41
  21. package/docs/architecture.md +16 -1
  22. package/docs/branding-contract.md +0 -3
  23. package/docs/copy-inventory.md +454 -479
  24. package/docs/copy-registry.json +746 -978
  25. package/docs/file-map.md +5 -34
  26. package/docs/module-api-changelog.md +4 -0
  27. package/docs/modules-contract.md +13 -1
  28. package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
  29. package/docs/onboarding/diagrams/README.md +3 -3
  30. package/docs/onboarding/diagrams/assertions.json +2 -22
  31. package/docs/onboarding/primer.md +9 -28
  32. package/docs/page-inventory.json +4 -1
  33. package/docs/page-readings.json +1032 -1077
  34. package/docs/recipes/render-pilot.md +29 -10
  35. package/migrations/core_257_module_store_registry.sql +87 -0
  36. package/migrations/core_258_drop_dev_box_tables.sql +55 -0
  37. package/modules/agents/module.json +1 -0
  38. package/modules/autonomy/module.json +1 -0
  39. package/modules/builder-settings/module.json +1 -0
  40. package/modules/builder-settings/render-prefs.js +4 -5
  41. package/modules/copy-desk/docx.js +78 -5
  42. package/modules/copy-desk/module.json +1 -0
  43. package/modules/copy-desk/routes/copy-desk.js +10 -10
  44. package/modules/copy-desk/tests/copy_docx.mjs +107 -1
  45. package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
  46. package/modules/discord/module.json +1 -0
  47. package/modules/discord/ship-broadcast.js +3 -3
  48. package/modules/economy/credits.js +2 -2
  49. package/modules/economy/module.json +1 -0
  50. package/modules/economy/routes/credits.js +1 -2
  51. package/modules/government/catalog.js +13 -17
  52. package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
  53. package/modules/government/module.json +1 -0
  54. package/modules/government/protected-surfaces.json +3 -11
  55. package/modules/government/resolver.js +3 -46
  56. package/modules/government/routes/government.js +0 -2
  57. package/modules/government/session-scopes.js +43 -83
  58. package/modules/government/session-scopes.json +8 -18
  59. package/modules/grading/module.json +1 -0
  60. package/modules/hall-ui/module.json +1 -0
  61. package/modules/hall-ui/public/board-lib.js +2 -2
  62. package/modules/hall-ui/public/builders.js +29 -40
  63. package/modules/hall-ui/public/collab.html +1 -1
  64. package/modules/hall-ui/public/collab.js +1 -1
  65. package/modules/hall-ui/public/diagrams.html +3 -3
  66. package/modules/hall-ui/public/dom-utils.js +1 -1
  67. package/modules/hall-ui/public/gate.js +2 -2
  68. package/modules/hall-ui/public/gate.states.json +1 -1
  69. package/modules/hall-ui/public/hall-render.js +43 -79
  70. package/modules/hall-ui/public/index.html +5 -1
  71. package/modules/hall-ui/public/modules.js +1 -0
  72. package/modules/hall-ui/public/oversight.css +5 -5
  73. package/modules/hall-ui/public/palette.js +0 -11
  74. package/modules/hall-ui/public/primer.js +1 -1
  75. package/modules/hall-ui/public/sessions.html +1 -1
  76. package/modules/hall-ui/public/settings-sessions.js +1 -1
  77. package/modules/hall-ui/public/settings.css +0 -1
  78. package/modules/hall-ui/public/settings.html +4 -33
  79. package/modules/hall-ui/public/settings.js +3 -33
  80. package/modules/hall-ui/public/settings.states.json +2 -2
  81. package/modules/hall-ui/public/studio.html +1 -1
  82. package/modules/hall-ui/public/style.css +10 -13
  83. package/modules/hall-ui/public/task.html +4 -1
  84. package/modules/hall-ui/public/task.js +36 -1
  85. package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
  86. package/modules/hall-ui/public/tweak-editor.css +16 -0
  87. package/modules/hall-ui/public/tweak-editor.html +26 -3
  88. package/modules/hall-ui/public/tweak-editor.js +32 -1
  89. package/modules/hall-ui/public/work.js +1 -1
  90. package/modules/hall-ui/records/tweak-editor.md +1 -0
  91. package/modules/ideas/module.json +1 -0
  92. package/modules/ideas/projection.js +1 -1
  93. package/modules/lifecycle/db-claim-reads.js +1 -58
  94. package/modules/lifecycle/db-ship.js +4 -9
  95. package/modules/lifecycle/db.js +0 -4
  96. package/modules/lifecycle/github-push.js +12 -14
  97. package/modules/lifecycle/kickoff-checklist.js +0 -4
  98. package/modules/lifecycle/lifecycle.js +0 -8
  99. package/modules/lifecycle/module.json +1 -0
  100. package/modules/lifecycle/publish-reconciler.js +10 -10
  101. package/modules/lifecycle/routes/tasks.js +13 -15
  102. package/modules/lifecycle/ship-card.js +1 -1
  103. package/modules/lifecycle/task-visuals.js +2 -2
  104. package/modules/memory/module.json +1 -0
  105. package/modules/memory/routes/memory.js +2 -2
  106. package/modules/npm-release/module.json +1 -0
  107. package/modules/onboarding/module.json +1 -0
  108. package/modules/onboarding/onboarding-state.js +17 -36
  109. package/modules/onboarding/routes/access-requests.js +3 -3
  110. package/modules/platform-identity/module.json +1 -0
  111. package/modules/platform-identity/platform-identity.js +13 -5
  112. package/modules/platform-identity/routes/sso.js +5 -4
  113. package/modules/platform-identity/tests/platform-identity.mjs +17 -3
  114. package/modules/provisioning/capacity.js +1 -1
  115. package/modules/provisioning/module.json +1 -0
  116. package/modules/provisioning/provisioning.js +6 -5
  117. package/modules/provisioning/routes/provisioning.js +4 -4
  118. package/modules/provisioning/starter-bundles.js +5 -25
  119. package/modules/public-landing/module.json +1 -0
  120. package/modules/public-landing/public/assets/cosmos.css +1 -1
  121. package/modules/public-landing/public/contact.html +1 -1
  122. package/modules/public-landing/public/privacy.html +1 -1
  123. package/modules/public-landing/public/projects.html +101 -32
  124. package/modules/public-landing/public/projects.probes.json +2 -2
  125. package/modules/public-landing/public/projects.states.json +4 -3
  126. package/modules/public-landing/public/terms.html +1 -1
  127. package/modules/security/module.json +1 -0
  128. package/modules/sessions/db.js +1 -1
  129. package/modules/sessions/module.json +1 -0
  130. package/modules/specialities/module.json +1 -0
  131. package/modules/status-ui/module.json +1 -0
  132. package/modules/ui-design/module.json +1 -0
  133. package/package-lock.json +2 -2
  134. package/package.json +1 -1
  135. package/release-notes.json +84 -0
  136. package/scripts/gds/artifact-format.js +203 -0
  137. package/scripts/gds/artifact-staleness.js +2 -2
  138. package/scripts/gds/audit-authorship.sh +1 -13
  139. package/scripts/gds/backfill-task-rewards.js +5 -5
  140. package/scripts/gds/claim.js +0 -15
  141. package/scripts/gds/claude-materialize.js +2 -2
  142. package/scripts/gds/cli-lib.js +15 -15
  143. package/scripts/gds/codemod-rename-src-gds.js +1 -1
  144. package/scripts/gds/context-pack.js +5 -7
  145. package/scripts/gds/diagram-facts.js +12 -23
  146. package/scripts/gds/do-api.js +13 -80
  147. package/scripts/gds/doc-cli-guard.js +2 -0
  148. package/scripts/gds/doctor.js +4 -4
  149. package/scripts/gds/feedback-latest.js +139 -9
  150. package/scripts/gds/fitness-checks-identity.js +2 -7
  151. package/scripts/gds/fitness-checks-packaging.js +4 -4
  152. package/scripts/gds/fitness-checks-write-validation.js +4 -0
  153. package/scripts/gds/fitness-ratchets.js +5 -5
  154. package/scripts/gds/fitness.js +10 -10
  155. package/scripts/gds/gen-api-client.js +4 -4
  156. package/scripts/gds/gen-api-docs.js +1 -1
  157. package/scripts/gds/gen-diagrams.js +49 -71
  158. package/scripts/gds/gen-repo-map.js +13 -14
  159. package/scripts/gds/gen-session-index.js +8 -8
  160. package/scripts/gds/http-api-client.js +12 -19
  161. package/scripts/gds/init.js +1 -1
  162. package/scripts/gds/leak-scan-allowlist.js +1 -1
  163. package/scripts/gds/lint-infra-exec.js +1 -1
  164. package/scripts/gds/local-preview-lib.js +8 -39
  165. package/scripts/gds/local-preview.js +2 -15
  166. package/scripts/gds/migration-namespace.js +3 -3
  167. package/scripts/gds/module-artifact.js +212 -0
  168. package/scripts/gds/module.js +95 -6
  169. package/scripts/gds/package-core.js +5 -116
  170. package/scripts/gds/plain-cards.js +1 -1
  171. package/scripts/gds/provision-config.js +2 -2
  172. package/scripts/gds/provision-net.js +2 -3
  173. package/scripts/gds/provision.js +9 -9
  174. package/scripts/gds/publish-manifest.js +1 -2
  175. package/scripts/gds/push-path-brief.js +10 -10
  176. package/scripts/gds/regen-instance-docs.js +1 -1
  177. package/scripts/gds/rename-history-check.js +1 -1
  178. package/scripts/gds/run-unit-tests.js +2 -16
  179. package/scripts/gds/sandbox-stage.js +39 -341
  180. package/scripts/gds/session-digest-build.js +2 -2
  181. package/scripts/gds/ship-deploy-target.js +13 -13
  182. package/scripts/gds/ship-flow.js +7 -7
  183. package/scripts/gds/ship-land.js +9 -10
  184. package/scripts/gds/ship-merge.js +4 -4
  185. package/scripts/gds/ship-preflight-steps.js +4 -4
  186. package/scripts/gds/ship-regen.js +15 -49
  187. package/scripts/gds/ship.js +13 -33
  188. package/scripts/gds/skill-preflight.js +9 -8
  189. package/scripts/gds/start.js +0 -15
  190. package/scripts/hall-preview/README.md +5 -5
  191. package/scripts/hall-preview/server.js +6 -6
  192. package/scripts/render-diagrams.sh +1 -1
  193. package/src/bongos/api-errors.js +1 -1
  194. package/src/bongos/api-prefix.js +4 -4
  195. package/src/bongos/auth-github.js +2 -3
  196. package/src/bongos/auth.js +51 -126
  197. package/src/bongos/db-kernel.js +2 -3
  198. package/src/bongos/db.js +5 -28
  199. package/src/bongos/module-scope-map.js +14 -29
  200. package/src/bongos/module-store.js +141 -0
  201. package/src/bongos/platform-visibility-gate.js +1 -1
  202. package/src/bongos/route-rank-check.js +3 -16
  203. package/src/bongos/routes/auth.js +0 -116
  204. package/src/bongos/routes/builders.js +5 -15
  205. package/src/bongos/routes/instance.js +8 -14
  206. package/src/bongos/routes/me.js +7 -23
  207. package/src/bongos/routes/modules.js +83 -0
  208. package/src/bongos/routes/security.js +3 -3
  209. package/src/bongos/routes.js +7 -2
  210. package/src/bongos/serve-internal.js +5 -6
  211. package/src/bongos-downloads.js +1 -6
  212. package/src/branding.js +2 -2
  213. package/src/build-info.js +4 -6
  214. package/src/instance-config.js +2 -2
  215. package/src/module-api.js +13 -6
  216. package/src/module-loader/catalog.js +3 -0
  217. package/src/module-loader/loader.js +4 -4
  218. package/src/module-loader/manifest-schema.js +16 -8
  219. package/src/module-seams.js +2 -3
  220. package/src/modules.js +42 -14
  221. package/tests/agents_spend_guard.mjs +1 -1
  222. package/tests/api_alias_caller_ratchet.mjs +1 -1
  223. package/tests/api_alias_exceptions.mjs +1 -1
  224. package/tests/api_client.mjs +1 -16
  225. package/tests/api_path_404.mjs +1 -1
  226. package/tests/auth_page_gate.mjs +10 -10
  227. package/tests/backfill_task_rewards.mjs +1 -1
  228. package/tests/canonical_profile_url.mjs +1 -3
  229. package/tests/claim_action.mjs +4 -1
  230. package/tests/claim_from_task_and_home.mjs +127 -0
  231. package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
  232. package/tests/cli_sessions.mjs +1 -1
  233. package/tests/consumer_layout_boot.mjs +2 -0
  234. package/tests/context_pack.mjs +13 -13
  235. package/tests/copy_desk_page_docx.mjs +33 -2
  236. package/tests/copy_inventory.mjs +7 -7
  237. package/tests/core_upgrade_runner.mjs +6 -6
  238. package/tests/credit_grant.mjs +2 -2
  239. package/tests/criterion_suggest.mjs +1 -1
  240. package/tests/deploy_divergence_line.mjs +11 -11
  241. package/tests/design_tokens_sync.mjs +3 -2
  242. package/tests/diagram_facts_offset.mjs +1 -1
  243. package/tests/do_api.mjs +106 -0
  244. package/tests/feedback_latest.mjs +122 -0
  245. package/tests/fitness.mjs +44 -42
  246. package/tests/fitness_ratchets.mjs +2 -2
  247. package/tests/gate_approvals.mjs +0 -1
  248. package/tests/github_push_land_proof.mjs +1 -1
  249. package/tests/go_live.mjs +3 -3
  250. package/tests/goal_advisory.mjs +3 -3
  251. package/tests/goal_suggest.mjs +3 -3
  252. package/tests/government_abuse_matrix.mjs +21 -19
  253. package/tests/government_ownership_scope.mjs +33 -35
  254. package/tests/government_parity.mjs +2 -2
  255. package/tests/government_protected_surfaces.mjs +56 -37
  256. package/tests/government_require_permission.mjs +3 -3
  257. package/tests/government_seed.mjs +37 -4
  258. package/tests/government_session_scope.mjs +92 -366
  259. package/tests/hall_audit.mjs +1 -1
  260. package/tests/hall_error_envelope.mjs +4 -0
  261. package/tests/hall_landing_boot.mjs +4 -1
  262. package/tests/hall_palette.mjs +1 -16
  263. package/tests/hall_record_world.mjs +6 -0
  264. package/tests/hall_tweak_editor.mjs +52 -1
  265. package/tests/helpers.mjs +1 -1
  266. package/tests/host_topology_skips.mjs +3 -4
  267. package/tests/html_comment_nesting.mjs +137 -0
  268. package/tests/infra_exec_paths.mjs +9 -10
  269. package/tests/init.mjs +4 -5
  270. package/tests/instance_manifest.mjs +18 -25
  271. package/tests/kickoff_checklist.mjs +1 -1
  272. package/tests/lib_sh_resolution.mjs +0 -221
  273. package/tests/local_preview.mjs +0 -12
  274. package/tests/migration_namespace.mjs +6 -6
  275. package/tests/module-scope-map.mjs +9 -9
  276. package/tests/module_api.mjs +2 -2
  277. package/tests/module_catalog.mjs +1 -1
  278. package/tests/module_cli.mjs +20 -20
  279. package/tests/module_contributions.mjs +6 -6
  280. package/tests/module_loader.mjs +6 -6
  281. package/tests/module_manifest.mjs +26 -17
  282. package/tests/module_route_rank.mjs +2 -2
  283. package/tests/module_store_publish.mjs +307 -0
  284. package/tests/module_store_publish_route.mjs +121 -0
  285. package/tests/module_store_registry_migration.mjs +70 -0
  286. package/tests/modules.mjs +56 -26
  287. package/tests/modules_route_wiring.mjs +1 -1
  288. package/tests/onboarding_route_signals.mjs +21 -31
  289. package/tests/onboarding_state.mjs +35 -49
  290. package/tests/paste_token_session_store.mjs +1 -1
  291. package/tests/permission_path.mjs +0 -3
  292. package/tests/platform_boot.mjs +1 -2
  293. package/tests/platform_visibility_gate.mjs +1 -1
  294. package/tests/profile_route.mjs +1 -6
  295. package/tests/project_modules_ui.mjs +17 -17
  296. package/tests/projects_hub.mjs +5 -5
  297. package/tests/projects_hub_app_step.mjs +138 -0
  298. package/tests/projects_hub_module_picker.mjs +77 -76
  299. package/tests/projects_hub_pre_uat.mjs +4 -5
  300. package/tests/provision.mjs +7 -7
  301. package/tests/provisioning_capacity.mjs +1 -1
  302. package/tests/provisioning_recommendations.mjs +2 -1
  303. package/tests/provisioning_starter_bundles.mjs +0 -22
  304. package/tests/publish_branch_route.mjs +11 -19
  305. package/tests/publish_manifest.mjs +2 -3
  306. package/tests/publish_reconciler.mjs +8 -8
  307. package/tests/push_path_brief.mjs +10 -9
  308. package/tests/rank_tier_single_source.mjs +1 -13
  309. package/tests/regrade_eligibility.mjs +1 -1
  310. package/tests/rename_history_restraint.mjs +1 -1
  311. package/tests/repo_map.mjs +12 -13
  312. package/tests/runner_drift.mjs +3 -21
  313. package/tests/sandbox_stage.mjs +57 -390
  314. package/tests/seam_wiring_guard.mjs +11 -11
  315. package/tests/search_isolation.mjs +3 -3
  316. package/tests/session_records.mjs +1 -1
  317. package/tests/session_rename_fallback.mjs +1 -22
  318. package/tests/session_start_freshness.mjs +3 -3
  319. package/tests/session_token_hash_db.mjs +1 -1
  320. package/tests/ship_card.mjs +1 -1
  321. package/tests/ship_ci_deploy.mjs +17 -17
  322. package/tests/ship_error_shape.mjs +1 -1
  323. package/tests/ship_premerge.mjs +1 -1
  324. package/tests/ship_resume.mjs +1 -1
  325. package/tests/skill_preflight.mjs +2 -2
  326. package/tests/skip_is_not_pass.mjs +1 -1
  327. package/tests/task_visual_slots.mjs +31 -0
  328. package/tests/terms_acceptance.mjs +2 -2
  329. package/tests/upgrade.mjs +4 -4
  330. package/tests/watch_sealed_floor.mjs +0 -1
  331. package/tests/wizard_draft_resume.mjs +2 -2
  332. package/tests/wizard_intent_resume.mjs +21 -10
  333. package/tests/wizard_preselect_why.mjs +18 -18
  334. package/docs/onboarding/browser-terminal-guide.md +0 -73
  335. package/docs/recipes/managed-settings-remote-control.md +0 -416
  336. package/modules/dev-box/CLAUDE.md +0 -15
  337. package/modules/dev-box/box-access.js +0 -623
  338. package/modules/dev-box/box-credential.js +0 -94
  339. package/modules/dev-box/box-onboard.js +0 -486
  340. package/modules/dev-box/boxes.js +0 -1038
  341. package/modules/dev-box/db.js +0 -39
  342. package/modules/dev-box/module.json +0 -19
  343. package/modules/dev-box/routes/box.js +0 -1223
  344. package/scripts/gds/box-auth-check.js +0 -160
  345. package/scripts/gds/box-infra.js +0 -321
  346. package/scripts/gds/box-sync.js +0 -216
  347. package/scripts/gds/box.js +0 -1332
  348. package/scripts/gds/cf-tunnel.js +0 -558
  349. package/scripts/gds/smoke-box.sh +0 -150
  350. package/scripts/gds/tree-preflight.js +0 -241
  351. package/src/bongos/app-pair.js +0 -172
  352. package/tests/app_pair.mjs +0 -168
  353. package/tests/box_access.mjs +0 -578
  354. package/tests/box_auth_check.mjs +0 -114
  355. package/tests/box_code_staleness.mjs +0 -129
  356. package/tests/box_connect_e2e.mjs +0 -196
  357. package/tests/box_cost.mjs +0 -70
  358. package/tests/box_credential.mjs +0 -125
  359. package/tests/box_dns_repoint.mjs +0 -153
  360. package/tests/box_env.mjs +0 -64
  361. package/tests/box_host_keys.mjs +0 -126
  362. package/tests/box_infra_resolve.mjs +0 -246
  363. package/tests/box_onboard.mjs +0 -317
  364. package/tests/box_scope_session.mjs +0 -119
  365. package/tests/box_sweep_docs.mjs +0 -119
  366. package/tests/box_sync.mjs +0 -55
  367. package/tests/box_sync_scope_report.mjs +0 -126
  368. package/tests/box_task_scope.mjs +0 -169
  369. package/tests/box_task_scope_pause.mjs +0 -66
  370. package/tests/box_terminal_ssrf.mjs +0 -96
  371. package/tests/boxes.mjs +0 -1585
  372. package/tests/cf_ruleset_preflight.mjs +0 -142
  373. package/tests/cf_tunnel.mjs +0 -496
  374. package/tests/docs_scrubber_damage.mjs +0 -111
  375. package/tests/module_scope_active_claims.mjs +0 -94
  376. package/tests/ship_api_client_pathspec.mjs +0 -100
  377. package/tests/tree_preflight.mjs +0 -243
@@ -65,7 +65,7 @@ const API = `(?:${ALL_API_PREFIXES.map(escRe).join('|')})`;
65
65
  // Caddy's on-demand-TLS `ask` and the provisioning probe —
66
66
  // server-to-server, cookie-less, and on a control plane a
67
67
  // refused ask means no project ever gets a certificate.
68
- // /downloads/* the installer script and the Dev Box updater — a
68
+ // /downloads/* the installer script and the release downloads — a
69
69
  // cookie-less `curl … | sh`, and release redirects carry
70
70
  // no project data.
71
71
  // /__prelaunch the pre-launch gate's own submit, when both gates are on.
@@ -221,9 +221,7 @@ const EXPECTED_RANKS = {
221
221
  // criterion confirmation, and gate-approvals — moved to MODULE_EXPECTED_RANKS
222
222
  // ['lifecycle'] below in BV1.R86. They live in the lifecycle module now
223
223
  // (modules/lifecycle/routes/…), so they're verified only when that module is in
224
- // scope, not unconditionally from this core list — the pins follow the routes,
225
- // the dev-box precedent.)
226
- // (dev-box fleet pins likewise live in MODULE_EXPECTED_RANKS below — BV1.R43.)
224
+ // scope, not unconditionally from this core list — the pins follow the routes.)
227
225
  // security engagement docs — exploit-detail report store (task 991). Pinned
228
226
  // because a silent downgrade here leaks the attacker cookbook below the
229
227
  // trust line. (security-the-PROPERTY stays kernel — routes/security.js.)
@@ -277,10 +275,8 @@ const GOVERNMENT_PINS = {
277
275
  // when that module is in scope (its routes were discovered in this scan). So an
278
276
  // instance that doesn't ship the module — or a hermetic test isolating a
279
277
  // different module — never trips a false "vanished" alarm, while a real rank
280
- // downgrade or a removal IS still caught wherever the module is present. (dev-box's
281
- // box-fleet routes moved here from EXPECTED_RANKS in BV1.R43 / task 1412 — the pins
282
- // follow the routes; the intent is unchanged: closing/blocking another builder's
283
- // paid infra, and reading the fleet/cost roster, stay archon-only.)
278
+ // downgrade or a removal IS still caught wherever the module is present — the pins
279
+ // follow the routes (BV1.R43 / task 1412 moved the first set here).
284
280
  //
285
281
  // The `ideas` / `memory` / `security` blocks were added in BV1.R109b: their routes are
286
282
  // declared multi-line and so were invisible to this auditor until it learned that form
@@ -293,15 +289,6 @@ const GOVERNMENT_PINS = {
293
289
  // (metic), `security.report.adjudicate` / `idea.ratify` / `memory.read.any` (archon) —
294
290
  // so tightening these to `perm:` pins is a clean follow-up once R104-R107 have landed.
295
291
  const MODULE_EXPECTED_RANKS = {
296
- 'dev-box': {
297
- // ADR 0157 lowered `box.fleet.manage` archon→metic (operating paid infra is admin
298
- // work, not an authority grant). Re-pinned by ATOM so the guarded invariant is
299
- // "these gate on the fleet permission", independent of its floor.
300
- 'GET /boxes': 'perm:box.fleet.manage',
301
- 'GET /boxes/cost-ledger': 'perm:box.fleet.manage',
302
- 'POST /boxes/:builderId/close': 'perm:box.fleet.manage',
303
- 'PATCH /boxes/:builderId/block': 'perm:box.fleet.manage',
304
- },
305
292
  // The lifecycle module's privileged routes (carved BV1.R86 — the pins follow the
306
293
  // routes from EXPECTED_RANKS). Verified only when the lifecycle module is in scope.
307
294
  lifecycle: {
@@ -13,7 +13,6 @@
13
13
  // - /auth/discord/* LINKING an existing builder to a Discord id for role sync.
14
14
  // It is NOT a way to sign in — Discord sits OUTSIDE the trust
15
15
  // boundary (ADR 0032), so it can never mint a session.
16
- // - /auth/app-pair/* the Dev Box desktop app pairing to its builder (ADR 0045).
17
16
  //
18
17
  // TWO INVARIANTS worth keeping in view before editing:
19
18
  // - `/auth/cli-token/issue` mints a CLI bearer from a browser COOKIE, never from a
@@ -36,7 +35,6 @@ const { reportActivityRollupForShippedTask } = require('../auth-admission.js');
36
35
  // endpoint refuses. Same composed answer the gate itself reads.
37
36
  const { effectiveJoinDoor } = require('../project-door.js');
38
37
  const db = require('../db');
39
- const { pairStore } = require('../app-pair');
40
38
  const { publicOrigin, parseCookie, validateOrRespond } = require('./_helpers');
41
39
  const { accountExistenceReadRateLimit } = require('../middleware/rate-limit');
42
40
  const { branding } = require('../../branding');
@@ -1162,120 +1160,6 @@ module.exports = function buildAuthRouter() {
1162
1160
  }
1163
1161
  });
1164
1162
 
1165
- // /auth/app-pair — browser-clicks-only sign-in for the Dev Box desktop app
1166
- // (#904, ADR 0045). The app starts a pairing, opens the browser at
1167
- // /builders/pair?code=…, the signed-in builder clicks ONE Approve button,
1168
- // and the app collects a freshly-minted source='cli' session over its own
1169
- // secret poll channel. Design + threat model in src/bongos/app-pair.js.
1170
- //
1171
- // Route order note: the two PUBLIC routes here sit so no requireBuilder/
1172
- // requireRank from a NEIGHBORING declaration falls inside route-rank-check's
1173
- // 20-line look-ahead — the deterministic auditor must classify them from
1174
- // their own `rank:` annotations, not a false middleware hit.
1175
-
1176
- // rank: public — starts a pairing; returns opaque codes only, no identity.
1177
- router.post('/auth/app-pair/start', (req, res) => {
1178
- if (validateOrRespond(req, res, {
1179
- device_name: { type: 'string', maxLength: 80 },
1180
- platform: { type: 'string', maxLength: 20 },
1181
- })) return;
1182
- const body = req.body || {};
1183
- const out = pairStore.start({ deviceName: body.device_name, platform: body.platform });
1184
- if (out.error === 'busy') {
1185
- return res.fail('pairing_busy', { status: 429, message: 'Too many pairings in flight. Try again in a few minutes.' });
1186
- }
1187
- // The approve-page URL is PINNED to the canonical public origin (same
1188
- // posture as the OAuth redirect_uris above) — never derived from the
1189
- // request host, so a spoofed Host header can't point the app's browser
1190
- // hand-off at a look-alike approve page.
1191
- const verifyPath = `/builders/pair?code=${out.pairCode}`;
1192
- res.status(201).json({
1193
- pair_code: out.pairCode,
1194
- poll_token: out.pollToken,
1195
- verify_path: verifyPath,
1196
- verify_url: `${WEB_REDIRECT_ORIGIN}${verifyPath}`,
1197
- expires_in: out.expiresIn,
1198
- interval: out.interval,
1199
- });
1200
- });
1201
-
1202
- // rank: any-builder — read-only: what the approve page shows BEFORE the
1203
- // click (device name/platform for the given code). Unknown and expired are
1204
- // indistinguishable (404) so the code space can't be probed for liveness.
1205
- router.get('/auth/app-pair/info', auth.requireBuilder, (req, res) => {
1206
- const info = pairStore.info(req.query.code);
1207
- if (!info) return res.fail('unknown_or_expired', 404);
1208
- res.json({
1209
- pair_code: info.pairCode,
1210
- device_name: info.deviceName,
1211
- platform: info.platform,
1212
- status: info.status,
1213
- });
1214
- });
1215
-
1216
- // rank: any-builder (own identity only) — approving signs the app in AS THE
1217
- // APPROVER. Cookie-gated exactly like /auth/cli-token/issue above: a leaked
1218
- // CLI bearer must not be able to approve pairings and mint itself siblings;
1219
- // only the httpOnly browser cookie — the click in the hall — can.
1220
- router.post('/auth/app-pair/approve', auth.requireBuilder, async (req, res) => {
1221
- const cookieToken = sessionCookieToken(req);
1222
- if (!cookieToken) {
1223
- return res.fail('cookie_required', { status: 403, message: 'Pairings are approved from an authenticated browser session in the builders’ hall, not from a bearer token.' });
1224
- }
1225
- if (validateOrRespond(req, res, {
1226
- pair_code: { required: true, type: 'string', maxLength: 32 },
1227
- })) return;
1228
- try {
1229
- const token = crypto.randomBytes(32).toString('hex');
1230
- const row = await db.createSession({
1231
- builderId: req.builder.id,
1232
- token,
1233
- source: 'cli', // 003_pms.sql CHECK; the app is a CLI-class bearer client
1234
- userAgent: `devbox-app pair (${req.headers['user-agent'] ?? 'unknown'})`,
1235
- expiresAt: null, // no expiry — a CLI bearer lasts until revoked (ADR 0294)
1236
- });
1237
- const out = pairStore.approve((req.body || {}).pair_code, {
1238
- token,
1239
- expires_at: row.expires_at,
1240
- builder: {
1241
- id: req.builder.id,
1242
- github_login: req.builder.github_login,
1243
- display_name: req.builder.display_name,
1244
- rank: req.builder.rank,
1245
- },
1246
- });
1247
- if (!out.ok) {
1248
- // The pairing was gone or already approved — revoke the session we
1249
- // just minted so an orphaned token never outlives this branch.
1250
- await db.revokeSession(token);
1251
- return out.error === 'already_approved'
1252
- ? res.fail('already_approved', 409)
1253
- : res.fail('unknown_or_expired', 404);
1254
- }
1255
- res.json({ ok: true });
1256
- } catch (err) {
1257
- log.error('[gds] POST /auth/app-pair/approve', err);
1258
- res.fail('approve_failed', { status: 500, message: 'internal error' });
1259
- }
1260
- });
1261
-
1262
- // rank: public — poll with the secret poll_token; the token IS the bearer
1263
- // (mirrors /auth/device/poll, where the device_code is the bearer). Kept
1264
- // LAST in this router so route-rank-check's look-ahead window stays clear
1265
- // of any neighboring gated declaration (see the block comment above).
1266
- router.post('/auth/app-pair/poll', (req, res) => {
1267
- if (validateOrRespond(req, res, {
1268
- poll_token: { required: true, type: 'string', maxLength: 128 },
1269
- })) return;
1270
- const out = pairStore.poll((req.body || {}).poll_token);
1271
- if (out.status === 'approved') {
1272
- return res.json({ status: 'approved', ...out.grant });
1273
- }
1274
- res.json(out.status === 'pending'
1275
- ? { status: 'pending', interval: out.interval }
1276
- : { status: 'expired' });
1277
- });
1278
-
1279
1163
  return router;
1280
1164
  };
1281
1165
 
@@ -82,14 +82,6 @@ function staleHoursFor(req, res) {
82
82
  // kernel PORT (lc()). The rank/identity mutations (setBuilderRank/deactivate/
83
83
  // reactivate) stay core db (this domain is builder identity, not the lifecycle).
84
84
  const lc = () => seams.resolveOptional('lifecycle');
85
- // Dev-box "has this builder ever connected a box?" read through the kernel seam
86
- // instead of a direct `require('../boxes')` (a forbidden core→module edge once
87
- // dev-box moved to modules/dev-box/, ADR 0083 / BV1.R43). The dev-box module
88
- // provides the `box.hasEverConnected` port when enabled; degrades to false on a
89
- // box-less instance (the box_connect onboarding step just never auto-clears).
90
- const boxHasEverConnected = (builderId) =>
91
- seams.resolveOptional('box.hasEverConnected', async () => false)(builderId);
92
-
93
85
  // The discord "has this builder linked their Discord account?" signal, read
94
86
  // through the kernel seam instead of a direct `db.getDiscordLink` (a forbidden
95
87
  // core→module edge once discord moved to modules/discord/, ADR 0083 / BV1.R47).
@@ -470,22 +462,20 @@ module.exports = function buildBuildersRouter() {
470
462
  // never a direct require of a module. Default-on (present in prod); degrades
471
463
  // to a null state row (shapeForApi synthesizes the "nothing cleared" view).
472
464
  const onboarding = seams.resolveOptional('onboarding');
473
- const [state, shipCount, discordLink, boxConnected, cliAuthed] = await Promise.all([
465
+ const [state, shipCount, discordLink, cliAuthed] = await Promise.all([
474
466
  onboarding ? onboarding.getState(pool, targetId) : null,
475
467
  lc().countShippedTasksForBuilder(targetId),
476
468
  discordIsLinked(targetId),
477
- boxHasEverConnected(targetId),
478
469
  db.hasCliSession(targetId),
479
470
  ]);
480
471
  // shipCount drives the terminal 'ship_three' stage's "N of 3" counter.
481
- // The box/discord/cli steps clear from LIVE signals (not the onboarding_state
472
+ // The discord/cli steps clear from LIVE signals (not the onboarding_state
482
473
  // row), so they must be passed in exactly as GET /me does — otherwise they
483
- // default to false and this route renders box_connect/join_discord/cli_setup
474
+ // default to false and this route renders join_discord/cli_setup
484
475
  // perpetually unchecked even after the builder cleared them (task 1444).
485
476
  const shaped = onboarding ? onboarding.shapeForApi(state, {
486
477
  shipCount,
487
478
  discordLinked: !!discordLink,
488
- boxConnected: !!boxConnected,
489
479
  cliAuthed: !!cliAuthed,
490
480
  }) : null;
491
481
  // Light cache — onboarding state changes a few times per builder, ever.
@@ -510,7 +500,7 @@ module.exports = function buildBuildersRouter() {
510
500
  // shareable without a sign-in wall (that IS the feature). It deliberately
511
501
  // WHITELISTS the public column subset off the full builder row — the private
512
502
  // columns getBuilderById also returns (anthropic_email / anthropic_user_id /
513
- // monthly_budget_usd / system_role / box_blocked*) are NEVER spread into the
503
+ // monthly_budget_usd / system_role) are NEVER spread into the
514
504
  // JSON. A leak-prevention unit test (tests/profile_route.mjs) asserts their
515
505
  // absence + the 404-on-unknown-id behaviour.
516
506
  // THROTTLED since task 1002804. The global limiter meters WRITES only
@@ -554,7 +544,7 @@ module.exports = function buildBuildersRouter() {
554
544
  ]);
555
545
  // WHITELIST — only the leaderboard-equivalent public field set. Never spread
556
546
  // the raw row: it carries anthropic_email / anthropic_user_id /
557
- // monthly_budget_usd / system_role / box_blocked* / deactivated_reason,
547
+ // monthly_budget_usd / system_role / deactivated_reason,
558
548
  // none of which are public.
559
549
  const builder = {
560
550
  id: b.id,
@@ -9,14 +9,12 @@
9
9
  // * modules + disciplines — the R54 feature-module flags
10
10
  // * rank ladder — the canonical live tiers + the instance's labels
11
11
  // * repo binding — the GitHub owner/repo for `--local` clones
12
- // * auth methods — how a stranger signs in (device-flow / web / app-pair)
12
+ // * auth methods — how a stranger signs in (device-flow / web)
13
13
  // * admission policy — invite-only vs open self-enrollment
14
- // * dev-box availability — whether the managed cloud-box backend is on
15
14
  //
16
15
  // WHY THIS EXISTS: GET /healthz returns only { ok, auth_configured } — too thin
17
- // for a CLI to brand + feature-gate itself (e.g. hide the `box` verb on an
18
- // instance that runs no dev-box module). This is the richer public descriptor
19
- // that gap calls for.
16
+ // for a CLI to brand + feature-gate itself against an instance's modules. This is
17
+ // the richer public descriptor that gap calls for.
20
18
  //
21
19
  // SECURITY (no secrets / no topology): the manifest is assembled ONLY from
22
20
  // already-client-safe projections — clientBranding() (the documented allow-list
@@ -35,7 +33,7 @@
35
33
  const express = require('express');
36
34
  const { corsPublicGet, asyncHandler } = require('./_helpers');
37
35
  const { clientBranding } = require('../../branding');
38
- const { clientModules, isModuleEnabled } = require('../../modules');
36
+ const { clientModules } = require('../../modules');
39
37
  const { loadRepoInfo } = require('../repo-info');
40
38
  const { authConfigured, openEnrollmentEnabled, loadIdpConfig, resolveSession } = require('../auth');
41
39
  const { RANK_TIER } = require('../permission-path-check');
@@ -49,15 +47,14 @@ const MANIFEST_SCHEMA = 'instance-manifest/v1';
49
47
 
50
48
  // Supported sign-in methods. All require GitHub auth to be configured (the
51
49
  // server holds the OAuth client creds): device-flow = the CLI Device Flow;
52
- // web = the browsers' hall sign-in (completeWebFlow); app-pair = the Dev Box
53
- // desktop app's browser-clicks-only pairing (/auth/app-pair/*). All three mount
54
- // unconditionally in routes/auth.js, so configured-ness is the only gate.
55
- const AUTH_METHODS = ['device-flow', 'web', 'app-pair'];
50
+ // web = the browsers' hall sign-in (completeWebFlow). Both mount unconditionally
51
+ // in routes/auth.js, so configured-ness is the only gate.
52
+ const AUTH_METHODS = ['device-flow', 'web'];
56
53
 
57
54
  // buildInstanceManifest — PURE assembler. Every input is injected so this is
58
55
  // unit-testable without disk, DB, or git. Inputs are the CLIENT-SAFE projections
59
56
  // (branding = clientBranding() output, modules = clientModules() output), the
60
- // canonical rank-tier map, loadRepoInfo()'s result, and three booleans. Returns
57
+ // canonical rank-tier map, loadRepoInfo()'s result, and two booleans. Returns
61
58
  // the JSON body verbatim.
62
59
  function buildInstanceManifest({
63
60
  branding,
@@ -66,7 +63,6 @@ function buildInstanceManifest({
66
63
  repo,
67
64
  authConfigured: authOk,
68
65
  openEnrollment,
69
- devBoxEnabled,
70
66
  idp = null,
71
67
  } = {}) {
72
68
  const b = branding || {};
@@ -109,7 +105,6 @@ function buildInstanceManifest({
109
105
  idp: idp || null,
110
106
  },
111
107
  admission: { policy: openEnrollment ? 'open' : 'invite-only' },
112
- devBox: { available: !!devBoxEnabled },
113
108
  };
114
109
  }
115
110
 
@@ -144,7 +139,6 @@ function currentManifest() {
144
139
  // what the gate will do — and 'invite-only' already covers every non-open
145
140
  // door, so narrowing it names no state (ADR 0247 D3).
146
141
  openEnrollment: openEnrollmentEnabled(),
147
- devBoxEnabled: isModuleEnabled('dev-box'),
148
142
  // Project OUT the client secret — the manifest is public; only origin +
149
143
  // client_id (both public) advertise federation.
150
144
  idp: idpCfg ? { origin: idpCfg.origin, client_id: idpCfg.clientId } : null,
@@ -74,15 +74,6 @@ function boundShipDaysToYear(rows, todayStr) {
74
74
  return o != null && o >= cutoff && o <= todayOrd;
75
75
  });
76
76
  }
77
- // The dev-box "has this builder ever connected a box?" signal, read through the
78
- // kernel seam instead of a direct `require('../boxes')` (a forbidden core→module
79
- // edge once dev-box moved to modules/dev-box/, ADR 0083 / BV1.R43). The dev-box
80
- // module provides the `box.hasEverConnected` port when enabled; resolveOptional
81
- // degrades to false on a box-less instance, so onboarding's box_connect step
82
- // simply never auto-clears there. Resolved per call (the provider registers at
83
- // boot, before any request).
84
- const boxHasEverConnected = (builderId) =>
85
- seams.resolveOptional('box.hasEverConnected', async () => false)(builderId);
86
77
  // The art-pipeline "is the shared newcomer key configured?" signal, read through
87
78
  // the kernel seam (ADR 0083 / BV1.R48). The art-pipeline module provides
88
79
  // 'art.sharedKeyConfigured' when enabled; resolveOptional degrades to false on a
@@ -192,7 +183,7 @@ module.exports = function buildMeRouter() {
192
183
  const specialities = seams.resolveOptional('specialities');
193
184
  const [
194
185
  builder, activeClaims, estVsActual, unlocked, lockedWithProgress, streakInfo,
195
- recentGrades, builderAvg, onboardingRow, shipCount, recentKarma, karma30d, discordLink, boxConnected, cliAuthed,
186
+ recentGrades, builderAvg, onboardingRow, shipCount, recentKarma, karma30d, discordLink, cliAuthed,
196
187
  behaviorPrefs, geminiKeyRow, needsRebaseTasks, foundingBuilder, helpRequestCount,
197
188
  taskRecommendationCount, boardVotesPending, socialMode, mingleIntroductionCount,
198
189
  navPermissions,
@@ -227,14 +218,8 @@ module.exports = function buildMeRouter() {
227
218
  // follow-up call. Boolean only — the discord_user_id itself is never sent
228
219
  // to the client (it's PII the UI has no need for).
229
220
  discordIsLinked(req.builder.id),
230
- // #823: whether the builder's dev box has ever reported a live Claude
231
- // process (first_connected_at). Auto-clears the onboarding 'box_connect'
232
- // step — the monotonic "properly connected to the dev box" signal.
233
- boxHasEverConnected(req.builder.id),
234
- // #777: whether the builder has ever authenticated the GDS CLI (ran
235
- // /builder-setup, or the box-connect bootstrap). Auto-clears the
236
- // onboarding 'cli_setup' step — distinct from box_connect (#823: both
237
- // steps have a place — connecting vs. signing the GDS in).
221
+ // #777: whether the builder has ever authenticated the CLI (ran
222
+ // /builder-setup). Auto-clears the onboarding 'cli_setup' step.
238
223
  db.hasCliSession(req.builder.id),
239
224
  // task 1268: the builder's behavior_prefs blob (the 'wandering' + 'render'
240
225
  // knobs), resolved against builder.rank below so /me carries the effective
@@ -431,7 +416,7 @@ module.exports = function buildMeRouter() {
431
416
  // returns a stage-1 "nothing cleared" view so the client renders the
432
417
  // checklist either way. Through the `onboarding` port (BV1.R81); null when
433
418
  // the module is off.
434
- onboarding: onboarding ? onboarding.shapeForApi(onboardingRow, { shipCount, discordLinked: !!discordLink, boxConnected: !!boxConnected, cliAuthed: !!cliAuthed }) : null,
419
+ onboarding: onboarding ? onboarding.shapeForApi(onboardingRow, { shipCount, discordLinked: !!discordLink, cliAuthed: !!cliAuthed }) : null,
435
420
  // F7 (ADR 0033): Discord link state for the hall's Connect/Unlink UI.
436
421
  // `configured` tells the client whether the server even has Discord OAuth
437
422
  // creds — when false the hall renders nothing (the start route 503s).
@@ -525,7 +510,7 @@ module.exports = function buildMeRouter() {
525
510
  };
526
511
  // Module→core contribution seam (BV1.R42, ADR 0083). Let each enabled module
527
512
  // add its own profile slice instead of core hardcoding it — the strangler
528
- // prerequisite that lets dev-box/discord/art leave this route. ADD-ONLY (core
513
+ // prerequisite that lets discord/art leave this route. ADD-ONLY (core
529
514
  // keys win); a collision means a move task took over a core key without
530
515
  // dropping the core copy. Empty until a module registers → byte-identical today.
531
516
  const slices = await seams.collectContributions('profile.fields', {
@@ -570,14 +555,13 @@ module.exports = function buildMeRouter() {
570
555
  }
571
556
  // Echo the onboarding state back so the hall's path checklist can tick
572
557
  // this step off without a follow-up /me round-trip.
573
- const [state, shipCount, discordLink, boxConnected, cliAuthed] = await Promise.all([
558
+ const [state, shipCount, discordLink, cliAuthed] = await Promise.all([
574
559
  onboardingPort ? onboardingPort.getState(pool, req.builder.id) : null,
575
560
  lc().countShippedTasksForBuilder(req.builder.id),
576
561
  discordIsLinked(req.builder.id),
577
- boxHasEverConnected(req.builder.id),
578
562
  db.hasCliSession(req.builder.id),
579
563
  ]);
580
- const onboarding = onboardingPort ? onboardingPort.shapeForApi(state, { shipCount, discordLinked: !!discordLink, boxConnected: !!boxConnected, cliAuthed: !!cliAuthed }) : null;
564
+ const onboarding = onboardingPort ? onboardingPort.shapeForApi(state, { shipCount, discordLinked: !!discordLink, cliAuthed: !!cliAuthed }) : null;
581
565
  res.json({ builder, onboarding });
582
566
  } catch (err) {
583
567
  if (err.code === 'invalid_discipline') {
@@ -17,6 +17,12 @@
17
17
  // authoritative check — `bongos module submit`
18
18
  // is a thin CLI client of this same route).
19
19
  // GET /modules/submissions list queued submissions (task 1771 reads this).
20
+ // POST /store/modules/:key/versions
21
+ // publish one module version to the Bongos-hosted
22
+ // module store (task 1004271, ADR 0338 D1) — the
23
+ // server end of `bongos module publish`. Not the
24
+ // upstreaming door above: submit proposes a module
25
+ // INTO core; publish puts a version in the store.
20
26
  //
21
27
  // Read is open to any signed-in builder (same gate as the atlas/primer pages);
22
28
  // every write (enable/disable, submit) is metic+archon, same rank as
@@ -58,6 +64,9 @@ const modulesLib = require('../../modules');
58
64
  // server process.
59
65
  const moduleCli = require('../../../scripts/gds/module');
60
66
  const moduleSubmissions = require('../module-submissions');
67
+ const moduleArtifact = require('../../../scripts/gds/module-artifact');
68
+ const { stageArtifact, commitArtifact, removeArtifact, relativeArtifactPath, publishVersion } = require('../module-store');
69
+ const { KEY_RE } = require('../../module-loader/manifest-schema');
61
70
  // Named imports, not a namespace object (task 1004052 strand walk): knip cannot follow
62
71
  // property access on a CommonJS namespace, so reaching these through one made them read
63
72
  // as unused exports and tripped the dead-code ratchet that held this branch out of main.
@@ -70,6 +79,21 @@ const { listOverrides, setOverride } = require('../module-overrides');
70
79
  // cap (SECURITY_DOC_CONTENT_MAX) in routes/security.js.
71
80
  const SUBMISSION_MAX_BYTES = 5_000_000;
72
81
 
82
+ // A published version's body is the tarball itself, never JSON (the global parser
83
+ // only reads application/json, so it passes this through). Capped at the parser and
84
+ // never inflated from a Content-Encoding, so the cap is the bytes on the wire.
85
+ const TARBALL_BODY_TYPES = ['application/gzip', 'application/x-gzip', 'application/octet-stream'];
86
+ const rawTarball = express.raw({ type: TARBALL_BODY_TYPES, limit: moduleArtifact.MAX_TARBALL_BYTES, inflate: false });
87
+ function tarballBody(req, res, next) {
88
+ rawTarball(req, res, (err) => {
89
+ if (!err) return next();
90
+ if (err.type === 'entity.too.large') {
91
+ return res.fail('artifact_too_large', { status: 413, message: `The tarball is over the ${moduleArtifact.MAX_TARBALL_BYTES}-byte store cap.` });
92
+ }
93
+ return res.fail('bad_tarball', { status: 400, message: 'The upload could not be read.' });
94
+ });
95
+ }
96
+
73
97
  function ownProjectName() {
74
98
  try { return require('../../branding').branding().identity.productName; } catch { return null; }
75
99
  }
@@ -264,6 +288,65 @@ module.exports = function buildModulesRouter() {
264
288
  res.json({ submissions });
265
289
  }));
266
290
 
291
+ // POST /api/bongos/store/modules/:key/versions — publish one module version to
292
+ // the store (task 1004271, ADR 0338 D1). Body: the gzip tarball
293
+ // `bongos module publish` builds (scripts/gds/module-artifact.js), sent as
294
+ // application/gzip. The route trusts nothing but the bytes: it recomputes every
295
+ // hash, re-runs the publish denylist and validates module.json itself
296
+ // (verifyModuleArtifact), then keeps the tarball on the control plane's disk and
297
+ // INSERTs the version row in one transaction (module-store.js). A key's first
298
+ // publish makes the caller its author; after that only the author may publish,
299
+ // a delisted key takes nothing new, and a version is published once and never
300
+ // changed.
301
+ //
302
+ // rank: metic+archon — it puts code in a shared store other instances install
303
+ // from. Gate: requirePermission('module.submit') — the atom that already guards
304
+ // filing a module into a shared queue; a dedicated `module.publish` atom is a
305
+ // follow-up once who-may-sell is decided.
306
+ router.post('/store/modules/:key/versions', auth.requireBuilder, auth.requirePermission('module.submit'), tarballBody,
307
+ asyncHandler('POST /store/modules/:key/versions', async (req, res) => {
308
+ const { key } = req.params;
309
+ if (!KEY_RE.test(key)) {
310
+ return res.fail('bad_module_key', { status: 400, message: 'A module key is lowercase kebab-case.' });
311
+ }
312
+ const tgz = Buffer.isBuffer(req.body) ? req.body : null;
313
+ // The audit log serializes req.body; a tarball there would be megabytes of
314
+ // byte array. Leave it a one-line marker instead.
315
+ req.body = { tarball_bytes: tgz ? tgz.length : 0 };
316
+ if (!tgz || !tgz.length) {
317
+ return res.fail('empty_artifact', { status: 400, message: 'Send the tarball as the body, Content-Type: application/gzip.' });
318
+ }
319
+
320
+ const v = await moduleArtifact.verifyModuleArtifact(tgz, { key });
321
+ if (!v.ok) {
322
+ return res.fail(v.code, { status: v.code === 'artifact_too_large' ? 413 : 422, message: v.message });
323
+ }
324
+
325
+ const staged = await stageArtifact(tgz, { key, version: v.version });
326
+ let result;
327
+ try {
328
+ result = await publishVersion({
329
+ key,
330
+ title: v.moduleJson.title,
331
+ description: v.moduleJson.description,
332
+ version: v.version,
333
+ coreVersion: v.coreVersion,
334
+ manifest: v.manifest,
335
+ treeSha256: v.treeSha256,
336
+ tarballSha256: v.tarballSha256,
337
+ bytes: v.bytes,
338
+ artifactPath: relativeArtifactPath(staged.final),
339
+ builderId: req.builder.id,
340
+ }, { beforeCommit: () => commitArtifact(staged) });
341
+ } finally {
342
+ await removeArtifact(staged.tmp);
343
+ }
344
+ if (!result.ok) return res.fail(result.code, { status: result.status, message: result.message });
345
+
346
+ console.log(`[gds] module "${key}" ${v.version} published to the store by builder ${req.builder.id}`);
347
+ res.status(201).json({ ok: true, created_module: result.created, version: result.version });
348
+ }));
349
+
267
350
  return router;
268
351
  };
269
352
 
@@ -113,9 +113,9 @@ module.exports = function buildSecurityRouter() {
113
113
  //
114
114
  // The DATED security engagement reports (exploit detail — full repro,
115
115
  // evidence, the attacker cookbook) are served from the DB, NOT committed to
116
- // the git clone: the repo is cloned in full onto every Metic+ dev box
117
- // (threat-model §6.3 / ADR 0020), so a committed exploit report is an
118
- // attacker cookbook on every box. The SAFE conceptual docs (threat-model.md,
116
+ // the git clone: the repo is cloned in full onto every Metic+ builder's
117
+ // machine (threat-model §6.3 / ADR 0020), so a committed exploit report is an
118
+ // attacker cookbook on every machine. The SAFE conceptual docs (threat-model.md,
119
119
  // the README) stay in docs/security/; only the dated reports live here, and
120
120
  // ALL three routes gate on requirePermission('security.report.adjudicate')
121
121
  // — an archon-floored atom (BV1.R104; was requireRank('archon') and admits
@@ -96,6 +96,11 @@ function buildGdsRouter() {
96
96
  // encoded (4 MB decoded). Same dynamic-:id pattern as publish-branch above;
97
97
  // modules/lifecycle/routes/task-visuals.js mounts its own 8 MB parser.
98
98
  /^\/tasks\/\d+\/visual$/.test(req.path) ||
99
+ // task 1004358: the named-slot sibling (TW11 — the eight page-tweak renders).
100
+ // Same 8 MB parser in modules/lifecycle/routes/visuals.js; a full-page render
101
+ // is ~90-130 KB, so without this every attach 413'd here first. The route
102
+ // validates the slot name itself, so the pattern only needs to be one segment.
103
+ /^\/tasks\/\d+\/visuals\/[^/]+$/.test(req.path) ||
99
104
  // task 1003850: the grade body carries committed_files — EVERY changed path, tens of
100
105
  // KB on its own for a mass change — plus the panel's findings. A 1,141-file rename
101
106
  // (task 1003705) produced a body over the 64 KB cap, so the grade could not be
@@ -162,8 +167,8 @@ function buildGdsRouter() {
162
167
  // core to every instance, not an OTB feature.
163
168
  router.use(buildLiveRouter());
164
169
  // Feature-module-gated mounts (R54, task 1193, ADR 0062 §4). Off on a vanilla
165
- // instance; ON for OTB via config/modules.json so prod is unchanged. dev-box,
166
- // art-pipeline, and discord (with its bug-attachments route) have all graduated
170
+ // instance; ON for OTB via config/modules.json so prod is unchanged. art-pipeline
171
+ // and discord (with its bug-attachments route) have both graduated
167
172
  // into real modules (modules/<key>/) — they are discovered + mounted by
168
173
  // moduleLoader.mountModuleRoutes below, not here (BV1.R43 / R48 / R47 / R72).
169
174
  // gate-approvals likewise moved to the lifecycle module (BV1.R86).
@@ -622,8 +622,8 @@ function mountInternalSurfaces(app) {
622
622
  //
623
623
  // Dual-mount (task 1919): the router + this discovery index are mounted at the
624
624
  // canonical API_PREFIX (/api/bongos) AND every LEGACY_API_PREFIXES entry
625
- // (/api/gds) — the legacy alias stays FOREVER so shipped Dev Box binaries, the
626
- // cached status mirror, and live boxes keep working (src/bongos/api-prefix.js). The
625
+ // (/api/gds) — the legacy alias stays FOREVER so older CLI releases and the
626
+ // cached status mirror keep working (src/bongos/api-prefix.js). The
627
627
  // SAME router instance is mounted at each path. Each discovery GET is registered
628
628
  // BEFORE its router mount so the exact base-path GET wins; every other
629
629
  // method/subpath falls through to the router.
@@ -670,7 +670,7 @@ function mountInternalSurfaces(app) {
670
670
  // below only prefixes non-/api paths, so without this placement /downloads/*
671
671
  // would be rewritten to /builders/downloads/* and 404). The installer scripts are served with the
672
672
  // server's PINNED origin substituted for __GDS_API_BASE__ — never a request
673
- // header, so a spoofed Host can't redirect a `curl | sh` (the box-connect.sh rule).
673
+ // header, so a spoofed Host can't redirect a `curl | sh`.
674
674
  const bongosCfg = tryLoadInstanceConfig('bongos-cli.json');
675
675
  if (bongosCfg) {
676
676
  const bongosDownloads = buildBongosDownloads(bongosCfg);
@@ -894,8 +894,7 @@ function mountInternalSurfaces(app) {
894
894
  // them to the hall, and an unauthenticated visitor into sign-in). The subdomain
895
895
  // shim above has already rewritten the bare /watch + /gate onto /builders/*.
896
896
  // (Defense in depth: every data endpoint these pages call carries its own
897
- // server-side gate.) /harbor sat here too until task 1003890 removed the dev
898
- // box: its page is gone, and a gate in front of nothing is a dead guard.
897
+ // server-side gate.)
899
898
  const WATCH_PAGE_RE = /^\/builders\/watch(?:\.html)?\/?$/;
900
899
  const GATE_PAGE_RE = /^\/builders\/gate(?:\.html)?\/?$/;
901
900
  // /sessions (the session-log ledger) belongs to that same Governance group and
@@ -1144,7 +1143,7 @@ function mountInternalSurfaces(app) {
1144
1143
  // the landing card + GitHub button render for a stranger.
1145
1144
  //
1146
1145
  // ONE tier of gate: ANY SIGNED-IN BUILDER — the Work Board, Roadmap, the Goals
1147
- // page, the task-detail page, the Repo Atlas, the Dev Box pair page, Settings
1146
+ // page, the task-detail page, the Repo Atlas, Settings
1148
1147
  // (#748, #904, task 1293, task 1757, task 751) and, since task 1002710, the
1149
1148
  // orientation reading rooms (primer, diagrams). A Xenos IS allowed; an
1150
1149
  // unauthenticated visitor is bounced into sign-in rather than served the shell.
@@ -4,8 +4,7 @@
4
4
  // The compiled per-platform binaries can't ride the git-based deploy (private
5
5
  // repo, no LFS, GitHub's 100MB cap), so CI publishes them as release assets on a
6
6
  // PUBLIC releases repo (config/bongos-cli.json — the same off-repo pattern as the
7
- // Dev Box app, src/devbox-downloads.js, and the ADR 0029 status mirror). This
8
- // module turns that committed config into:
7
+ // ADR 0029 status mirror). This module turns that committed config into:
9
8
  //
10
9
  // GET /downloads/bongos/latest.json → { version, assets: { target: {file,url} } }
11
10
  // GET /downloads/bongos/version → { version, assets: { target: url } }
@@ -14,10 +13,6 @@
14
13
  // so every URL a user sees or the installer fetches stays on the instance apex,
15
14
  // and a release bump is a one-file config change that rides the normal ship flow.
16
15
  // Pure (config in, urls out) so tests need no server.
17
- //
18
- // (A near-twin of src/devbox-downloads.js by design — same off-repo release
19
- // pattern, different artifact. Kept as its own module for a clear name + its own
20
- // config pointer; the two are independent instance features.)
21
16
 
22
17
  function buildBongosDownloads(cfg) {
23
18
  if (!cfg || typeof cfg !== 'object') throw new Error('bongos-downloads: missing config');
package/src/branding.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/branding.js — the branding contract (ADR 0062 §3, task 1192 / V4.R53).
2
2
  //
3
3
  // The single source for every instance-identity string the platform renders:
4
- // product/world name, company, domains/origins, cookie + OAuth + dev-box
4
+ // product/world name, company, domains/origins, cookie + OAuth
5
5
  // hosts, currency label, the track set, theme, and the project name injected
6
- // into LLM prompts. Generalizes devbox-app/src/brand.js to the whole platform.
6
+ // into LLM prompts.
7
7
  //
8
8
  // Resolution precedence (highest wins):
9
9
  // env override -> instance config (config/branding.json) -> neutral
package/src/build-info.js CHANGED
@@ -41,12 +41,10 @@ function readGitSha() {
41
41
  }
42
42
  }
43
43
 
44
- // The deployed commit's date (ISO 8601), for the dev-box staleness comparison
45
- // (boxes.computeCodeStaleness, idea 332). A box reports its own HEAD commit date;
46
- // if it's > 2 days behind THIS, the box is flagged stale. Best-effort: after a
47
- // `git reset --hard origin/main` deploy, HEAD is the deployed commit, so its
48
- // committer date is the right reference. Null when git is unavailable (then the
49
- // "behind" check is skipped and only never-reported boxes flag).
44
+ // The deployed commit's date (ISO 8601), served on /version beside the commit.
45
+ // Best-effort: after a `git reset --hard origin/main` deploy, HEAD is the
46
+ // deployed commit, so its committer date is the right reference. Null when git
47
+ // is unavailable.
50
48
  function readGitCommitDate() {
51
49
  try {
52
50
  return execSync('git show -s --format=%cI HEAD', {