@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
@@ -4,19 +4,19 @@
4
4
  // scripts/gds/push-path-brief.js — say out loud, at session start, that this machine
5
5
  // cannot push (task 1003259 / idea 1000838).
6
6
  //
7
- // THE GAP. On 2026-08-24 a dev-box agent hit auth failures on every raw git probe,
8
- // concluded "no push credential exists", and asked the owner to mint a fine-grained
9
- // PAT and store it in ~/.git-credentials on the box — the exact anti-pattern the
10
- // server-mediated publish path (ADR 0055) exists to avoid. The agent was not being
11
- // careless: from inside a box every direct GitHub probe fails BY DESIGN (the clone
12
- // token is one-shot and scrubbed from .git/config after each fetch — ADR 0031 §6),
7
+ // THE GAP. On 2026-08-24 an agent on a credential-less checkout hit auth failures on
8
+ // every raw git probe, concluded "no push credential exists", and asked the owner to
9
+ // mint a fine-grained PAT and store it in ~/.git-credentials — the exact anti-pattern
10
+ // the server-mediated publish path (ADR 0055) exists to avoid. The agent was not being
11
+ // careless: on that checkout every direct GitHub probe failed BY DESIGN (the
12
+ // clone token was one-shot and scrubbed from .git/config after each fetch — ADR 0031 §6),
13
13
  // so "missing credential" is the only story the local evidence supports, and the true
14
14
  // state at the time (branch already pushed, PR open, CI red) was invisible to it.
15
- // Nothing the box session loads says the model out loud. This says it out loud.
15
+ // Nothing the session loads says the model out loud. This says it out loud.
16
16
  //
17
17
  // SO: the third SessionStart truth-teller, beside deploy-divergence.js and
18
18
  // strand-watch.js. On the server-publish path (pushVia() 'server' — no authenticated
19
- // gh, i.e. a dev box or any credential-less checkout) it prints ONE line stating the
19
+ // gh, i.e. any credential-less checkout) it prints ONE line stating the
20
20
  // credential model and where the truth about published work lives
21
21
  // (GET /api/bongos/tasks/:id/publish-status). On a laptop that pushes locally
22
22
  // (pushVia() 'local') it prints NOTHING — a line that fires on every healthy laptop
@@ -58,7 +58,7 @@ function briefLine(via) {
58
58
 
59
59
  // Lazy require so a broken or partial checkout degrades to silence, not a stack
60
60
  // trace injected into the session. pushVia() honors <PREFIX>_PUSH_VIA_SERVER, else
61
- // probes for an authenticated gh (absent on every box → 'server' instantly).
61
+ // probes for an authenticated gh (absent on a credential-less checkout → 'server' instantly).
62
62
  function detectVia() {
63
63
  return require('./ship-deploy-target.js').pushVia();
64
64
  }
@@ -70,7 +70,7 @@ function report(via) {
70
70
  console.log(' Publish path');
71
71
  console.log(' ──────────────────────────────────────────────────────────');
72
72
  if (via === 'server') {
73
- console.log(' server-mediated (no authenticated gh — a dev box or credential-less checkout).');
73
+ console.log(' server-mediated (no authenticated gh — a credential-less checkout).');
74
74
  console.log(` ${BRIEF}`);
75
75
  for (const line of plainCardLines('no_push_credential')) console.log(line);
76
76
  } else if (via === 'local') {
@@ -3,7 +3,7 @@
3
3
  // regen-instance-docs.js — regenerate a STANDALONE instance's live navigation docs (task 1002314).
4
4
  //
5
5
  // THE GAP. On the monorepo, the gitignored whole-file navigation docs are regenerated at deploy by
6
- // scripts/deploy/deploy-prod.sh + infra/box-source-fetch.sh (ADR 0110 §2/§3). STANDALONE instances
6
+ // scripts/deploy/deploy-prod.sh (ADR 0110 §2/§3). STANDALONE instances
7
7
  // (ADR 0108 — their own lean repo with @bongos/core vendored) never got that step: their bespoke
8
8
  // pull-deploy regenerated NOTHING, so a fresh instance shipped with an empty docs/ and nothing kept
9
9
  // the session-log index live. This script is the STANDALONE analogue of deploy-prod.sh's regen block
@@ -45,7 +45,7 @@
45
45
  // notices if a blanket find-replace rewrites them. This does.
46
46
  //
47
47
  // 4. ALIAS MOUNTS — '/api/gds' is a PERMANENT alias (src/bongos/api-prefix.js
48
- // says never to remove it: shipped Dev Box binaries and live dev boxes
48
+ // says never to remove it: shipped binaries and live instances
49
49
  // call it and cannot be force-updated). Asserted against the real exported
50
50
  // mount list. The DISPATCH half — that the alias is a live mount and so
51
51
  // gets no corrective 404 hint — is already covered by
@@ -93,7 +93,7 @@ const INTEGRATION = new Set([
93
93
  // DB-free route test can only assert the shape of.
94
94
  'community_leaderboard_db',
95
95
  'asset_version', 'auth_flow_slice1', 'backfill_sessions', 'bfg', 'bfg_write',
96
- 'box_connect_e2e', 'box_onboard', 'boxes', 'cf_tunnel', 'cli_token_reissue',
96
+ 'cli_token_reissue',
97
97
  // consumer_layout_boot builds the core .tgz, `npm install`s it, and boots a live
98
98
  // platform-server (task 2142), then installs the package from its own shipped lockfile,
99
99
  // migrates brand-new EMPTY databases and serves a database-backed route from one (task
@@ -398,20 +398,6 @@ function discoverModuleTests({ modulesDir = MODULES_DIR, isEnabled = isModuleEna
398
398
  // above, the real repair is for the test to establish the state it needs instead of
399
399
  // inheriting whatever ran before it.
400
400
  //
401
- // THE THREE DEV-BOX TESTS ARE PARKED, NOT DELETED, and that reverses a first pass
402
- // of this task. The owner ruled that dev-box work can go, and boxes / box_onboard /
403
- // box_connect_e2e were duly deleted — but modules/dev-box/ is still present, still
404
- // mounted and unmodified by that change, and those files carry the only regression
405
- // coverage for LIVE guards: the setBoxState column allowlist (SQL injection), the
406
- // rank/authz floors, the spoofed X-Forwarded-Host that must never reach the served
407
- // connect.sh, and the authorized_keys command= / embedded-newline rejections.
408
- // Deleting a security regression test for code that still runs is not retirement,
409
- // it is removing the alarm and leaving the door. modules/dev-box/CLAUDE.md also
410
- // still names boxes + box_onboard as that module's required suite, and
411
- // scripts/gds/smoke-box.sh still invokes all three by path — deleting the files
412
- // turned that script into an immediate MODULE_NOT_FOUND. They come out when the
413
- // module does, in the change that removes it.
414
- //
415
401
  // consumer_layout_boot LEFT THIS LIST under task 1002863. Its "missing core tarball" was
416
402
  // package-core's fail-closed leak gate reading the lane's throwaway database password (the
417
403
  // word postgres, present in hundreds of core files) as a leaked credential and refusing to
@@ -421,7 +407,7 @@ function discoverModuleTests({ modulesDir = MODULES_DIR, isEnabled = isModuleEna
421
407
  // now says out loud.
422
408
  const INTEGRATION_PARKED = new Set([
423
409
  'acclaim_feed_evidence_db',
424
- 'bfg', 'bfg_write', 'box_connect_e2e', 'box_onboard', 'boxes',
410
+ 'bfg', 'bfg_write',
425
411
  'claim_gate_open_blocker', 'claim_gate_rebase',
426
412
  'clawback_confirmed_backlog', 'copy_desk_flags_db',
427
413
  'government_board_membership_db', 'handle_machinery', 'hub_profile_leak',
@@ -4,21 +4,17 @@
4
4
  // REVIEWED in a browser before /builder-ship pushes it toward the main game.
5
5
  // (#927, ADR 0046 — the sandbox-first review step.)
6
6
  //
7
- // Two preview contexts (resolvePreviewContext picks one):
8
- // - BOX (ADR 0031): box-game-preview.service serves the working tree at
9
- // https://sandbox-<login>.example.com; "staging" restarts the service.
10
- // - LOCAL (task 1056): a builder on a full local clone (laptop) runs the same
11
- // ADR 0052 game-only entry privately at http://localhost:<port> via
12
- // scripts/gds/local-preview.js; "staging" (re)starts that local process.
13
- // Either way staging = (re)start the preview to pick up the current working
14
- // tree, health-check it, record a marker of exactly WHAT tree state was staged,
15
- // and hand back the URL. ship.js consults the marker through runShipGate(): if
16
- // the tree changed since the last staging, the builder is asked to stage +
17
- // review again before the claim resolves. On approval (a TTY 'y' or an
18
- // --approved re-run on a fresh stage) the gate returns a durable self-approval
19
- // record that ship.js persists on the task (#1048) — box AND local alike.
7
+ // The preview is LOCAL (task 1056): a builder on a full clone runs the ADR 0052
8
+ // game-only entry privately at http://localhost:<port> via
9
+ // scripts/gds/local-preview.js, and "staging" (re)starts that process to pick up
10
+ // the current working tree, health-checks it, records a marker of exactly WHAT
11
+ // tree state was staged, and hands back the URL. ship.js consults the marker
12
+ // through runShipGate(): if the tree changed since the last staging, the builder
13
+ // is asked to stage + review again before the claim resolves. On approval (a TTY
14
+ // 'y' or an --approved re-run on a fresh stage) the gate returns a durable
15
+ // self-approval record that ship.js persists on the task (#1048).
20
16
  //
21
- // Usage (a partial clone with no game has no sandbox; the script says so):
17
+ // Usage (a checkout with no game has no sandbox; the script says so):
22
18
  // node scripts/gds/sandbox-stage.js # stage + print the review URL
23
19
  // node scripts/gds/sandbox-stage.js status # preview state + is the marker current?
24
20
  // node scripts/gds/sandbox-stage.js url # just print the sandbox URL
@@ -31,13 +27,10 @@ const path = require('node:path');
31
27
  const readline = require('node:readline');
32
28
  const localPreview = require('./local-preview-lib');
33
29
 
34
- // /etc/otb/box.env exists ONLY on a provisioned dev box (written by box.js
35
- // provision, #898) — the idiomatic "am I on a box?" marker in this codebase.
36
- const BOX_ENV_PATH = '/etc/otb/box.env';
37
- const PREVIEW_WRAPPER = '/usr/local/bin/box-preview';
38
- const PREVIEW_UNIT = 'box-game-preview.service';
30
+ // The default health URL waitHealthy probes when a caller supplies none; the
31
+ // local context always passes its own (localPreview.healthUrl).
39
32
  const HEALTH_URL = 'http://127.0.0.1:3000/';
40
- const HEALTH_TIMEOUT_MS = 90_000; // first start may run npm ci (ExecStartPre)
33
+ const HEALTH_TIMEOUT_MS = 90_000; // a cold first start can be slow
41
34
  const HEALTH_POLL_MS = 2_000;
42
35
 
43
36
  // Game surfaces the sandbox can actually show. The sandbox host serves the
@@ -47,17 +40,10 @@ const HEALTH_POLL_MS = 2_000;
47
40
  // The GAME CLIENT directories the sandbox renders. These MOVED in the module
48
41
  // carve (ADR 0083, BV1.R49 / task 1418): public/ -> modules/game/public/,
49
42
  // src/world/ -> modules/game/world/, src/rooms/ -> modules/game/rooms/. The
50
- // pre-carve spelling sat here matching NOTHING, and staging still reported
51
- // success because the two entrypoints below kept syncing — so a builder staged,
52
- // saw 'synced previewable surfaces', and got a sandbox serving whatever client
53
- // was already on /workspace (task 1002062; found when a new /studio page 404'd).
43
+ // pre-carve spelling sat here matching NOTHING for 69 days (task 1002062), so
44
+ // tests/sandbox_stage.mjs pins the list against the real tree.
54
45
  const PREVIEWABLE_DIRS = ['modules/game/public', 'modules/game/world', 'modules/game/rooms'];
55
- // The single directory the preview serves AT THE URL ROOT: modules/game/public/x
56
- // is served as /x. pickVerifyTarget maps between the two, so it must be exactly
57
- // one dir — hence its own constant rather than PREVIEWABLE_DIRS[0].
58
- const PREVIEWABLE_STATIC_DIR = 'modules/game/public';
59
- // The entrypoints that render the game. A sparse box (STARTER_SPARSE_PATHS) may
60
- // legitimately lack these, which is why a miss here is not on its own an error.
46
+ // The entrypoints that render the game.
61
47
  const PREVIEWABLE_ENTRYPOINTS = ['server.js', 'src/preview-server.js'];
62
48
  // DERIVED, all three of them. They used to be three hand-maintained copies of the
63
49
  // same list, and the carve updated none of them; deriving removes the drift class
@@ -66,40 +52,6 @@ const PREVIEWABLE_PREFIXES = PREVIEWABLE_DIRS.map((d) => `${d}/`);
66
52
  const PREVIEWABLE_FILES = new Set(PREVIEWABLE_ENTRYPOINTS);
67
53
  const PREVIEWABLE_PATHSPECS = [...PREVIEWABLE_DIRS, ...PREVIEWABLE_ENTRYPOINTS];
68
54
 
69
- // On a box, box-game-preview.service has WorkingDirectory=/workspace and serves
70
- // /workspace/public — the MAIN checkout. A Claude session, though, runs in a git
71
- // WORKTREE under /workspace/.claude/worktrees/<name>, so its edits never touch
72
- // /workspace and the sandbox re-served stale code (the #1068 "I don't feel them"
73
- // bug). We sync the previewable surfaces from the session's branch into this
74
- // served root before restarting. Kept as a constant so tests can point it at a
75
- // temp dir.
76
- const BOX_SERVED_ROOT = '/workspace';
77
-
78
- function isDevBox(deps = {}) {
79
- const f = deps.fs || fs;
80
- return f.existsSync(deps.boxEnvPath || BOX_ENV_PATH);
81
- }
82
-
83
- // Parse BOX_PREVIEW_HOSTNAME out of box.env text. Returns the hostname string
84
- // or null (a quick-tunnel box has no preview hostname — ADR 0044 §Consequences).
85
- function readPreviewHostname(envText) {
86
- const m = /^\s*(?:export\s+)?BOX_PREVIEW_HOSTNAME=(.*)$/m.exec(String(envText || ''));
87
- if (!m) return null;
88
- const v = m[1].trim().replace(/^["']|["']$/g, '').trim();
89
- return v || null;
90
- }
91
-
92
- function previewUrl(deps = {}) {
93
- const f = deps.fs || fs;
94
- const p = deps.boxEnvPath || BOX_ENV_PATH;
95
- try {
96
- const host = readPreviewHostname(f.readFileSync(p, 'utf8'));
97
- return host ? `https://${host}` : null;
98
- } catch {
99
- return null;
100
- }
101
- }
102
-
103
55
  function isPreviewableFile(file) {
104
56
  const fp = String(file || '').replace(/\\/g, '/');
105
57
  if (PREVIEWABLE_FILES.has(fp)) return true;
@@ -188,34 +140,17 @@ function writeMarker(marker, deps = {}) {
188
140
  // review URL. The agent relays the URL, the human reviews, and
189
141
  // the re-run passes on the now-fresh marker. Never hangs on
190
142
  // stdin; never silently skips the review.
191
- // gateActive = there IS a sandbox here to review on (a dev box OR a local game
192
- // preview). The decision is identical for both; only HOW we stage + the URL differ.
143
+ // gateActive = there IS a sandbox here to review on (a local game preview).
193
144
  function decideShipGate({ gateActive, previewUrl: url, previewable, stagedFresh, tty, skipStage }) {
194
145
  if (skipStage) return { action: 'skip', reason: '--skip-stage' };
195
- if (!gateActive) return { action: 'skip', reason: 'no sandbox here (not a dev box; no local game preview)' };
146
+ if (!gateActive) return { action: 'skip', reason: 'no sandbox here (no local game preview)' };
196
147
  if (!previewable) return { action: 'skip', reason: 'no previewable game surfaces in the diff' };
197
- if (!url) return { action: 'skip', reason: 'preview has no URL (quick-tunnel box / local preview unavailable)' };
148
+ if (!url) return { action: 'skip', reason: 'preview has no URL (local preview unavailable)' };
198
149
  if (stagedFresh) return { action: 'pass', reason: 'working tree already staged' };
199
150
  return tty ? { action: 'review', reason: 'stage + review before ship' }
200
151
  : { action: 'stage_abort', reason: 'non-interactive: stage, then pause for review' };
201
152
  }
202
153
 
203
- function restartPreview(deps = {}) {
204
- const run = deps.execFileSync || execFileSync;
205
- const f = deps.fs || fs;
206
- // The baked wrapper is the expected path on every box (cloud-init +
207
- // box-source-fetch self-heal bake it to /usr/local/bin, and it handles its
208
- // own sudo). The raw systemctl branch is only a degraded fallback.
209
- if (f.existsSync(deps.previewWrapper || PREVIEW_WRAPPER)) {
210
- run(deps.previewWrapper || PREVIEW_WRAPPER, ['restart'], { stdio: 'inherit' });
211
- return;
212
- }
213
- const needSudo = typeof process.getuid === 'function' && process.getuid() !== 0;
214
- const cmd = needSudo ? 'sudo' : 'systemctl';
215
- const argv = needSudo ? ['-n', 'systemctl', 'restart', PREVIEW_UNIT] : ['restart', PREVIEW_UNIT];
216
- run(cmd, argv, { stdio: 'inherit' });
217
- }
218
-
219
154
  // task 1002607: the discriminator probe URL — same origin as the health URL, at
220
155
  // the API healthz path only the FULL server mounts. Pure + exported for tests.
221
156
  function apiHealthzProbeUrl(healthUrl) {
@@ -224,9 +159,9 @@ function apiHealthzProbeUrl(healthUrl) {
224
159
 
225
160
  // task 1002607: is the thing answering the health URL actually the MAIN server?
226
161
  // The game-only preview entry (ADR 0052) mounts no /api/bongos, so a 200 from the
227
- // API healthz proves the answerer is the full server — whose default bind is the
228
- // SAME 127.0.0.1:3000 the box health check probes — not the preview. A probe
229
- // error/404 means "not the main server", which is the expected preview answer.
162
+ // API healthz proves the answerer is the full server — which can hold the very
163
+ // port the preview was asked to bind — not the preview. A probe error/404 means
164
+ // "not the main server", which is the expected preview answer.
230
165
  async function answeredByMainServer(healthUrl, fetchImpl) {
231
166
  const probe = apiHealthzProbeUrl(healthUrl);
232
167
  if (!probe) return false;
@@ -268,187 +203,13 @@ async function waitHealthy(deps = {}) {
268
203
  return { ok: false, reason: lastErr };
269
204
  }
270
205
 
271
- // The git checkout the session is editing. On a box this is a linked worktree
272
- // under /workspace/.claude/worktrees/<name>; on a laptop it's the clone root.
273
- // Returns the toplevel path or null (not a repo).
274
- function sessionCheckoutRoot(deps = {}) {
275
- const run = deps.execFileSync || execFileSync;
276
- const cwd = deps.cwd || process.cwd();
277
- try {
278
- return run('git', ['rev-parse', '--show-toplevel'], { cwd, encoding: 'utf8' }).trim() || null;
279
- } catch {
280
- return null;
281
- }
282
- }
283
-
284
- // Box-only (#1068 gap 1): materialize the session worktree's previewable game
285
- // surfaces into the served checkout (/workspace) so the box preview shows the
286
- // SESSION's edits, not stale main. The served root and the worktree share one
287
- // object store (linked worktree), so `git -C /workspace checkout <branch> -- <paths>`
288
- // brings the branch's committed content for exactly the previewable pathspecs.
289
- // ship.js preflight already proved the worktree is clean (committed == working
290
- // tree), so committed content is what the builder means. No-op when the session
291
- // checkout IS the served root (a box where the builder edits /workspace directly,
292
- // or a laptop). Returns { ok, synced, reason?, branch? }.
293
- // Canonicalize a path through symlinks for a same-directory comparison. Needed
294
- // because `git rev-parse --show-toplevel` (sessionCheckoutRoot) returns the REAL
295
- // path, while servedRoot may be a symlink — on macOS `/tmp` -> `/private/var`
296
- // (idea #278), or any symlinked served root. path.resolve does NOT resolve
297
- // symlinks, so the raw comparison false-negatives "same dir" and the code would
298
- // wrongly try to git-sync a checkout onto itself. Falls back to path.resolve when
299
- // the path doesn't exist yet (realpathSync throws ENOENT).
300
- // On Windows the two sides arrive in different spellings of the same directory:
301
- // `git rev-parse --show-toplevel` answers with forward slashes and the LONG filename,
302
- // while a configured servedRoot may be backslashed and 8.3-short (C:\Users\RUNNER~1\…).
303
- // path.resolve folds the separators and realpathSync.native — unlike realpathSync —
304
- // expands 8.3, so the comparison is about directories rather than renderings of them.
305
- // Without both, the same-dir check false-negatived and the box git-synced a checkout
306
- // onto itself, the very thing this function exists to avoid (task 1003529).
307
- function realPathOr(p) {
308
- try { return fs.realpathSync.native(path.resolve(p)); } catch { return path.resolve(p); }
309
- }
310
-
311
- function syncWorktreeToServed(deps = {}) {
312
- const run = deps.execFileSync || execFileSync;
313
- const servedRoot = deps.servedRoot || BOX_SERVED_ROOT;
314
- const sessionRoot = sessionCheckoutRoot(deps);
315
- if (!sessionRoot || realPathOr(sessionRoot) === realPathOr(servedRoot)) {
316
- return { ok: true, synced: false, reason: 'session checkout is the served checkout' };
317
- }
318
- let branch;
319
- try {
320
- branch = run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: sessionRoot, encoding: 'utf8' }).trim();
321
- } catch {
322
- branch = null;
323
- }
324
- if (!branch || branch === 'HEAD') {
325
- return { ok: false, synced: false, reason: 'could not resolve the session branch (detached HEAD?)' };
326
- }
327
- // Check out each previewable pathspec INDEPENDENTLY: `git checkout <ref> -- a b c`
328
- // fails ENTIRELY if any single pathspec is absent from the ref, and a sparse
329
- // box (STARTER_SPARSE_PATHS) may legitimately lack server.js / src/preview-server.js.
330
- // Per-pathspec lets us sync what exists and skip what doesn't; verifyServedFresh
331
- // is the safety net that catches a sync that didn't actually land. First restore
332
- // each path from the served checkout's own HEAD (undo any prior ship's leftovers),
333
- // then overlay the session branch's content.
334
- let synced = false;
335
- let lastErr = null;
336
- // task 1002062: this collapsed to a single boolean, so a run that synced ONLY
337
- // the entrypoints and missed every client directory reported plain success.
338
- // Track them by name — the caller warns on a missed DIRECTORY, which is the
339
- // shape a stale pathspec list takes.
340
- const missed = [];
341
- for (const ps of PREVIEWABLE_PATHSPECS) {
342
- try {
343
- run('git', ['-C', servedRoot, 'checkout', '--', ps], { stdio: 'ignore' });
344
- } catch {
345
- /* pathspec absent in the served HEAD — nothing to restore, fine */
346
- }
347
- try {
348
- run('git', ['-C', servedRoot, 'checkout', branch, '--', ps], { stdio: 'ignore' });
349
- synced = true;
350
- } catch (e) {
351
- lastErr = e; // pathspec absent in the branch (sparse/missing) — skip it
352
- missed.push(ps);
353
- }
354
- }
355
- if (!synced) {
356
- return { ok: false, synced: false, missed, reason: `git checkout ${branch} -- <previewable> in ${servedRoot} synced nothing${lastErr ? ` (${lastErr.message})` : ''}` };
357
- }
358
- // A missed ENTRYPOINT is normal on a sparse box. A missed client DIRECTORY means
359
- // the sandbox is about to serve a client this stage never touched.
360
- const missedDirs = missed.filter((m) => PREVIEWABLE_DIRS.includes(m));
361
- return { ok: true, synced: true, branch, missed, missedDirs };
362
- }
363
-
364
- // Pick the smallest cheap-to-compare changed previewable static file to verify,
365
- // and the URL path the preview serves it at (public/game/x.js -> /game/x.js).
366
- // Returns { relPath, urlPath } or null when there's no served static change.
367
- function pickVerifyTarget(deps = {}) {
368
- const run = deps.execFileSync || execFileSync;
369
- const cwd = deps.cwd || process.cwd();
370
- const f = deps.fs || fs;
371
- try {
372
- // task 1002062: this hardcoded 'public' three times, independently of the
373
- // constants above, so it kept the pre-carve spelling even after they moved.
374
- // Derived from PREVIEWABLE_STATIC_DIR now, so there is one place to change.
375
- const staticDir = deps.staticDir || PREVIEWABLE_STATIC_DIR;
376
- const prefix = `${staticDir}/`;
377
- const out = run('git', ['status', '--porcelain', '--', staticDir], { cwd, encoding: 'utf8' });
378
- let chosen = out
379
- .split('\n')
380
- .map((line) => line.slice(3).replace(/^"|"$/g, '').trim())
381
- .find((rel) => rel && rel.startsWith(prefix) && f.existsSync(path.join(cwd, rel)));
382
- if (!chosen) {
383
- // committed-only change: fall back to the diff vs the frozen origin/main
384
- const diff = run('git', ['diff', '--name-only', 'origin/main...HEAD', '--', staticDir], { cwd, encoding: 'utf8' });
385
- chosen = diff
386
- .split('\n')
387
- .map((s) => s.trim())
388
- .find((p) => p.startsWith(prefix) && f.existsSync(path.join(cwd, p)));
389
- }
390
- if (!chosen) return null;
391
- return { relPath: chosen, urlPath: '/' + chosen.slice(prefix.length) };
392
- } catch {
393
- return null;
394
- }
395
- }
396
-
397
- // Box-only (#1068 gap 1b): after staging, fetch one changed static file from the
398
- // RUNNING preview over loopback (bypasses Cloudflare + browser cache, so it
399
- // proves the SERVER is fresh) and confirm it byte-matches the worktree. Returns
400
- // { ok, verified?|skipped?, reason? }. A mismatch means the sandbox is serving
401
- // stale code despite the restart — fail loudly.
402
- async function verifyServedFresh(deps = {}) {
403
- const fetchImpl = deps.fetchImpl || fetch;
404
- const f = deps.fs || fs;
405
- const cwd = deps.cwd || process.cwd();
406
- const base = (deps.healthUrl || HEALTH_URL).replace(/\/$/, '');
407
- const target = pickVerifyTarget(deps);
408
- if (!target) return { ok: true, skipped: true, reason: `no served static change to verify (e.g. only ${PREVIEWABLE_DIRS.filter((d) => d !== PREVIEWABLE_STATIC_DIR).join(' or ')} changed)` };
409
- let want;
410
- try {
411
- want = f.readFileSync(path.join(cwd, target.relPath), 'utf8');
412
- } catch {
413
- return { ok: true, skipped: true, reason: 'could not read the worktree file to compare' };
414
- }
415
- try {
416
- const res = await fetchImpl(`${base}${target.urlPath}`, {
417
- headers: { 'Cache-Control': 'no-cache' },
418
- signal: AbortSignal.timeout(5_000),
419
- });
420
- if (res.status >= 400) return { ok: false, reason: `preview returned HTTP ${res.status} for ${target.urlPath}` };
421
- const got = await res.text();
422
- if (got === want) return { ok: true, verified: target.relPath };
423
- return { ok: false, reason: `served ${target.urlPath} does NOT match your worktree ${target.relPath} — the sandbox is serving stale code` };
424
- } catch (e) {
425
- return { ok: false, reason: `could not fetch ${target.urlPath} from the running preview to verify freshness: ${e.message}` };
426
- }
427
- }
428
-
429
- // Which sandbox does this machine have? A box (ADR 0044) is keyed by ANY box
430
- // marker (localPreview.onBox: box.env, the box-preview wrapper, or the
431
- // box-game-preview.service unit — broader than box.env alone so a box with a
432
- // lost/unreadable box.env is STILL routed to the box branch, never to localhost
433
- // — task 1443); a full LOCAL clone (task 1056) is keyed by the presence of the
434
- // game-only entry (src/preview-server.js). Returns a uniform context
435
- // { kind, url, healthUrl, restart(deps) } — or null when there is NO sandbox
436
- // here (a partial clone with no game). On a box the url comes from box.env's
437
- // BOX_PREVIEW_HOSTNAME; if that's missing the url is null and stage() returns a
438
- // clear "no sandbox hostname" message — but it is NEVER a localhost URL the
439
- // builder's browser can't reach. deps.previewContext short-circuits for
440
- // hermetic tests. The box branch is checked FIRST: a box also has the game
441
- // entry, so order is what keeps onBox from being shadowed by hasGameEntry.
206
+ // Which sandbox does this machine have? A full LOCAL clone (task 1056) is keyed by
207
+ // the presence of the game-only entry (src/preview-server.js). Returns a uniform
208
+ // context { kind, url, healthUrl, restart(deps) } — or null when there is NO
209
+ // sandbox here (a checkout with no game). deps.previewContext short-circuits for
210
+ // hermetic tests.
442
211
  function resolvePreviewContext(deps = {}) {
443
212
  if (deps.previewContext !== undefined) return deps.previewContext;
444
- if (localPreview.onBox(deps)) {
445
- return {
446
- kind: 'box',
447
- url: previewUrl(deps),
448
- healthUrl: HEALTH_URL,
449
- restart: (d) => restartPreview(d),
450
- };
451
- }
452
213
  if (localPreview.hasGameEntry(deps)) {
453
214
  return {
454
215
  kind: 'local',
@@ -466,37 +227,9 @@ function resolvePreviewContext(deps = {}) {
466
227
  // answer, write the staged marker. Returns { ok, url, kind, reason? }.
467
228
  async function stage({ taskId } = {}, deps = {}) {
468
229
  const ctx = resolvePreviewContext(deps);
469
- if (!ctx) return { ok: false, reason: 'no sandbox here (not a dev box, and this clone has no local game preview)' };
470
- if (!ctx.url) {
471
- return { ok: false, reason: ctx.kind === 'box'
472
- ? 'no sandbox hostname on this box — box.env has no BOX_PREVIEW_HOSTNAME (a quick-tunnel box, or box.env is missing/unreadable). Run `box-preview status` for your URL, or ask the operator to re-provision with BOX_NAMED_TUNNEL=1 (ADR 0044). NOT a localhost link — that is unreachable from your browser on a box.'
473
- : 'local preview URL unavailable' };
474
- }
475
- console.log(ctx.kind === 'local'
476
- ? 'Staging your working tree on your local sandbox (starting the game preview)...'
477
- : 'Staging your working tree on the sandbox (syncing your worktree, then restarting the game preview)...');
478
- // #1068 gap 1: a box serves /workspace, not the session worktree. Sync the
479
- // previewable surfaces from the branch into /workspace BEFORE the restart so
480
- // the preview picks up the SESSION's edits. hard:true → the ship gate aborts
481
- // (a stale sandbox review is worse than no review). Local already serves the
482
- // worktree, so this is box-only.
483
- if (ctx.kind === 'box') {
484
- const sync = (deps.syncWorktreeToServed || syncWorktreeToServed)(deps);
485
- if (!sync.ok) {
486
- return { ok: false, hard: true, reason: `could not sync your worktree to the served checkout (${sync.reason}); the sandbox would show stale ${deps.servedRoot || BOX_SERVED_ROOT} code` };
487
- }
488
- if (sync.synced) console.log(` synced previewable surfaces from ${sync.branch} into ${deps.servedRoot || BOX_SERVED_ROOT}`);
489
- // task 1002062 — the tell that went unsaid for 69 days. Staging "succeeded"
490
- // while every game-client directory matched nothing, so the sandbox served a
491
- // client the stage never touched and the only hint was a freshness-verify skip.
492
- if (sync.missedDirs && sync.missedDirs.length) {
493
- console.warn(
494
- ` ⚠ these previewable directories matched NOTHING in ${sync.branch}: ${sync.missedDirs.join(', ')}`
495
- + ' — the sandbox will serve whatever client is already on the served checkout.'
496
- + ' If this repo carved or renamed them, PREVIEWABLE_DIRS in sandbox-stage.js is stale.'
497
- );
498
- }
499
- }
230
+ if (!ctx) return { ok: false, reason: 'no sandbox here (this checkout has no local game preview)' };
231
+ if (!ctx.url) return { ok: false, reason: 'local preview URL unavailable' };
232
+ console.log('Staging your working tree on your local sandbox (starting the game preview)...');
500
233
  try {
501
234
  ctx.restart(deps);
502
235
  } catch (e) {
@@ -504,19 +237,7 @@ async function stage({ taskId } = {}, deps = {}) {
504
237
  }
505
238
  const health = await waitHealthy({ ...deps, healthUrl: ctx.healthUrl });
506
239
  if (!health.ok) {
507
- const where = ctx.kind === 'local' ? 'node scripts/gds/local-preview.js logs' : 'box-preview logs';
508
- return { ok: false, reason: `preview did not come up healthy (${health.reason}) — check '${where}'` };
509
- }
510
- // #1068 gap 1b: confirm the restarted box preview is actually serving the
511
- // synced bytes (loopback fetch, cache-bypassed). A mismatch is a hard fail —
512
- // claiming "staged" when the sandbox shows stale code is the bug we're killing.
513
- if (ctx.kind === 'box') {
514
- const v = await (deps.verifyServedFresh || verifyServedFresh)({ ...deps, healthUrl: ctx.healthUrl });
515
- if (!v.ok) {
516
- return { ok: false, hard: true, reason: `staged but FAILED freshness verification: ${v.reason}` };
517
- }
518
- if (v.verified) console.log(` verified the sandbox is serving your edits (${v.verified} matches)`);
519
- else if (v.skipped) console.log(` freshness verify skipped (${v.reason})`);
240
+ return { ok: false, reason: `preview did not come up healthy (${health.reason}) — check 'node scripts/gds/local-preview.js logs'` };
520
241
  }
521
242
  writeMarker({
522
243
  stamp: computeTreeStamp(deps),
@@ -544,8 +265,8 @@ function promptYesNo(question) {
544
265
  // to present the URL as its own callout in chat. Keep the URL ALONE on its own
545
266
  // line, framed by a rule, so it stands out from the surrounding log noise and
546
267
  // stays clickable. This is the moment the change is "ready to review", so the
547
- // link has to be impossible to miss. Works for a box (sandbox-<login>) or a
548
- // local clone (http://localhost:<port>) — the URL is passed in.
268
+ // link has to be impossible to miss. The URL (http://localhost:<port>) is
269
+ // passed in.
549
270
  function printReviewBlock(url, taskId) {
550
271
  const bar = '─'.repeat(64);
551
272
  console.log('');
@@ -563,8 +284,7 @@ function printReviewBlock(url, taskId) {
563
284
 
564
285
  // Build the recorded-approval payload ship.js threads into the resolve call
565
286
  // (#1048). The server stamps `by`/`at` authoritatively — we only supply the
566
- // content stamp of what was reviewed, the sandbox URL (box OR local), and HOW
567
- // it was approved.
287
+ // content stamp of what was reviewed, the sandbox URL, and HOW it was approved.
568
288
  function approvalRecord(method, url, deps) {
569
289
  return { stamp: computeTreeStamp(deps), url, method };
570
290
  }
@@ -573,8 +293,7 @@ function approvalRecord(method, url, deps) {
573
293
  // Returns { ok: true } to proceed or { ok: false, reason } to abort the ship
574
294
  // with the claim still active so the builder can iterate. On a recorded
575
295
  // approval it also returns { sandboxReview } so ship.js can persist the
576
- // self-approval as a durable fact on the task (#1048) — for a box or a local
577
- // clone (task 1056).
296
+ // self-approval as a durable fact on the task (#1048).
578
297
  //
579
298
  // `approved` (the --approved flag) records the builder's sign-off, but ONLY
580
299
  // when the current game state is already staged-fresh (decideShipGate → 'pass')
@@ -616,18 +335,7 @@ async function runShipGate({ taskId, files, skipStage, tty, approved }, deps = {
616
335
  // Reuse the already-resolved context so we don't resolve it twice.
617
336
  const staged = await stage({ taskId }, { ...deps, previewContext: ctx });
618
337
  if (!staged.ok) {
619
- // #1068: a HARD failure (couldn't sync the worktree to the served checkout,
620
- // or the preview verifiably served stale code) means a review here would be
621
- // a LIE — abort with the claim intact so the builder fixes it.
622
- if (staged.hard) {
623
- return {
624
- ok: false,
625
- review: true,
626
- url: staged.url,
627
- reason: `sandbox staging could not be trusted (${staged.reason}) — fix it, then re-run /builder-ship (or --skip-stage to bypass the review). Your claim is still active`,
628
- };
629
- }
630
- // task 1002607 (§4.4 station 9): a SOFT failure (the preview didn't come up)
338
+ // task 1002607 (§4.4 station 9): a staging failure (the preview didn't come up)
631
339
  // used to WARN one stderr line and return ok — the review gate silently
632
340
  // turned itself off and the ship went on as if the review had happened.
633
341
  // Fail closed like the smoke gate (task 1002605): abort with the claim
@@ -672,7 +380,7 @@ async function runShipGate({ taskId, files, skipStage, tty, approved }, deps = {
672
380
  // (the ADR 0052 game-only server); the core repo does not ship one. Name what is
673
381
  // actually missing and both real causes. Exported so the wording is pinned.
674
382
  const NO_SANDBOX_MSG =
675
- 'No sandbox here: this machine is not a dev box, and this checkout has no game preview entry (src/preview-server.js — the ADR 0052 game-only server).';
383
+ 'No sandbox here: this checkout has no game preview entry (src/preview-server.js — the ADR 0052 game-only server).';
676
384
  const NO_SANDBOX_DETAIL =
677
385
  'That entry file comes from an instance\'s product layer. This repo does not ship one, and a partial/sparse clone of an instance that has one also lacks it — nothing here can be staged or previewed.';
678
386
 
@@ -682,7 +390,7 @@ async function cliMain() {
682
390
  const NO_SANDBOX = NO_SANDBOX_MSG;
683
391
 
684
392
  if (cmd === 'url') {
685
- console.log(ctx && ctx.url ? ctx.url : '(no sandbox here — not a dev box, and no local game preview in this clone)');
393
+ console.log(ctx && ctx.url ? ctx.url : '(no sandbox here — no local game preview in this checkout)');
686
394
  return;
687
395
  }
688
396
  if (cmd === 'status') {
@@ -691,7 +399,7 @@ async function cliMain() {
691
399
  const stamp = computeTreeStamp();
692
400
  const fresh = Boolean(stamp && marker && marker.stamp === stamp);
693
401
  console.log(`sandbox kind: ${ctx.kind}`);
694
- console.log(`sandbox URL: ${ctx.url || '(none — quick-tunnel box)'}`);
402
+ console.log(`sandbox URL: ${ctx.url || '(none)'}`);
695
403
  if (ctx.kind === 'local') {
696
404
  console.log(`local preview: ${localPreview.isRunning() ? `running (pid ${localPreview.readPid()})` : 'stopped — staging will start it'}`);
697
405
  }
@@ -723,9 +431,6 @@ async function cliMain() {
723
431
  }
724
432
 
725
433
  module.exports = {
726
- isDevBox,
727
- readPreviewHostname,
728
- previewUrl,
729
434
  isPreviewableFile,
730
435
  anyPreviewable,
731
436
  computeTreeStamp,
@@ -737,16 +442,9 @@ module.exports = {
737
442
  resolvePreviewContext,
738
443
  stage,
739
444
  runShipGate,
740
- BOX_ENV_PATH,
741
- BOX_SERVED_ROOT,
742
445
  PREVIEWABLE_PATHSPECS,
743
446
  PREVIEWABLE_DIRS,
744
447
  PREVIEWABLE_ENTRYPOINTS,
745
- PREVIEWABLE_STATIC_DIR,
746
- sessionCheckoutRoot,
747
- syncWorktreeToServed,
748
- pickVerifyTarget,
749
- verifyServedFresh,
750
448
  // task 1002607: the health truth table + the honest no-sandbox wording, pinned.
751
449
  waitHealthy,
752
450
  apiHealthzProbeUrl,