@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
@@ -1,15 +1,12 @@
1
1
  // scripts/gds/do-api.js — a thin DigitalOcean API v2 client for the control plane.
2
2
  //
3
- // Shared control-plane client (ADR 0111 §2, task #1943): the DO client both the
4
- // dev-box CLI (scripts/gds/box.js) and the instance-provisioning runner
5
- // (scripts/gds/provision.js) use. It lived in modules/dev-box/ historically, but
6
- // it is operator-CLI code (never the web tier), so it belongs in the control-plane
7
- // tier alongside its callers — and a module may not import a sibling module, so two
8
- // CLIs can only share it from here. Originally #598 (box lifecycle).
3
+ // Control-plane client (ADR 0111 §2, task #1943): the DO client the
4
+ // instance-provisioning runner (scripts/gds/provision.js) and its teardown
5
+ // (provision-teardown.js) use. It is operator-CLI code (never the web tier), so it
6
+ // lives in the control-plane tier alongside its callers. Originally #598.
9
7
  //
10
- // The ONLY network module in the box-lifecycle subsystem. Everything that
11
- // touches DO goes through here so the rest of the code (boxes.js decisions,
12
- // box.js orchestration) stays testable without a live account.
8
+ // Everything that touches DO goes through here so the provisioning code stays
9
+ // testable without a live account.
13
10
  //
14
11
  // Design:
15
12
  // - Injectable transport (`fetchImpl`) so tests pass a fake fetch and assert
@@ -23,10 +20,8 @@
23
20
  // - Live mode requires a token (DO_API_TOKEN); the constructor throws clearly
24
21
  // if asked to run live without one — it never silently no-ops a real call.
25
22
  //
26
- // We implement only the calls the lifecycle needs:
27
- // createDroplet · getDroplet · deleteDroplet · listDroplets
28
- // snapshotDroplet (action) · getAction (poll) · deleteSnapshot
29
- // createDropletFromSnapshot
23
+ // We implement only the calls provisioning needs:
24
+ // createDroplet · getDroplet · deleteDroplet · settleDroplet (poll to active)
30
25
 
31
26
  const { JsonHttpApi, makeApiError } = require('./http-api-client');
32
27
 
@@ -54,7 +49,7 @@ class DoApi extends JsonHttpApi {
54
49
 
55
50
  // ---- droplets --------------------------------------------------------------
56
51
 
57
- // image: either a base image slug (e.g. 'docker-20-04') OR a snapshot id (int).
52
+ // image: a base image slug (e.g. 'ubuntu-24-04-x64').
58
53
  async createDroplet({ name, region, size, image, userData, tags, sshKeys }) {
59
54
  const body = {
60
55
  name, region, size, image,
@@ -79,53 +74,9 @@ class DoApi extends JsonHttpApi {
79
74
  return this._req('DELETE', `/droplets/${dropletId}`, null, { dryRunResult: { deleted: true } });
80
75
  }
81
76
 
82
- // List every droplet carrying `tag` (our fleet tag, e.g. otb-builder-box). The
83
- // input to the drift reconcile (task 1289): compare these REAL droplets against
84
- // the GDS box rows to find zombies (a droplet no live box claims) and ghosts (a
85
- // box row whose droplet vanished). per_page=200 covers the whole fleet in one
86
- // page at our scale (<100 boxes); a future larger fleet would follow
87
- // links.pages.next. Returns { droplets: [...] }; dry-run returns an empty list
88
- // (the reconcile builds its OWN live read client so planning still sees drift).
89
- async listDroplets({ tag } = {}) {
90
- const qs = tag ? `?tag_name=${encodeURIComponent(tag)}&per_page=200` : '?per_page=200';
91
- return this._req('GET', `/droplets${qs}`, null, { dryRunResult: { droplets: [] } });
92
- }
93
-
94
- // ---- snapshots (via droplet actions) --------------------------------------
95
-
96
- // Snapshot a running droplet. DO recommends powering off first for a clean
97
- // snapshot; the orchestrator powers off then calls this. Returns an ACTION
98
- // whose completion must be polled (getAction) before the droplet is destroyed.
99
- async snapshotDroplet(dropletId, snapshotName) {
100
- return this._req('POST', `/droplets/${dropletId}/actions`, { type: 'snapshot', name: snapshotName }, {
101
- dryRunResult: { action: { id: `dryrun-action-${dropletId}`, status: 'completed', type: 'snapshot' } },
102
- });
103
- }
104
-
105
- async powerOffDroplet(dropletId) {
106
- return this._req('POST', `/droplets/${dropletId}/actions`, { type: 'power_off' }, {
107
- dryRunResult: { action: { id: `dryrun-poweroff-${dropletId}`, status: 'completed' } },
108
- });
109
- }
110
-
111
- async getAction(actionId) {
112
- return this._req('GET', `/actions/${actionId}`, null, {
113
- dryRunResult: { action: { id: actionId, status: 'completed' } },
114
- });
115
- }
116
-
117
- async deleteSnapshot(snapshotId) {
118
- return this._req('DELETE', `/snapshots/${snapshotId}`, null, { dryRunResult: { deleted: true } });
119
- }
120
-
121
- async createDropletFromSnapshot({ name, region, size, snapshotId, userData, tags, sshKeys }) {
122
- return this.createDroplet({ name, region, size, image: snapshotId, userData, tags, sshKeys });
123
- }
124
-
125
- // Shared poll primitive behind waitForAction + settleDroplet (the box
126
- // subsystem's two DO poll loops, unified per the #1092 cleanup). Calls step(i)
127
- // each round; step returns { done:true, value } to stop, or a falsy value to keep
128
- // polling. Returns the stopping value, or { timedOut:true } after `tries` rounds.
77
+ // Poll primitive behind settleDroplet (#1092). Calls step(i) each round; step
78
+ // returns { done:true, value } to stop, or a falsy value to keep polling.
79
+ // Returns the stopping value, or { timedOut:true } after `tries` rounds.
129
80
  // `sleepFn` lets tests drive the loop with no real clock.
130
81
  async _poll({ tries = 60, sleepMs = 5000, sleepFn } = {}, step) {
131
82
  const sleep = sleepFn || ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -137,26 +88,8 @@ class DoApi extends JsonHttpApi {
137
88
  return { timedOut: true };
138
89
  }
139
90
 
140
- // Poll an action to completion. Returns true if 'completed', throws on 'errored'.
141
- // sleepFn lets tests drive the loop deterministically (no real sleeping).
142
- async waitForAction(actionId, { tries = 60, sleepMs = 5000, sleepFn } = {}) {
143
- if (this.dryRun) return true;
144
- const out = await this._poll({ tries, sleepMs, sleepFn }, async () => {
145
- const { action } = await this.getAction(actionId);
146
- if (action.status === 'completed') return { done: true, value: true };
147
- if (action.status === 'errored') {
148
- throw new DoApiError(`DO action ${actionId} errored`, { body: action });
149
- }
150
- return null;
151
- });
152
- if (out && out.timedOut) throw new DoApiError(`DO action ${actionId} did not complete in time`);
153
- return out;
154
- }
155
-
156
91
  // Wait until a freshly-created droplet is 'active' and return its public IPv4.
157
- // Lives here (alongside waitForAction) so the box subsystem's two DO poll loops
158
- // share one primitive; scripts/gds/box.js calls it as a method. dry-run returns
159
- // no IP (it never created a real droplet).
92
+ // dry-run returns no IP (it never created a real droplet).
160
93
  async settleDroplet(dropletId, { tries = 60, sleepMs = 5000, sleepFn } = {}) {
161
94
  if (this.dryRun) return { ip: null };
162
95
  const out = await this._poll({ tries, sleepMs, sleepFn }, async () => {
@@ -41,6 +41,8 @@ const DOC_CLI_ALLOW = new Map([
41
41
  'art-stage.js ships with the art-pipeline HOST module, which publish-manifest.js excludes from core — absent from a core-only checkout by design; the skill says so inline.'],
42
42
  ['.claude/skills/otb-tile-generate/SKILL.md::scripts/gds/art-stage.js',
43
43
  'same as paint/SKILL.md — the art staging CLI travels with the excluded art-pipeline module, not the portable core.'],
44
+ ['docs/session-logs/2026-09-10-task-1003507-the-box-that-could-not-be-reaped.md::scripts/gds/box.js',
45
+ 'a dated session log — the historical record, never rewritten — of work on a CLI the dev-box removal deleted (task 1003893); it describes what was run then, not what to run now.'],
44
46
  ]);
45
47
 
46
48
  // `node scripts/gds/foo.js` … up to the end of the line/segment, so the flags that
@@ -167,7 +167,7 @@ function checkToolchain() {
167
167
  // Check 3 — Git plumbing
168
168
  // --------------------------------------------------------------------------
169
169
  // Pure verdict for the hooks gate, extracted so the truth table is testable
170
- // (tests/doctor_hooks.mjs) — the box-auth-check `decideBoxAuth` precedent.
170
+ // (tests/doctor_hooks.mjs).
171
171
  //
172
172
  // `.husky/` is INSTANCE topology, not shipped in the portable core (README
173
173
  // "Secrets scanning"). So its absence is not a builder error: it means ADR 0043
@@ -371,7 +371,7 @@ function checkDeployTooling() {
371
371
  }
372
372
 
373
373
  // ADR 0055 / task 1025: in ci mode there are two publish paths. A machine with
374
- // no GitHub push credential (a dev box) ships SERVER-MEDIATED — it uploads its
374
+ // no GitHub push credential ships SERVER-MEDIATED — it uploads its
375
375
  // branch to the GDS, which pushes + opens the PR with the server's credential —
376
376
  // so gh is NOT needed here. Resolve the same path ship.js will take and skip the
377
377
  // gh requirement when it's server-mediated.
@@ -382,7 +382,7 @@ function checkDeployTooling() {
382
382
  /* keep 'local'; the gh checks below are the safe, stricter default */
383
383
  }
384
384
  if (via === 'server') {
385
- ok('deploy', `Deploy mode: ci (server-mediated publish) — this machine has no GitHub push credential, so the GDS pushes your branch + opens the PR for you. gh is not required here. NOTE: on a dev box, run /builder-reauth (browser) first so your session is a real cli session, not the box-scoped one.`);
385
+ ok('deploy', `Deploy mode: ci (server-mediated publish) — this machine has no GitHub push credential, so the GDS pushes your branch + opens the PR for you. gh is not required here.`);
386
386
  return;
387
387
  }
388
388
 
@@ -425,7 +425,7 @@ function checkDeployTooling() {
425
425
 
426
426
  // Intent-runner drift (task 1002722): a runner on a stale core once turned a domain-attach
427
427
  // into a teardown (task 1002716). Only a host with *intent-runner* units shows this section,
428
- // so a builder laptop or dev box sees nothing.
428
+ // so a builder laptop sees nothing.
429
429
  function runnerDriftRows(results) {
430
430
  return (results || []).map((r) => {
431
431
  const msg = `${r.name}: ${r.reason}`;
@@ -3,8 +3,8 @@
3
3
  // /feedback Claude Code slash command.
4
4
  //
5
5
  // Resolves the LATEST feedback bundle the Bongos app wrote to
6
- // ~/CloudBongos/feedback/ (via the shared `latest` pointer in
7
- // bongos-app/src/handoff.js) and prints it for a Claude Code session:
6
+ // ~/CloudBongos/feedback/ (via the shared `latest` pointer, ADR 0087) and
7
+ // prints it for a Claude Code session:
8
8
  //
9
9
  // 1. the full prompt.md body (the model-agnostic transcript + ask, R11), and
10
10
  // 2. the ABSOLUTE path of every attached frame, on its own line, so Claude's
@@ -20,13 +20,139 @@
20
20
  //
21
21
  // Local + read-only: it reads files under your home dir. No GDS session, no
22
22
  // network — so it works on any machine the Bongos app runs on.
23
+ //
24
+ // resolveLatest + feedbackBaseDir are ported here from the app's
25
+ // bongos-app/src/handoff.js, which no longer lives in this repo (the desktop
26
+ // apps publish off-repo, ADR 0098 — task 1004356). The contract is ADR 0087's:
27
+ // `latest.json` is authoritative, the `latest/` symlink is a convenience that
28
+ // may be absent, and with neither the reader self-heals by scanning for the
29
+ // newest bundle dir holding a prompt.md.
23
30
 
24
31
  'use strict';
25
32
 
33
+ const fs = require('node:fs/promises');
34
+ const os = require('node:os');
26
35
  const path = require('node:path');
27
- const { resolveLatest, feedbackBaseDir } = require(
28
- path.join(__dirname, '..', '..', 'bongos-app', 'src', 'handoff.js'),
29
- );
36
+
37
+ const IMAGE_EXT = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif']);
38
+
39
+ /** Where the Bongos app writes bundles: ~/CloudBongos/feedback. */
40
+ function feedbackBaseDir() {
41
+ return path.join(os.homedir(), 'CloudBongos', 'feedback');
42
+ }
43
+
44
+ async function isBundleDir(dir) {
45
+ try {
46
+ return (await fs.stat(path.join(dir, 'prompt.md'))).isFile();
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ // The dir `latest.json` names, or null if it is missing, unreadable or stale.
53
+ // Accepts the pointer by `dir` (absolute) or by `id` (a child of baseDir).
54
+ async function fromPointer(baseDir) {
55
+ let ptr;
56
+ try {
57
+ ptr = JSON.parse(await fs.readFile(path.join(baseDir, 'latest.json'), 'utf8'));
58
+ } catch {
59
+ return null;
60
+ }
61
+ if (!ptr || typeof ptr !== 'object') return null;
62
+ const candidates = [];
63
+ if (typeof ptr.dir === 'string' && ptr.dir) candidates.push(path.resolve(baseDir, ptr.dir));
64
+ if (typeof ptr.id === 'string' && ptr.id && !/[\\/]/.test(ptr.id)) candidates.push(path.join(baseDir, ptr.id));
65
+ for (const dir of candidates) if (await isBundleDir(dir)) return dir;
66
+ return null;
67
+ }
68
+
69
+ async function fromSymlink(baseDir) {
70
+ const link = path.join(baseDir, 'latest');
71
+ if (!(await isBundleDir(link))) return null;
72
+ try {
73
+ return await fs.realpath(link);
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ // Self-heal: the newest child dir (by prompt.md mtime, then name) with a prompt.md.
80
+ async function fromScan(baseDir) {
81
+ let entries;
82
+ try {
83
+ entries = await fs.readdir(baseDir, { withFileTypes: true });
84
+ } catch {
85
+ return null;
86
+ }
87
+ let best = null;
88
+ for (const e of entries) {
89
+ if (!e.isDirectory() || e.name === 'latest') continue;
90
+ const dir = path.join(baseDir, e.name);
91
+ let st;
92
+ try {
93
+ st = await fs.stat(path.join(dir, 'prompt.md'));
94
+ } catch {
95
+ continue;
96
+ }
97
+ if (!st.isFile()) continue;
98
+ if (!best || st.mtimeMs > best.mtimeMs || (st.mtimeMs === best.mtimeMs && e.name > best.name)) {
99
+ best = { dir, name: e.name, mtimeMs: st.mtimeMs };
100
+ }
101
+ }
102
+ return best && best.dir;
103
+ }
104
+
105
+ // The bundle's frames as absolute paths: those prompt.md references (in the
106
+ // order the walkthrough cites them) that exist, then any other image in the dir.
107
+ async function collectImages(dir, promptMarkdown) {
108
+ const out = [];
109
+ const seen = new Set();
110
+ const add = (p) => {
111
+ const key = process.platform === 'win32' ? p.toLowerCase() : p;
112
+ if (!seen.has(key)) { seen.add(key); out.push(p); }
113
+ };
114
+ for (const m of promptMarkdown.matchAll(/!\[[^\]]*\]\(\s*<?([^)>]+?)>?\s*\)/g)) {
115
+ const p = path.resolve(dir, m[1]);
116
+ if (!IMAGE_EXT.has(path.extname(p).toLowerCase())) continue;
117
+ try {
118
+ if ((await fs.stat(p)).isFile()) add(p);
119
+ } catch { /* referenced but absent — skip */ }
120
+ }
121
+ const walk = async (d, depth) => {
122
+ let entries;
123
+ try {
124
+ entries = await fs.readdir(d, { withFileTypes: true });
125
+ } catch {
126
+ return;
127
+ }
128
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
129
+ for (const e of entries) {
130
+ const p = path.join(d, e.name);
131
+ if (e.isDirectory() && depth > 0) await walk(p, depth - 1);
132
+ else if (e.isFile() && IMAGE_EXT.has(path.extname(e.name).toLowerCase())) add(p);
133
+ }
134
+ };
135
+ await walk(dir, 1);
136
+ return out;
137
+ }
138
+
139
+ /**
140
+ * The newest feedback bundle under baseDir, or null if there is none.
141
+ * @returns {Promise<null|{id:string,dir:string,promptPath:string,promptMarkdown:string,imagePaths:string[]}>}
142
+ */
143
+ async function resolveLatest(baseDir = feedbackBaseDir()) {
144
+ const dir = (await fromPointer(baseDir)) || (await fromSymlink(baseDir)) || (await fromScan(baseDir));
145
+ if (!dir) return null;
146
+ const promptPath = path.join(dir, 'prompt.md');
147
+ const promptMarkdown = await fs.readFile(promptPath, 'utf8');
148
+ return {
149
+ id: path.basename(dir),
150
+ dir,
151
+ promptPath,
152
+ promptMarkdown,
153
+ imagePaths: await collectImages(dir, promptMarkdown),
154
+ };
155
+ }
30
156
 
31
157
  function parseArgs(argv) {
32
158
  const out = { json: false, base: undefined };
@@ -88,7 +214,11 @@ async function main() {
88
214
  process.stdout.write(`${lines.join('\n')}\n`);
89
215
  }
90
216
 
91
- main().catch((err) => {
92
- process.stderr.write(`feedback-latest: ${err.message}\n`);
93
- process.exitCode = 1;
94
- });
217
+ module.exports = { resolveLatest, feedbackBaseDir };
218
+
219
+ if (require.main === module) {
220
+ main().catch((err) => {
221
+ process.stderr.write(`feedback-latest: ${err.message}\n`);
222
+ process.exitCode = 1;
223
+ });
224
+ }
@@ -71,10 +71,6 @@ const IDENTITY_SCAN_EXTRA_FILES = [];
71
71
  // one, and any file NOT listed must have zero hits. Getting better never fails.
72
72
  //
73
73
  // Why each is legitimate:
74
- // * box.js — REAL Cloudflare/DNS zone names for the operator plane
75
- // (`dev.<host>`, the terminal zone). These are infrastructure identifiers,
76
- // not an instance URL; routing them through branding would be wrong. (Its
77
- // two genuine API-base defaults WERE fixed in 1002428.)
78
74
  // * publish-manifest.js — the literal FILENAMES of three real committed docs
79
75
  // (the two decouple recipes, plus the instance's own name/trademark ADR that
80
76
  // task 1003869 excluded: its prose redacts cleanly but the FILENAME rides in
@@ -91,8 +87,8 @@ const IDENTITY_SCAN_EXTRA_FILES = [];
91
87
  // 1002567). Each is a HOST-PINNED OPS script, not portable code: they name the
92
88
  // live box's actual Postgres database/role and its actual /etc secrets dir, and
93
89
  // every one of those literals is already only the DEFAULT behind an env override
94
- // (GDS_BACKUP_DB, PROD_DB/STAGING_DB, PG_SOCKET). Same rationale as box.js's real
95
- // DNS zones — an infrastructure identifier, not an instance URL, and the systemd
90
+ // (GDS_BACKUP_DB, PROD_DB/STAGING_DB, PG_SOCKET). Each is an infrastructure
91
+ // identifier, not an instance URL, and the systemd
96
92
  // units on that box invoke these by path. Re-pointing an instance's backups is
97
93
  // deployment work, not a branding read. Their COUNT is still frozen here, so a
98
94
  // NEW literal in any of them fails exactly like one in an unlisted file.
@@ -116,7 +112,6 @@ const IDENTITY_SCAN_ALLOWED = {
116
112
  // instance it declines to run instead of destroying the wrong database.
117
113
  'scripts/gds/seed-staging.sh': 4,
118
114
  'scripts/gds/backfill-sessions.js': 2,
119
- 'scripts/gds/box.js': 3,
120
115
  'scripts/gds/publish-manifest.js': 3,
121
116
  'scripts/gds/seed-bongos-coreB-tranche1-tasks.js': 1,
122
117
  'scripts/gds/seed-bongos-coreB-tranche2-tasks.js': 1,
@@ -24,7 +24,7 @@ const { CORE_ROOT, rel, walkJs, walkJsAllRoots } = require('./fitness-lib.js');
24
24
  // ============================================================================
25
25
  //
26
26
  // A vanilla self-host boot ships only the PUBLISHABLE tree (publish-manifest.js). An
27
- // instance-only config (config/devbox-app.json, config/bongos-cli.json, config/branding.json,
27
+ // instance-only config (config/bongos-cli.json, config/branding.json,
28
28
  // config/modules.json, …) is boundary-EXCLUDED, so a bare `require('../../config/<excluded>.json')`
29
29
  // in core code throws MODULE_NOT_FOUND and CRASHES the boot on a fresh instance — the exact
30
30
  // class that shipped in R88 (two hard-requires in src/bongos/serve-internal.js; task 1824). The
@@ -81,9 +81,9 @@ function checkCoreRequiresExcludedConfig() {
81
81
  // require()s a boundary-EXCLUDED module (modules/game, modules/art-pipeline,
82
82
  // modules/character-anim, …) throws MODULE_NOT_FOUND once that module lives in the
83
83
  // consumer's instance repo rather than the core package (R89 split). The cure is
84
- // either to reclassify the module INTO core (if it belongs there — e.g. provisioning,
85
- // sibling of dev-box) or to exclude the requiring file with the feature it serves
86
- // (art tooling). Catches BOTH require forms: a direct literal
84
+ // either to reclassify the module INTO core (if it belongs there — e.g. provisioning)
85
+ // or to exclude the requiring file with the feature it serves (art tooling).
86
+ // Catches BOTH require forms: a direct literal
87
87
  // require('../modules/<key>/…') AND a require(path.join(<root>, 'modules/<key>/…')).
88
88
  // Only an EXCLUDED module required from a PUBLISHABLE file is a violation; requiring a
89
89
  // publishable (core) module is fine, and the loader's DYNAMIC dispatch
@@ -46,6 +46,10 @@ const WRITE_VALIDATION_EXEMPT = new Set([
46
46
  // the route's own express.raw parser and read by docx.readUpload, which refuses
47
47
  // anything that is not a bounded, readable Word file (not_a_docx / upload_too_large).
48
48
  'POST /copy-desk/pages/:pageId/draft.docx',
49
+ // Not a JSON body either: the raw module tarball (task 1004271, ADR 0338 D1), capped by
50
+ // the route's express.raw parser and read by module-artifact.verifyModuleArtifact, which
51
+ // recomputes every hash and refuses anything that is not a well-formed module version.
52
+ 'POST /store/modules/:key/versions',
49
53
  ]);
50
54
 
51
55
  // KNOWN GAPS, NOT EXEMPTIONS (task 1002551 / BV1.R109b → tracked by task 1002552).
@@ -284,9 +284,9 @@ function knipIssueCount() {
284
284
  // baseline map leaves every `floor === undefined`, and compareToBaselines classifies
285
285
  // an absent floor as a WARNING — so a checkout with no config/ reported the ratchet
286
286
  // as PASSED. Measured: with the baselines gone, a planted 5,000-line and 94-cycle
287
- // regression produced 0 violations. That is not a hypothetical checkout — `config/`
288
- // is absent from every dev box's sparse slice, so the builders who most needed the
289
- // gate were the ones silently exempt from it, learning from red CI instead.
287
+ // regression produced 0 violations. That was not a hypothetical checkout — a sparse
288
+ // checkout without `config/` once left the builders who most needed the gate
289
+ // silently exempt from it, learning from red CI instead.
290
290
  //
291
291
  // Distinguish the two absences, because they are not the same fact:
292
292
  // - the FILE is missing/malformed -> cannot verify anything -> hard fail (here);
@@ -315,7 +315,7 @@ function readBaselines(baselinePath = BASELINE_PATH) {
315
315
  metrics: {},
316
316
  error: missing
317
317
  ? `config/fitness-baselines.json is MISSING — the quality ratchet cannot verify anything, so it fails CLOSED rather than reporting a pass it did not check. `
318
- + `If this is a dev box, config/ was historically outside the sparse checkout (task 1003227); re-sync the box (\`node scripts/gds/box-sync.js\`). `
318
+ + `If this is a sparse or partial checkout, take the whole tree (\`git sparse-checkout disable\`). `
319
319
  + `To bootstrap a fresh baseline deliberately: node scripts/gds/fitness-ratchets.js --update-baselines`
320
320
  : `config/fitness-baselines.json is unreadable (${e && e.message}) — failing CLOSED.`,
321
321
  };
@@ -350,7 +350,7 @@ const METRIC_HINTS = {
350
350
  legacy_api_gds_callers:
351
351
  ' — a NEW in-repo caller was written against the permanent `/api/gds` alias (task 1004064, '
352
352
  + 'idea 1001030). Ask for `/api/bongos` instead: the alias exists for callers we CANNOT update '
353
- + '(shipped Dev Box binaries, live dev boxes), not for code in this repo. Run '
353
+ + '(shipped binaries, live instances), not for code in this repo. Run '
354
354
  + '`node scripts/gds/api-alias-caller-scan.js --files` to see every counted line. The alias '
355
355
  + 'contract, the pinned OAuth callbacks, the uptime poller + smoke, the rename guardrails, '
356
356
  + 'tests/, docs/, migrations/ and captured fixtures are excluded by construction — and so is '
@@ -343,11 +343,11 @@ function checkDeterministicRoutines() {
343
343
  // published doorway); the core NEVER imports a module; a module NEVER imports a
344
344
  // sibling module. These two checks enforce it.
345
345
  //
346
- // Both were GREEN through the strangler: dev-box + art-pipeline (the M07/M08
347
- // moves) import only the doorway, and the core imports neither. The boundary
346
+ // Both were GREEN through the strangler: the first two moved modules (M07/M08)
347
+ // imported only the doorway, and the core imported neither. The boundary
348
348
  // shipped in WARN mode so a half-moved module mid-migration couldn't red the
349
349
  // build; BV1.R45 (task 1414) flips it to ENFORCING now that the first move
350
- // (dev-box, M07) proved clean — the monotonic ratchet (ADR 0062 §9 / design
350
+ // (M07) proved clean — the monotonic ratchet (ADR 0062 §9 / design
351
351
  // 04§3). A NEW cross-boundary import is now a red build; warn-mode is gone.
352
352
  let MODULE_BOUNDARY_ENFORCING = true;
353
353
  const MODULE_API_REL = 'src/module-api.js';
@@ -497,7 +497,7 @@ function checkScopeMapImportsProtected() {
497
497
  // module-AGNOSTIC: it discovers, validates, and mounts modules uniformly and may
498
498
  // never special-case one by name. The import checks (Check 7/8) stop a module's
499
499
  // CODE from being reached by `require`; this stops a module's IDENTITY from being
500
- // hardcoded into the kernel (`if (key === 'dev-box') …`, a copied module helper,
500
+ // hardcoded into the kernel (`if (key === 'art-pipeline') …`, a copied module helper,
501
501
  // a registry entry that should have moved out). "No module-specific code" is
502
502
  // operationalized as the one drift-free signal — the kernel names no discovered
503
503
  // module KEY (the canonical id from modules/<key>/module.json). Domain terms are
@@ -606,14 +606,14 @@ const KERNEL_FILES = [
606
606
  // Kernel files exempt from the no-module-keys scan (Check 10) because naming a
607
607
  // module is, for them, the JOB — they stay in KERNEL_FILES for the import check
608
608
  // (Check 12), which guards dependency *direction* (a much stronger guarantee than
609
- // "never write the string 'dev-box'"). Two legitimate reasons a kernel file names
609
+ // "never write the string 'art-pipeline'"). Two legitimate reasons a kernel file names
610
610
  // a module:
611
611
  // • the REGISTRY — module-scope-map.js IS the module→path map (and
612
612
  // checkScopeMapImportsProtected already guards how it derives `protected`).
613
613
  // • the TRUST BOUNDARY — auth.js carries the Discord OAuth account-LINK flow
614
614
  // (core by design — see modules/discord/CLAUDE.md "NOT in this module"); and
615
615
  // permission-path-check.js / route-rank-check.js MUST name module path
616
- // subtrees ('modules/dev-box/', …) to protect + rank-gate them. Forbidding
616
+ // subtrees ('modules/grading/', …) to protect + rank-gate them. Forbidding
617
617
  // these the string would force the trust layer to stop protecting module
618
618
  // surfaces — exactly backwards. The generic machinery + data + config files
619
619
  // (module-api, the loader, db-kernel, secret-box, pool, _helpers, branding,
@@ -683,8 +683,8 @@ function moduleDir(key) {
683
683
  return path.join(CORE_ROOT, 'modules', key);
684
684
  }
685
685
 
686
- // Match a module key as a DELIMITED token so `dev-box` hits in `'dev-box'` /
687
- // `mount('dev-box')` but never inside `dev-boxes` or `xdev-box`. Hyphens are
686
+ // Match a module key as a DELIMITED token so `art-pipeline` hits in `'art-pipeline'` /
687
+ // `mount('art-pipeline')` but never inside `art-pipelines` or `xart-pipeline`. Hyphens are
688
688
  // non-word chars, so the boundary guards against word-chars AND hyphens on both
689
689
  // sides (no lookbehind — portable on every node the unit gate runs).
690
690
  function keyTokenRe(key) {
@@ -816,7 +816,7 @@ function checkKernelImportsNoDomain() {
816
816
  // migrations," never "drop its tables" (ADR 0083 §Module-owned migrations);
817
817
  // there is no down path, so a DROP/TRUNCATE can only be an accident.
818
818
  // • NAMESPACED — every object it CREATEs lives under the <key>_ prefix
819
- // (dev-box → dev_box_), and it may only ALTER its own namespaced tables;
819
+ // (art-pipeline → art_pipeline_), and it may only ALTER its own namespaced tables;
820
820
  // core/sibling state is reached through the doorway/seams, never raw DDL.
821
821
  //
822
822
  // LEGACY_NAMESPACED: tables that predate the <key>_-prefix convention (created before the module
@@ -848,7 +848,7 @@ const CREATE_OBJ_RE = /\bcreate\s+(?:or\s+replace\s+)?(?:unique\s+)?(?:materiali
848
848
  const CREATE_INDEX_RE = /\bcreate\s+(?:unique\s+)?index\s+(?:concurrently\s+)?(?:if\s+not\s+exists\s+)?(?:"?[\w.]+"?)\s+on\s+(?:only\s+)?("?[\w.]+"?)/gi;
849
849
  const ALTER_TABLE_RE = /\balter\s+table\s+(?:if\s+exists\s+)?(?:only\s+)?("?[\w.]+"?)/gi;
850
850
 
851
- // SQL object-namespace prefix for a module key: dev-box → dev_box (SQL-safe).
851
+ // SQL object-namespace prefix for a module key: art-pipeline → art_pipeline (SQL-safe).
852
852
  function moduleNamespace(key) { return key.replace(/-/g, '_'); }
853
853
 
854
854
  // strip SQL comments so a commented-out statement is never scanned
@@ -208,8 +208,8 @@ function stripEsmExports(esm) {
208
208
  .replace(/^export default createClient;\n?/m, '');
209
209
  }
210
210
 
211
- // index.cjs — for Node consumers that use require() (e.g. the devbox Electron
212
- // app, which is a zero-dep CJS subproject and vendors this file at package time).
211
+ // index.cjs — for Node consumers that use require() (e.g. a zero-dep CJS subproject
212
+ // that vendors this file at package time).
213
213
  function renderClientCjs(esm) {
214
214
  const header =
215
215
  '// @bongos/client (CommonJS build) — GENERATED from index.mjs by\n' +
@@ -394,8 +394,8 @@ function renderPackageJson(spec) {
394
394
  // updated while the API it is generated FROM moved 500+ core versions. It had zero consumers
395
395
  // the whole time: nothing across the core or any instance repo ever declared it as a
396
396
  // dependency — every consumer VENDORS a copy instead. That was two places when this was
397
- // written; task 1003891 deleted the Dev Box Electron app and ADR 0324 retired
398
- // @cloudbongos/cli, so there is now no vendoring consumer left at all. The
397
+ // written; both have since been deleted or retired (ADR 0324 retired
398
+ // @cloudbongos/cli), so there is now no vendoring consumer left at all. The
399
399
  // owner removed it from the registry on 2026-09-09.
400
400
  //
401
401
  // The manifest itself stays: `main`/`types`/`exports` are what make the vendored copy and a
@@ -837,7 +837,7 @@ function buildOpenapi(model) {
837
837
  license: { name: 'AGPL-3.0-or-later', url: 'https://www.gnu.org/licenses/agpl-3.0.html' },
838
838
  description: [
839
839
  `The Bongos API — the build-methodology backend`,
840
- `(builders, tasks, claims, credits, grading, memory, dev boxes).`,
840
+ `(builders, tasks, claims, credits, grading, memory).`,
841
841
  ``,
842
842
  `**API version:** \`${API_VERSION}\` — served at \`${VERSIONED_API_PREFIX}\`. The`,
843
843
  `unversioned \`${API_PREFIX}\` and legacy \`/api/gds\` are permanent aliases`,