@bongos/core 1.18.0 → 1.18.18

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 (553) hide show
  1. package/.bongos-core.json +1740 -640
  2. package/.claude/skills/builder-box/SKILL.md +6 -0
  3. package/.claude/skills/builder-sequence/SKILL.md +97 -0
  4. package/.claude/skills/builder-ship/SKILL.md +8 -2
  5. package/.claude/skills/builder-start/SKILL.md +11 -0
  6. package/.claude/skills/builder-sync/SKILL.md +41 -0
  7. package/.claude/skills/fix-task/SKILL.md +64 -0
  8. package/.claude/skills/goal-create/SKILL.md +158 -0
  9. package/.claude/skills/goal-review/SKILL.md +3 -1
  10. package/.claude/skills/idea-triage/SKILL.md +14 -8
  11. package/.claude/skills/merge-mode/SKILL.md +2 -2
  12. package/.claude/skills/new-project/SKILL.md +1 -0
  13. package/.claude/skills/planning-session/SKILL.md +45 -18
  14. package/.env.local.example +4 -3
  15. package/README.md +5 -3
  16. package/bin/bongos.js +1 -0
  17. package/clients/bongos-client/README.md +2 -2
  18. package/clients/bongos-client/bongos-client.global.js +82 -18
  19. package/clients/bongos-client/index.cjs +82 -18
  20. package/clients/bongos-client/index.d.ts +131 -29
  21. package/clients/bongos-client/index.mjs +82 -18
  22. package/config/branding.neutral.json +11 -2
  23. package/config/governance.neutral.json +21 -0
  24. package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +24 -0
  25. package/docs/adr/0090-metic-task-authoring.md +1 -1
  26. package/docs/adr/0142-project-context-is-prose-only.md +41 -0
  27. package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
  28. package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
  29. package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
  30. package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
  31. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
  32. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
  33. package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
  34. package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
  35. package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
  36. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
  37. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
  38. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
  39. package/docs/adr/0152-metic-task-abandonment.md +67 -0
  40. package/docs/adr/0153-sequential-chain-runner.md +51 -0
  41. package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
  42. package/docs/adr/README.md +15 -2
  43. package/docs/api/openapi.json +2648 -483
  44. package/docs/api-reference.md +73 -13
  45. package/docs/architecture.md +4 -2
  46. package/docs/branding-contract.md +3 -1
  47. package/docs/canonical-permissions.md +21 -8
  48. package/docs/file-map.md +15 -7
  49. package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
  50. package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
  51. package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
  52. package/docs/onboarding/diagrams/assertions.json +4 -7
  53. package/docs/onboarding/drachmae-and-karma.md +3 -3
  54. package/docs/onboarding/primer.md +17 -16
  55. package/docs/onboarding/slash-commands.md +124 -0
  56. package/docs/recipes/builder-box-lifecycle.md +23 -0
  57. package/docs/recipes/devbox-app-release.md +14 -4
  58. package/docs/recipes/instance-first-run.md +16 -0
  59. package/docs/recipes/local-dev.md +81 -0
  60. package/docs/recipes/ops-gotchas.md +19 -1
  61. package/docs/recipes/overnight-builder.md +1 -1
  62. package/docs/recipes/packaging-the-core.md +13 -0
  63. package/docs/recipes/parallel-goal-waves.md +174 -0
  64. package/docs/recipes/ship-and-regrade.md +2 -2
  65. package/docs/recipes/standalone-live-docs.md +48 -0
  66. package/docs/recipes/upgrading-the-core.md +5 -2
  67. package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
  68. package/migrations/003_pms.sql +6 -7
  69. package/migrations/006_pms_v2.sql +3 -31
  70. package/migrations/039_newcomer_friendly_column.sql +5 -0
  71. package/migrations/102_remove_box_first_provision_approval.sql +4 -0
  72. package/migrations/core_192_github_oauth_tokens.sql +33 -0
  73. package/migrations/core_193_task_needs_rebase.sql +28 -0
  74. package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
  75. package/migrations/core_195_idea_triage_provenance.sql +57 -0
  76. package/migrations/core_196_session_reward_topup.sql +69 -0
  77. package/modules/autonomy/pollers/routine-timers.js +9 -8
  78. package/modules/builder-settings/wandering-prefs.js +61 -18
  79. package/modules/dev-box/app/README.md +73 -0
  80. package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
  81. package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
  82. package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
  83. package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
  84. package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
  85. package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
  86. package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
  87. package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
  88. package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
  89. package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
  90. package/modules/dev-box/app/assets/tray/win-off.png +0 -0
  91. package/modules/dev-box/app/assets/tray/win-on.png +0 -0
  92. package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
  93. package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
  94. package/modules/dev-box/app/build-resources/icon.png +0 -0
  95. package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
  96. package/modules/dev-box/app/electron-builder.yml +95 -0
  97. package/modules/dev-box/app/package-lock.json +5291 -0
  98. package/modules/dev-box/app/package.json +30 -0
  99. package/modules/dev-box/app/renderer/app.js +319 -0
  100. package/modules/dev-box/app/renderer/index.html +45 -0
  101. package/modules/dev-box/app/renderer/styles.css +246 -0
  102. package/modules/dev-box/app/renderer/theme.generated.css +16 -0
  103. package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
  104. package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
  105. package/modules/dev-box/app/scripts/gen-brand.js +138 -0
  106. package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
  107. package/modules/dev-box/app/scripts/gen-icons.js +286 -0
  108. package/modules/dev-box/app/scripts/vendor-client.js +23 -0
  109. package/modules/dev-box/app/src/box-controller.js +287 -0
  110. package/modules/dev-box/app/src/brand.generated.json +13 -0
  111. package/modules/dev-box/app/src/brand.js +16 -0
  112. package/modules/dev-box/app/src/config.js +19 -0
  113. package/modules/dev-box/app/src/gds-client.js +90 -0
  114. package/modules/dev-box/app/src/main.js +610 -0
  115. package/modules/dev-box/app/src/pairing.js +57 -0
  116. package/modules/dev-box/app/src/preload.js +29 -0
  117. package/modules/dev-box/app/src/session-store.js +58 -0
  118. package/modules/dev-box/app/src/ssh-setup.js +569 -0
  119. package/modules/dev-box/app/src/task-picker.js +70 -0
  120. package/modules/dev-box/app/src/tray.js +99 -0
  121. package/modules/dev-box/app/src/updater.js +55 -0
  122. package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
  123. package/modules/dev-box/app/test/unit.mjs +526 -0
  124. package/modules/dev-box/box-access.js +121 -4
  125. package/modules/dev-box/boxes.js +20 -0
  126. package/modules/dev-box/module.json +1 -1
  127. package/modules/dev-box/routes/box.js +87 -3
  128. package/modules/economy/credits.js +116 -25
  129. package/modules/economy/reward.js +8 -0
  130. package/modules/economy/streak.js +2 -1
  131. package/modules/governance/CLAUDE.md +15 -0
  132. package/modules/governance/catalog.js +419 -0
  133. package/modules/governance/config.js +174 -0
  134. package/modules/governance/db.js +125 -0
  135. package/modules/governance/migrations/governance_001_tables.sql +81 -0
  136. package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
  137. package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
  138. package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
  139. package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
  140. package/modules/governance/module.json +10 -0
  141. package/modules/governance/protected-surfaces.js +88 -0
  142. package/modules/governance/protected-surfaces.json +80 -0
  143. package/modules/governance/rank-roles.js +83 -0
  144. package/modules/governance/resolver.js +221 -0
  145. package/modules/governance/routes/governance.js +89 -0
  146. package/modules/governance/routes/role-sync.js +49 -0
  147. package/modules/governance/session-scopes.js +127 -0
  148. package/modules/governance/session-scopes.json +38 -0
  149. package/modules/hall-ui/public/atlas.css +117 -0
  150. package/modules/hall-ui/public/atlas.html +3 -114
  151. package/modules/hall-ui/public/brand-apply.js +34 -6
  152. package/modules/hall-ui/public/builders.js +187 -219
  153. package/modules/hall-ui/public/diagrams.html +2 -0
  154. package/modules/hall-ui/public/dom-utils.js +27 -0
  155. package/modules/hall-ui/public/drachmae.html +2 -0
  156. package/modules/hall-ui/public/drachmae.js +54 -1
  157. package/modules/hall-ui/public/gate.css +98 -0
  158. package/modules/hall-ui/public/gate.html +3 -95
  159. package/modules/hall-ui/public/goal-inbox.js +4 -1
  160. package/modules/hall-ui/public/goals-page.js +626 -114
  161. package/modules/hall-ui/public/goals.css +79 -1
  162. package/modules/hall-ui/public/goals.html +38 -11
  163. package/modules/hall-ui/public/hall-widgets.js +62 -52
  164. package/modules/hall-ui/public/harbor.css +123 -0
  165. package/modules/hall-ui/public/harbor.html +3 -120
  166. package/modules/hall-ui/public/index.html +11 -4
  167. package/modules/hall-ui/public/instances-panel.js +4 -1
  168. package/modules/hall-ui/public/modules.css +63 -0
  169. package/modules/hall-ui/public/modules.html +3 -60
  170. package/modules/hall-ui/public/my-projects.html +78 -0
  171. package/modules/hall-ui/public/my-projects.js +142 -0
  172. package/modules/hall-ui/public/new-project-wizard.js +7 -3
  173. package/modules/hall-ui/public/not-ready.html +2 -0
  174. package/modules/hall-ui/public/pair.css +73 -0
  175. package/modules/hall-ui/public/pair.html +3 -70
  176. package/modules/hall-ui/public/palette.js +302 -0
  177. package/modules/hall-ui/public/primer.css +13 -0
  178. package/modules/hall-ui/public/primer.html +2 -0
  179. package/modules/hall-ui/public/profile.html +2 -0
  180. package/modules/hall-ui/public/ranks.html +2 -0
  181. package/modules/hall-ui/public/roadmap.css +179 -0
  182. package/modules/hall-ui/public/roadmap.html +9 -172
  183. package/modules/hall-ui/public/roadmap.js +329 -316
  184. package/modules/hall-ui/public/scouting.html +45 -0
  185. package/modules/hall-ui/public/scouting.js +73 -0
  186. package/modules/hall-ui/public/sessions.css +16 -0
  187. package/modules/hall-ui/public/sessions.html +3 -13
  188. package/modules/hall-ui/public/settings.html +2 -0
  189. package/modules/hall-ui/public/settings.js +4 -4
  190. package/modules/hall-ui/public/shell.js +143 -18
  191. package/modules/hall-ui/public/style.css +231 -39
  192. package/modules/hall-ui/public/task-detail.js +347 -26
  193. package/modules/hall-ui/public/task.html +6 -1
  194. package/modules/hall-ui/public/watch.html +2 -0
  195. package/modules/hall-ui/public/work.html +8 -6
  196. package/modules/hall-ui/public/work.js +1 -1
  197. package/modules/ideas/inbox.js +61 -11
  198. package/modules/ideas/routes/inbox.js +23 -0
  199. package/modules/lifecycle/claim-eligibility.js +72 -0
  200. package/modules/lifecycle/db.js +519 -139
  201. package/modules/lifecycle/done-when.js +18 -3
  202. package/modules/lifecycle/github-push.js +19 -2
  203. package/modules/lifecycle/goal-advisory.js +114 -0
  204. package/modules/lifecycle/goal-authz.js +62 -0
  205. package/modules/lifecycle/lifecycle.js +9 -0
  206. package/modules/lifecycle/publish-reconciler.js +109 -6
  207. package/modules/lifecycle/routes/claims.js +68 -23
  208. package/modules/lifecycle/routes/done-when.js +110 -15
  209. package/modules/lifecycle/routes/goals.js +8 -38
  210. package/modules/lifecycle/routes/tasks.js +38 -6
  211. package/modules/lifecycle/ship-card.js +6 -0
  212. package/modules/onboarding/onboarding-state.js +1 -1
  213. package/modules/platform-identity/CLAUDE.md +15 -0
  214. package/modules/platform-identity/hub-device.js +147 -0
  215. package/modules/platform-identity/hub-keys.js +151 -0
  216. package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
  217. package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
  218. package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
  219. package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
  220. package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
  221. package/modules/platform-identity/module.json +18 -0
  222. package/modules/platform-identity/platform-identity.js +531 -0
  223. package/modules/platform-identity/pollers/code-sweep.js +43 -0
  224. package/modules/platform-identity/routes/my-projects.js +140 -0
  225. package/modules/platform-identity/routes/projects.js +107 -0
  226. package/modules/platform-identity/routes/scouting.js +35 -0
  227. package/modules/platform-identity/routes/sso.js +402 -0
  228. package/modules/platform-identity/tests/platform-identity.mjs +388 -0
  229. package/modules/platform-identity/ui/.gitkeep +0 -0
  230. package/modules/security/routes/reports.js +10 -0
  231. package/modules/sessions/db.js +166 -21
  232. package/modules/sessions/module.json +1 -1
  233. package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
  234. package/modules/sessions/routes/sessions.js +33 -0
  235. package/modules/status-ui/public/explore.html +54 -0
  236. package/modules/status-ui/public/explore.js +67 -0
  237. package/package-lock.json +2 -2
  238. package/package.json +1 -1
  239. package/public-landing/assets/anim/idle/frame_00.webp +0 -0
  240. package/public-landing/assets/anim/idle/frame_01.webp +0 -0
  241. package/public-landing/assets/anim/idle/frame_02.webp +0 -0
  242. package/public-landing/assets/anim/idle/frame_03.webp +0 -0
  243. package/public-landing/assets/anim/idle/frame_04.webp +0 -0
  244. package/public-landing/assets/anim/idle/frame_05.webp +0 -0
  245. package/public-landing/assets/anim/idle/frame_06.webp +0 -0
  246. package/public-landing/assets/anim/idle/frame_07.webp +0 -0
  247. package/public-landing/assets/anim/idle/frame_08.webp +0 -0
  248. package/public-landing/assets/anim/idle/frame_09.webp +0 -0
  249. package/public-landing/assets/anim/idle/frame_10.webp +0 -0
  250. package/public-landing/assets/anim/idle/frame_11.webp +0 -0
  251. package/public-landing/assets/anim/idle/frame_12.webp +0 -0
  252. package/public-landing/assets/anim/idle/frame_13.webp +0 -0
  253. package/public-landing/assets/anim/idle/frame_14.webp +0 -0
  254. package/public-landing/assets/anim/idle/frame_15.webp +0 -0
  255. package/public-landing/assets/anim/idle/frame_16.webp +0 -0
  256. package/public-landing/assets/anim/idle/frame_17.webp +0 -0
  257. package/public-landing/assets/anim/idle/frame_18.webp +0 -0
  258. package/public-landing/assets/anim/idle/frame_19.webp +0 -0
  259. package/public-landing/assets/anim/idle/frame_20.webp +0 -0
  260. package/public-landing/assets/anim/idle/frame_21.webp +0 -0
  261. package/public-landing/assets/anim/idle/frame_22.webp +0 -0
  262. package/public-landing/assets/anim/idle/frame_23.webp +0 -0
  263. package/public-landing/assets/anim/idle/frame_24.webp +0 -0
  264. package/public-landing/assets/anim/idle/frame_25.webp +0 -0
  265. package/public-landing/assets/anim/idle/frame_26.webp +0 -0
  266. package/public-landing/assets/anim/idle/frame_27.webp +0 -0
  267. package/public-landing/assets/anim/idle/frame_28.webp +0 -0
  268. package/public-landing/assets/anim/idle/frame_29.webp +0 -0
  269. package/public-landing/assets/anim/idle/frame_30.webp +0 -0
  270. package/public-landing/assets/anim/idle/frame_31.webp +0 -0
  271. package/public-landing/assets/anim/idle/frame_32.webp +0 -0
  272. package/public-landing/assets/anim/idle/frame_33.webp +0 -0
  273. package/public-landing/assets/anim/idle/frame_34.webp +0 -0
  274. package/public-landing/assets/anim/idle/frame_35.webp +0 -0
  275. package/public-landing/assets/anim/idle/frame_36.webp +0 -0
  276. package/public-landing/assets/anim/idle/frame_37.webp +0 -0
  277. package/public-landing/assets/anim/idle/frame_38.webp +0 -0
  278. package/public-landing/assets/anim/idle/frame_39.webp +0 -0
  279. package/public-landing/assets/anim/idle/frame_40.webp +0 -0
  280. package/public-landing/assets/anim/idle/frame_41.webp +0 -0
  281. package/public-landing/assets/anim/idle/frame_42.webp +0 -0
  282. package/public-landing/assets/anim/idle/frame_43.webp +0 -0
  283. package/public-landing/create.html +252 -62
  284. package/public-landing/gate/README.md +137 -0
  285. package/public-landing/gate/dispmaps/bar.png +0 -0
  286. package/public-landing/gate/dispmaps/flat.png +0 -0
  287. package/public-landing/gate/dispmaps/key.png +0 -0
  288. package/public-landing/gate/index.html +587 -0
  289. package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
  290. package/public-landing/gate/previews/off-the-boats.jpg +0 -0
  291. package/public-landing/gate/space.css +799 -0
  292. package/public-landing/gate/space.js +1826 -0
  293. package/public-landing/gate/sprites/enter.png +0 -0
  294. package/public-landing/gate/sprites/search.png +0 -0
  295. package/public-landing/gate/sprites/terminal-blank.png +0 -0
  296. package/public-landing/gate/sprites/tile.png +0 -0
  297. package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
  298. package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
  299. package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
  300. package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
  301. package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
  302. package/public-landing/world/assets/plates/scene-establishing.png +0 -0
  303. package/scripts/gds/abandon.js +6 -4
  304. package/scripts/gds/api.js +10 -5
  305. package/scripts/gds/api.ps1 +26 -0
  306. package/scripts/gds/api.sh +6 -0
  307. package/scripts/gds/audit-ships.js +7 -1
  308. package/scripts/gds/backfill-sessions.js +78 -18
  309. package/scripts/gds/backup.js +23 -1
  310. package/scripts/gds/box-connect-lib.js +124 -31
  311. package/scripts/gds/box-sync.js +66 -0
  312. package/scripts/gds/box.js +225 -53
  313. package/scripts/gds/cf-tunnel.js +145 -6
  314. package/scripts/gds/claim.js +34 -3
  315. package/scripts/gds/cli-lib.js +161 -21
  316. package/scripts/gds/code.js +6 -2
  317. package/scripts/gds/deploy-divergence.js +209 -0
  318. package/scripts/gds/dev-lib.js +309 -0
  319. package/scripts/gds/dev.js +322 -0
  320. package/scripts/gds/doctor.js +90 -61
  321. package/scripts/gds/fetch-art-key.js +1 -1
  322. package/scripts/gds/fetch-sound-prefs.js +2 -2
  323. package/scripts/gds/fitness.js +228 -12
  324. package/scripts/gds/gds-db-backup.service +16 -1
  325. package/scripts/gds/gds-db-offsite.service +9 -1
  326. package/scripts/gds/gen-api-client.js +1 -1
  327. package/scripts/gds/gen-api-docs.js +36 -14
  328. package/scripts/gds/gen-diagrams.js +45 -28
  329. package/scripts/gds/gen-file-map.js +2 -1
  330. package/scripts/gds/gen-freshness.js +32 -0
  331. package/scripts/gds/gen-repo-map.js +3 -2
  332. package/scripts/gds/gen-session-index.js +14 -5
  333. package/scripts/gds/hooks/pre-push.js +2 -2
  334. package/scripts/gds/init.js +60 -3
  335. package/scripts/gds/install-git-hooks.js +1 -1
  336. package/scripts/gds/key-card.js +15 -6
  337. package/scripts/gds/key-status.js +1 -1
  338. package/scripts/gds/linkify-refs.js +35 -9
  339. package/scripts/gds/lint-migrations.js +102 -0
  340. package/scripts/gds/login.js +58 -11
  341. package/scripts/gds/mirror-redact.js +5 -0
  342. package/scripts/gds/package-core.js +263 -4
  343. package/scripts/gds/paste-token.js +7 -1
  344. package/scripts/gds/preflight.js +126 -0
  345. package/scripts/gds/provision.js +268 -19
  346. package/scripts/gds/publish-manifest.js +22 -1
  347. package/scripts/gds/reauth.js +7 -3
  348. package/scripts/gds/regen-instance-docs.js +47 -0
  349. package/scripts/gds/run-unit-tests.js +42 -16
  350. package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
  351. package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
  352. package/scripts/gds/sequence.js +425 -0
  353. package/scripts/gds/session-cost.js +10 -1
  354. package/scripts/gds/setup.js +72 -5
  355. package/scripts/gds/shell.js +5 -3
  356. package/scripts/gds/ship.js +433 -59
  357. package/scripts/gds/smoke-gds.js +139 -8
  358. package/scripts/gds/start.js +171 -22
  359. package/scripts/gds/status.js +1 -1
  360. package/scripts/gds/triage.js +10 -9
  361. package/scripts/gds/upgrade.js +271 -7
  362. package/scripts/migrate.sh +10 -0
  363. package/scripts/status-mirror-sync.js +114 -84
  364. package/src/bongos/api-errors.js +1 -0
  365. package/src/bongos/auth.js +809 -107
  366. package/src/bongos/db-kernel.js +27 -0
  367. package/src/bongos/db.js +105 -0
  368. package/src/bongos/github-repos.js +93 -0
  369. package/src/bongos/module-scope-map.js +27 -1
  370. package/src/bongos/permission-path-check.js +136 -102
  371. package/src/bongos/repo-info.js +47 -11
  372. package/src/bongos/route-rank-check.js +429 -46
  373. package/src/bongos/routes/_helpers.js +12 -1
  374. package/src/bongos/routes/auth.js +312 -13
  375. package/src/bongos/routes/backup.js +58 -2
  376. package/src/bongos/routes/github.js +90 -0
  377. package/src/bongos/routes/instance.js +15 -3
  378. package/src/bongos/routes/me.js +35 -1
  379. package/src/bongos/routes.js +2 -0
  380. package/src/bongos/serve-internal.js +84 -15
  381. package/src/branding.js +49 -2
  382. package/src/instance-config.js +46 -0
  383. package/src/module-api.js +140 -1
  384. package/src/platform-server.js +86 -9
  385. package/tests/abandon_task.mjs +59 -3
  386. package/tests/api_cli_flags.mjs +53 -0
  387. package/tests/auth_page_gate.mjs +13 -0
  388. package/tests/backfill_sessions.mjs +127 -1
  389. package/tests/backup_cli.mjs +54 -0
  390. package/tests/bongos_box_connect.mjs +77 -0
  391. package/tests/box_access.mjs +130 -1
  392. package/tests/box_credential.mjs +125 -0
  393. package/tests/box_host_keys.mjs +24 -0
  394. package/tests/box_infra_resolve.mjs +149 -0
  395. package/tests/box_scope_session.mjs +6 -2
  396. package/tests/box_sync.mjs +55 -0
  397. package/tests/box_task_scope.mjs +99 -0
  398. package/tests/branding.mjs +41 -23
  399. package/tests/cf_ruleset_preflight.mjs +142 -0
  400. package/tests/cf_tunnel.mjs +37 -0
  401. package/tests/claim_gate_rebase.mjs +98 -0
  402. package/tests/clawback_confirmed_backlog.mjs +103 -0
  403. package/tests/cli_lib_formatters.mjs +20 -0
  404. package/tests/confirm_attribution.mjs +30 -46
  405. package/tests/credit_grant.mjs +198 -10
  406. package/tests/criterion_authoring_gate.mjs +272 -0
  407. package/tests/criterion_progress.mjs +33 -1
  408. package/tests/dev_lib.mjs +253 -0
  409. package/tests/devbox_artifact_gate.mjs +75 -0
  410. package/tests/devbox_dmg_backdrop.mjs +137 -0
  411. package/tests/devbox_icons.mjs +304 -0
  412. package/tests/devbox_installer_brand.mjs +232 -0
  413. package/tests/devbox_key_register.mjs +234 -0
  414. package/tests/docs_openapi_route.mjs +110 -0
  415. package/tests/doctor_hooks.mjs +141 -0
  416. package/tests/extract_json.mjs +140 -0
  417. package/tests/fitness.mjs +89 -0
  418. package/tests/gen_freshness.mjs +96 -0
  419. package/tests/github_repos_helpers.mjs +91 -0
  420. package/tests/github_token_store.mjs +121 -0
  421. package/tests/goal_advisory.mjs +251 -0
  422. package/tests/goal_criterion_confirm_ui.mjs +269 -0
  423. package/tests/goal_routes.mjs +59 -4
  424. package/tests/goal_tier.mjs +12 -3
  425. package/tests/governance_catalog.mjs +129 -0
  426. package/tests/governance_claim_eligibility.mjs +262 -0
  427. package/tests/governance_config.mjs +187 -0
  428. package/tests/governance_ownership_scope.mjs +410 -0
  429. package/tests/governance_page_gates.mjs +324 -0
  430. package/tests/governance_parity.mjs +336 -0
  431. package/tests/governance_principals.mjs +232 -0
  432. package/tests/governance_protected_surfaces.mjs +208 -0
  433. package/tests/governance_require_permission.mjs +223 -0
  434. package/tests/governance_resolver.mjs +84 -0
  435. package/tests/governance_role_sync.mjs +231 -0
  436. package/tests/governance_seed.mjs +127 -0
  437. package/tests/governance_session_scope.mjs +434 -0
  438. package/tests/governance_vanilla_boot.mjs +203 -0
  439. package/tests/grade_attribution.mjs +7 -7
  440. package/tests/hall_audit.mjs +223 -0
  441. package/tests/hall_nav.mjs +191 -0
  442. package/tests/hall_palette.mjs +206 -0
  443. package/tests/hall_signout.mjs +124 -0
  444. package/tests/hook_user_agents.mjs +10 -0
  445. package/tests/idea_triage.mjs +162 -0
  446. package/tests/idp_device_exchange.mjs +198 -0
  447. package/tests/idp_federation.mjs +100 -0
  448. package/tests/init.mjs +51 -0
  449. package/tests/instance_config.mjs +65 -0
  450. package/tests/instance_mismatch_guard.mjs +23 -6
  451. package/tests/instance_release_config.mjs +104 -0
  452. package/tests/linkify_refs.mjs +15 -2
  453. package/tests/migrations_forward_safe.mjs +86 -0
  454. package/tests/module_api.mjs +1 -1
  455. package/tests/module_contributions.mjs +55 -0
  456. package/tests/module_loader.mjs +10 -5
  457. package/tests/module_scope_active_claims.mjs +94 -0
  458. package/tests/needs_rebase.mjs +92 -0
  459. package/tests/one_active_claim_per_session.mjs +76 -0
  460. package/tests/override_attribution.mjs +10 -8
  461. package/tests/override_request_ownership.mjs +8 -4
  462. package/tests/package_core.mjs +246 -1
  463. package/tests/parse_cookie_decode.mjs +42 -0
  464. package/tests/pay_on_land.mjs +129 -0
  465. package/tests/platform_boot.mjs +100 -1
  466. package/tests/post_login_landing.mjs +42 -0
  467. package/tests/preflight_checks.mjs +72 -0
  468. package/tests/provision.mjs +289 -7
  469. package/tests/pruned_lockfile_integrity.mjs +79 -0
  470. package/tests/publish_branch_route.mjs +53 -0
  471. package/tests/publish_reconciler.mjs +187 -2
  472. package/tests/repo_info_binding.mjs +248 -0
  473. package/tests/reward_gate.mjs +11 -2
  474. package/tests/reward_mode_cost_plus_only.mjs +116 -0
  475. package/tests/reward_mode_surfacing.mjs +168 -0
  476. package/tests/route_permission_check.mjs +301 -0
  477. package/tests/route_rank_check.mjs +254 -12
  478. package/tests/route_validation.mjs +30 -0
  479. package/tests/sequence.mjs +267 -0
  480. package/tests/session_digest.mjs +44 -0
  481. package/tests/session_index.mjs +18 -6
  482. package/tests/session_records.mjs +39 -1
  483. package/tests/session_reward_on_land.mjs +381 -0
  484. package/tests/session_reward_reconcile.mjs +229 -0
  485. package/tests/ship_api_client_pathspec.mjs +100 -0
  486. package/tests/ship_base_flag.mjs +93 -0
  487. package/tests/ship_ci_deploy.mjs +140 -3
  488. package/tests/ship_upstream_deletion.mjs +57 -0
  489. package/tests/smoke_target.mjs +233 -0
  490. package/tests/start_env_guard.mjs +87 -0
  491. package/tests/start_rebase_warning.mjs +103 -0
  492. package/tests/status_mirror_sync.mjs +122 -0
  493. package/tests/stop_refcheck.mjs +19 -1
  494. package/tests/task_goal_link.mjs +15 -6
  495. package/tests/upgrade.mjs +393 -5
  496. package/tests/web_admission_status.mjs +162 -0
  497. package/tests/write_validation_gaps.mjs +110 -0
  498. package/.gitignore +0 -91
  499. package/docs/adr/0139-platform-history-migration-to-cloudbongos.md +0 -94
  500. package/docs/adr/0140-cloudbongos-standalone-reprovision.md +0 -43
  501. package/scripts/hall-preview/fixtures/access-requests.json +0 -20
  502. package/scripts/hall-preview/fixtures/achievements.json +0 -76
  503. package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
  504. package/scripts/hall-preview/fixtures/audit-log.json +0 -68
  505. package/scripts/hall-preview/fixtures/blockers__detail.json +0 -12
  506. package/scripts/hall-preview/fixtures/box__me.json +0 -5
  507. package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -69
  508. package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
  509. package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
  510. package/scripts/hall-preview/fixtures/builders__roster.json +0 -48
  511. package/scripts/hall-preview/fixtures/goals.json +0 -502
  512. package/scripts/hall-preview/fixtures/goals__25.json +0 -96
  513. package/scripts/hall-preview/fixtures/goals__26.json +0 -67
  514. package/scripts/hall-preview/fixtures/goals__3.json +0 -103
  515. package/scripts/hall-preview/fixtures/goals__35.json +0 -49
  516. package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
  517. package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
  518. package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
  519. package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
  520. package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -32
  521. package/scripts/hall-preview/fixtures/inbox.json +0 -330
  522. package/scripts/hall-preview/fixtures/inbox__detail.json +0 -15
  523. package/scripts/hall-preview/fixtures/instance.json +0 -124
  524. package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
  525. package/scripts/hall-preview/fixtures/me.json +0 -476
  526. package/scripts/hall-preview/fixtures/me__art-key__own.json +0 -6
  527. package/scripts/hall-preview/fixtures/me__event-sound-prefs.json +0 -209
  528. package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
  529. package/scripts/hall-preview/fixtures/me__render.json +0 -5
  530. package/scripts/hall-preview/fixtures/me__skill-prefs.json +0 -143
  531. package/scripts/hall-preview/fixtures/me__sound-prefs.json +0 -67
  532. package/scripts/hall-preview/fixtures/me__wandering.json +0 -43
  533. package/scripts/hall-preview/fixtures/override-requests.json +0 -15
  534. package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
  535. package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
  536. package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
  537. package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
  538. package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
  539. package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
  540. package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
  541. package/scripts/hall-preview/fixtures/public__grades.json +0 -50
  542. package/scripts/hall-preview/fixtures/public__progress.json +0 -124
  543. package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
  544. package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
  545. package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
  546. package/scripts/hall-preview/fixtures/security__docs.json +0 -10
  547. package/scripts/hall-preview/fixtures/security__reports.json +0 -31
  548. package/scripts/hall-preview/fixtures/sessions__search.json +0 -9
  549. package/scripts/hall-preview/fixtures/tasks.json +0 -2420
  550. package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
  551. package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
  552. package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
  553. package/scripts/hall-preview/fixtures/versions.json +0 -89
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ // scripts/gds/box-sync.js — BV1.R96 (goal 1000051): "sync now".
3
+ //
4
+ // Re-run the box's source-fetch ON DEMAND so a freshly-claimed task's code
5
+ // materializes within seconds, instead of waiting for the ~10-min cron (which
6
+ // stays the backstop). Because the box's fetch calls GET /box/source-access —
7
+ // which since BV1.R94 returns the sparse set for the builder's ACTIVE CLAIM
8
+ // (task/module scope, ADR 0148) — re-running it RE-SCOPES the box to whatever the
9
+ // builder just claimed. So `/builder-claim N` then `box-sync` = the box holds
10
+ // exactly task N's module code, seconds later.
11
+ //
12
+ // Runs ON THE BOX, where infra/box-source-fetch.sh lives (it is in the box's BASE
13
+ // sparse set). OFF a box there is no fetch script — the source IS the local
14
+ // checkout — so it says so plainly and exits non-zero rather than failing opaquely.
15
+ //
16
+ // Usage: node scripts/gds/box-sync.js # re-fetch now
17
+ // node scripts/gds/box-sync.js --dry-run # print what it WOULD run
18
+
19
+ const fs = require('node:fs');
20
+ const path = require('node:path');
21
+ const { spawnSync } = require('node:child_process');
22
+
23
+ // Reuse box.js's instance-root-first infra resolver (ADR 0150 / R98) so this
24
+ // locates infra/ the SAME way provisioning does — the box's fetch script lives
25
+ // beside the cloud-init template it validates on.
26
+ const { resolveBoxInfraDir } = require('./box.js');
27
+
28
+ const FETCH_SCRIPT = 'box-source-fetch.sh';
29
+
30
+ // Absolute path to the box source-fetch script, or null when it can't be found
31
+ // (i.e. not on a box). Pure: opts thread through to resolveBoxInfraDir for tests.
32
+ function resolveFetchScript(opts = {}) {
33
+ const infraDir = resolveBoxInfraDir(opts);
34
+ if (!infraDir) return null;
35
+ const p = path.join(infraDir, FETCH_SCRIPT);
36
+ try { return fs.existsSync(p) ? p : null; } catch { return null; }
37
+ }
38
+
39
+ function main(argv = process.argv.slice(2)) {
40
+ const dryRun = argv.includes('--dry-run') || argv.includes('-n');
41
+ const script = resolveFetchScript();
42
+ if (!script) {
43
+ console.error(
44
+ `sync: no infra/${FETCH_SCRIPT} found. This runs ON a dev box (where the box `
45
+ + `source-fetch lives). Off a box your source IS your local checkout — nothing to sync.`,
46
+ );
47
+ process.exit(1);
48
+ }
49
+ if (dryRun) {
50
+ console.log(`sync [dry-run]: would run bash ${script}`);
51
+ console.log(' → re-fetches the sparse checkout for your ACTIVE claim (re-scopes in place).');
52
+ process.exit(0);
53
+ }
54
+ console.log(`sync: re-running ${script} — re-scoping the box to your active claim…`);
55
+ const r = spawnSync('bash', [script], { stdio: 'inherit' });
56
+ if (r.error) {
57
+ console.error(`sync: could not run the fetch (${r.error.message})`);
58
+ process.exit(1);
59
+ }
60
+ if (r.status === 0) console.log('sync: done — your box now holds the code for your active claim.');
61
+ process.exit(r.status == null ? 1 : r.status);
62
+ }
63
+
64
+ if (require.main === module) main();
65
+
66
+ module.exports = { resolveFetchScript, FETCH_SCRIPT };
@@ -46,6 +46,55 @@ const MAX_USERDATA_BYTES = 64 * 1024;
46
46
 
47
47
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
48
48
 
49
+ // The box cloud-init (infra/builder-box-cloud-init.yaml + its ~9 gz+b64-inlined
50
+ // scripts) is INSTANCE content, deliberately NOT shipped in @bongos/core (the R89
51
+ // packaging split). On a VENDORED instance box.js runs from
52
+ // node_modules/@bongos/core, so REPO_ROOT/infra does not exist — and the box then
53
+ // boots BARE (no clone, no toolchain, no terminal). So resolve infra/ from the
54
+ // INSTANCE root, riding ADR 0108's resolvers, instead of assuming it sits beside
55
+ // box.js. ADR 0150 (BV1.R97/R98).
56
+ const instanceConfig = require(path.join(REPO_ROOT, 'src/instance-config'));
57
+ const BOX_INFRA_TEMPLATE = 'builder-box-cloud-init.yaml';
58
+
59
+ // Ordered candidate infra/ dirs — first that actually holds the cloud-init
60
+ // template wins. Pure (env + repoRoot injected) so it is unit-testable without
61
+ // touching the real filesystem layout.
62
+ function boxInfraCandidates({ env = process.env, repoRoot = REPO_ROOT } = {}) {
63
+ const out = [];
64
+ const seen = new Set();
65
+ const add = (d) => { if (d && !seen.has(d)) { seen.add(d); out.push(d); } };
66
+ // 1. explicit override: <PREFIX>_BOX_INFRA_DIR (operator escape hatch).
67
+ const override = instanceConfig.resolveEnv('BOX_INFRA_DIR', { env });
68
+ if (override) add(path.resolve(override));
69
+ // 2. the instance root via an EXPLICIT pointer (<PREFIX>_INSTANCE_ROOT env or a
70
+ // bongos.config.json {"instanceRoot":"."}) — where a vendored instance keeps infra/.
71
+ const inst = instanceConfig.resolveInstanceRootExplicit({ env });
72
+ if (inst) add(path.join(inst, 'infra'));
73
+ // 3. REPO_ROOT/infra — the dev single-checkout (and the ADR 0144 node_modules workaround).
74
+ add(path.join(repoRoot, 'infra'));
75
+ // 4. walk up out of node_modules from the core root, so a vendored
76
+ // …/node_modules/@bongos/core still finds its host instance's infra/ with no pointer set.
77
+ let dir = repoRoot;
78
+ for (let i = 0; i < 6; i += 1) {
79
+ const parent = path.dirname(dir);
80
+ if (parent === dir) break;
81
+ dir = parent;
82
+ add(path.join(dir, 'infra'));
83
+ }
84
+ return out;
85
+ }
86
+
87
+ // The resolved infra/ dir (first candidate holding the template), or null when the
88
+ // box cloud-init cannot be located anywhere — the caller MUST fail loud on null
89
+ // rather than provision a box with empty user-data (ADR 0150).
90
+ function resolveBoxInfraDir(opts = {}) {
91
+ for (const c of boxInfraCandidates(opts)) {
92
+ try { if (fs.existsSync(path.join(c, BOX_INFRA_TEMPLATE))) return c; }
93
+ catch { /* unreadable candidate → try the next */ }
94
+ }
95
+ return null;
96
+ }
97
+
49
98
  const CONFIG = {
50
99
  floor: process.env.BOX_PROVISION_MIN_RANK || 'xenos',
51
100
  region: process.env.BOX_REGION || 'nyc3',
@@ -103,6 +152,44 @@ function makeCf(apply) {
103
152
  });
104
153
  }
105
154
 
155
+ // SECURITY PREFLIGHT (task 1002511, ADR 0053 / SR#11). The edge cookie-strip
156
+ // Transform Rule is applied BEST-EFFORT deep inside ensureNamedTunnel, so a
157
+ // CLOUDFLARE_API_TOKEN missing the zone "Transform Rules" edit scope fails as one
158
+ // buried log line — and the apex session cookie keeps reaching the box subdomains.
159
+ // This probe runs BEFORE any DigitalOcean spend (dry-run included; a read-only
160
+ // lookup running for real in a plan is the same precedent as the drift reconcile's
161
+ // live droplet listing) and shouts the exact fix.
162
+ //
163
+ // HONESTY: the probe is a GET, so it can only DISPROVE the scope. A readable phase
164
+ // does NOT mean the rule can be written — only the PUT inside ensureNamedTunnel
165
+ // proves that. Never print this as a confirmed control. Never blocks provisioning:
166
+ // the rule is defense-in-depth and a box without it still works.
167
+ async function preflightCookieStripScope() {
168
+ const { cfTunnel } = loadDeps();
169
+ let scope;
170
+ try {
171
+ scope = await cfTunnel.checkRulesetScope({
172
+ zone: CF.zone, phase: cfTunnel.COOKIE_STRIP_PHASE, token: CF.apiToken,
173
+ });
174
+ } catch (e) {
175
+ scope = { readable: false, reason: `ruleset preflight crashed: ${e.message}`, guidance: '' };
176
+ }
177
+ if (scope.readable) {
178
+ console.log(` cookie-strip ruleset is readable on ${CF.zone} — edit scope stays UNPROVEN until the rule is applied (ADR 0053)`);
179
+ return scope;
180
+ }
181
+ console.log('');
182
+ console.log(' ⚠⚠ SECURITY (ADR 0053 / SR#11) — the edge cookie-strip rule cannot be confirmed');
183
+ console.log(` ${scope.reason}`);
184
+ console.log(` IMPACT: the apex session cookie (Domain=.${CF.zone}) is sent by the browser to`);
185
+ console.log(` term-*.${CF.terminalZone} and sandbox-*.${CF.terminalZone} — hosts OUTSIDE the`);
186
+ console.log(' ADR 0016 trust boundary — until that Transform Rule is actually in place.');
187
+ if (scope.guidance) console.log(` FIX: ${scope.guidance}`);
188
+ console.log(' Provisioning CONTINUES — this is defense-in-depth, never a provisioning gate.');
189
+ console.log('');
190
+ return scope;
191
+ }
192
+
106
193
  function numEnv(name, dflt) {
107
194
  const v = process.env[name];
108
195
  if (v == null || v === '') return dflt;
@@ -130,8 +217,8 @@ function loadDeps() {
130
217
  // eslint-disable-next-line global-require
131
218
  const { DoApi } = require(path.join(REPO_ROOT, 'scripts/gds/do-api'));
132
219
  // eslint-disable-next-line global-require
133
- const { CfTunnelApi } = require(path.join(REPO_ROOT, 'scripts/gds/cf-tunnel'));
134
- _deps = { pool, boxes, boxAccess, boxOnboard, DoApi, CfTunnelApi };
220
+ const cfTunnel = require(path.join(REPO_ROOT, 'scripts/gds/cf-tunnel'));
221
+ _deps = { pool, boxes, boxAccess, boxOnboard, DoApi, CfTunnelApi: cfTunnel.CfTunnelApi, cfTunnel };
135
222
  return _deps;
136
223
  }
137
224
 
@@ -254,8 +341,18 @@ function runDnsHookBlackhole(hostname, hook = CONFIG.dnsHook) {
254
341
  // for the builder being provisioned (#685) — base64'd here and baked in so the
255
342
  // FIRST SSH login works before the box has a GDS token. An empty/absent block
256
343
  // renders an empty managed block (a keyless box; the operator is warned upstream).
257
- function readCloudInit({ authorizedKeysBlock = '', boxEnv = '', gdsSession = '' } = {}) {
258
- const p = path.join(REPO_ROOT, 'infra', 'builder-box-cloud-init.yaml');
344
+ function readCloudInit({ authorizedKeysBlock = '', boxEnv = '', gdsSession = '', infraDir = resolveBoxInfraDir() } = {}) {
345
+ // Fail loud, never provision a BARE box (ADR 0150). No infra/ located → there is
346
+ // no cloud-init to render, so THROW here rather than the old silent `return null`
347
+ // that let cmdProvision ship empty user-data (a box that boots as bare Ubuntu).
348
+ if (!infraDir) {
349
+ throw new Error(
350
+ `box cloud-init not found: no infra/${BOX_INFRA_TEMPLATE} in any candidate location `
351
+ + `(set <PREFIX>_BOX_INFRA_DIR or <PREFIX>_INSTANCE_ROOT, or ship infra/). A box must `
352
+ + `never be provisioned without user-data — it would boot bare (ADR 0150).`,
353
+ );
354
+ }
355
+ const p = path.join(infraDir, BOX_INFRA_TEMPLATE);
259
356
  // gzip+base64 a repo file into the cloud-init write_files (`encoding: gz+b64`).
260
357
  // EVERY inlined infra file goes through this: cloud-init gunzips it back to the
261
358
  // exact bytes on the box, so it is byte-identical to plain b64 but ~2-3x smaller,
@@ -263,54 +360,61 @@ function readCloudInit({ authorizedKeysBlock = '', boxEnv = '', gdsSession = ''
263
360
  // -fetch growth in task 1314 had pushed the all-b64-but-source-fetch render to
264
361
  // 67 KB — gz+b64-ing the rest brings it back to ~51 KB, task 1321). Only the
265
362
  // dynamic runtime values (authorized_keys, box.env, gds-session) stay plain b64
266
- // below — they are minted per-provision, not read from infra/. Warn loudly (not
267
- // throw) if a file is unreadable so a half-wired box surfaces, not boots silent.
363
+ // below — they are minted per-provision, not read from infra/. A missing inlined
364
+ // file is COLLECTED and fails loud after the render (ADR 0150) — a half-wired box
365
+ // (that component silently never boots) is a bug, not something to warn past.
366
+ const missingInfra = [];
268
367
  const fileGzB64 = (name) => {
269
368
  try {
270
- return zlib.gzipSync(fs.readFileSync(path.join(REPO_ROOT, 'infra', name))).toString('base64');
271
- } catch (e) {
272
- console.error(`⚠ ${name} not readable (${e.message}) — the box will boot WITHOUT it; fix infra/${name} before provisioning`);
369
+ return zlib.gzipSync(fs.readFileSync(path.join(infraDir, name))).toString('base64');
370
+ } catch {
371
+ missingInfra.push(name);
273
372
  return '';
274
373
  }
275
374
  };
276
- try {
277
- const raw = fs.readFileSync(p, 'utf8');
278
- // Inject the rank-gated source-fetch (#600) + authorized-keys-fetch (#685)
279
- // scripts as gz+b64 (a single YAML-safe token — no indentation/escaping
280
- // pitfalls; cloud-init gunzips encoding: gz+b64 back to the exact file), plus
281
- // the baked authorized_keys block. ONE canonical copy of each lives in infra/.
282
- return raw
283
- .replace(/__REPO_URL__/g, process.env.BOX_REPO_URL || '')
284
- .replace(/__BOX_SOURCE_FETCH_B64__/g, fileGzB64('box-source-fetch.sh'))
285
- .replace(/__BOX_AUTHKEYS_FETCH_B64__/g, fileGzB64('box-authorized-keys-fetch.sh'))
286
- .replace(/__BUILDER_AUTHORIZED_KEYS_B64__/g, Buffer.from(authorizedKeysBlock || '', 'utf8').toString('base64'))
287
- // /etc/otb/box.env — empty by default (quick tunnel); the named-tunnel
288
- // connector token + stable hostname when BOX_NAMED_TUNNEL is on (#771).
289
- .replace(/__BOX_ENV_B64__/g, Buffer.from(boxEnv || '', 'utf8').toString('base64'))
290
- // /root/.config/otb/gds-session.json — the builder's GDS session, minted at
291
- // provision so box-source-fetch (#600) can authenticate on FIRST boot and
292
- // clone the rank-scoped repo. Without it the box never clones (the #818
293
- // chicken-and-egg: the session normally comes from /builder-setup, which
294
- // lives in the un-cloned repo).
295
- .replace(/__GDS_SESSION_B64__/g, Buffer.from(gdsSession || '', 'utf8').toString('base64'))
296
- // #774: the browser-terminal units, inlined so they start at FIRST BOOT
297
- // (independent of the repo clone). ONE canonical copy lives in infra/.
298
- .replace(/__BOX_TERMINAL_SERVICE_B64__/g, fileGzB64('box-terminal.service'))
299
- // #917: credential-drift watcher pair — restarts ttyd when the credential
300
- // file lands/changes after the service started (the wake/first-boot race).
301
- .replace(/__BOX_TERMINAL_CREDSYNC_SERVICE_B64__/g, fileGzB64('box-terminal-credsync.service'))
302
- .replace(/__BOX_TERMINAL_CREDENTIAL_PATH_B64__/g, fileGzB64('box-terminal-credential.path'))
303
- .replace(/__CLOUDFLARED_SERVICE_B64__/g, fileGzB64('cloudflared.service'))
304
- .replace(/__CLOUDFLARED_RUN_B64__/g, fileGzB64('cloudflared-run.sh'))
305
- // #893: the `close-box` teardown command, present at first boot so a bare
306
- // browser-only box has it before any repo clone (box-source-fetch refreshes
307
- // it on existing boxes). ONE canonical copy lives in infra/close-box.sh.
308
- .replace(/__CLOSE_BOX_B64__/g, fileGzB64('close-box.sh'))
309
- // task 986: the browser-terminal auto-launcher — runs `claude` on first
310
- // connect so the builder skips the "type claude" step. box-source-fetch
311
- // refreshes it on existing boxes. ONE canonical copy in infra/otb-box-launch.sh.
312
- .replace(/__OTB_BOX_LAUNCH_B64__/g, fileGzB64('otb-box-launch.sh'));
313
- } catch { return null; }
375
+ const raw = fs.readFileSync(p, 'utf8');
376
+ // Inject the rank-gated source-fetch (#600) + authorized-keys-fetch (#685)
377
+ // scripts as gz+b64 (a single YAML-safe token — no indentation/escaping
378
+ // pitfalls; cloud-init gunzips encoding: gz+b64 back to the exact file), plus
379
+ // the baked authorized_keys block. ONE canonical copy of each lives in infra/.
380
+ const rendered = raw
381
+ .replace(/__REPO_URL__/g, process.env.BOX_REPO_URL || '')
382
+ .replace(/__BOX_SOURCE_FETCH_B64__/g, fileGzB64('box-source-fetch.sh'))
383
+ .replace(/__BOX_AUTHKEYS_FETCH_B64__/g, fileGzB64('box-authorized-keys-fetch.sh'))
384
+ .replace(/__BUILDER_AUTHORIZED_KEYS_B64__/g, Buffer.from(authorizedKeysBlock || '', 'utf8').toString('base64'))
385
+ // /etc/otb/box.env — empty by default (quick tunnel); the named-tunnel
386
+ // connector token + stable hostname when BOX_NAMED_TUNNEL is on (#771).
387
+ .replace(/__BOX_ENV_B64__/g, Buffer.from(boxEnv || '', 'utf8').toString('base64'))
388
+ // /root/.config/otb/gds-session.json — the builder's GDS session, minted at
389
+ // provision so box-source-fetch (#600) can authenticate on FIRST boot and
390
+ // clone the rank-scoped repo. Without it the box never clones (the #818
391
+ // chicken-and-egg: the session normally comes from /builder-setup, which
392
+ // lives in the un-cloned repo).
393
+ .replace(/__GDS_SESSION_B64__/g, Buffer.from(gdsSession || '', 'utf8').toString('base64'))
394
+ // #774: the browser-terminal units, inlined so they start at FIRST BOOT
395
+ // (independent of the repo clone). ONE canonical copy lives in infra/.
396
+ .replace(/__BOX_TERMINAL_SERVICE_B64__/g, fileGzB64('box-terminal.service'))
397
+ // #917: credential-drift watcher pair — restarts ttyd when the credential
398
+ // file lands/changes after the service started (the wake/first-boot race).
399
+ .replace(/__BOX_TERMINAL_CREDSYNC_SERVICE_B64__/g, fileGzB64('box-terminal-credsync.service'))
400
+ .replace(/__BOX_TERMINAL_CREDENTIAL_PATH_B64__/g, fileGzB64('box-terminal-credential.path'))
401
+ .replace(/__CLOUDFLARED_SERVICE_B64__/g, fileGzB64('cloudflared.service'))
402
+ .replace(/__CLOUDFLARED_RUN_B64__/g, fileGzB64('cloudflared-run.sh'))
403
+ // #893: the `close-box` teardown command, present at first boot so a bare
404
+ // browser-only box has it before any repo clone (box-source-fetch refreshes
405
+ // it on existing boxes). ONE canonical copy lives in infra/close-box.sh.
406
+ .replace(/__CLOSE_BOX_B64__/g, fileGzB64('close-box.sh'))
407
+ // task 986: the browser-terminal auto-launcher — runs `claude` on first
408
+ // connect so the builder skips the "type claude" step. box-source-fetch
409
+ // refreshes it on existing boxes. ONE canonical copy in infra/otb-box-launch.sh.
410
+ .replace(/__OTB_BOX_LAUNCH_B64__/g, fileGzB64('otb-box-launch.sh'));
411
+ if (missingInfra.length) {
412
+ throw new Error(
413
+ `box cloud-init is incomplete under ${infraDir}: missing ${missingInfra.join(', ')} `
414
+ + `— a box would boot half-wired (ADR 0150). Fix the instance infra/ before provisioning.`,
415
+ );
416
+ }
417
+ return rendered;
314
418
  }
315
419
 
316
420
  // ---- provision -------------------------------------------------------------
@@ -357,6 +461,20 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
357
461
  console.log(` region=${CONFIG.region} size=${CONFIG.size} image=${CONFIG.baseImage} host=${hostname}`);
358
462
  console.log(` baked SSH keys: ${sshKeys.length}${sshKeys.length === 0 ? ' (⚠ builder cannot SSH in until a key is registered — see `box.js onboard`)' : ''}`);
359
463
 
464
+ // ADR 0150 — resolve the box cloud-init source up front (dry-run AND apply) and
465
+ // FAIL LOUD if it can't be located. A missing infra/ means empty user-data, i.e.
466
+ // a box that boots BARE. Aborting here (before any session/tunnel is minted)
467
+ // surfaces the packaging bug in the PLAN, not as a silent bare box hours later.
468
+ const infraDir = resolveBoxInfraDir();
469
+ if (!infraDir) {
470
+ return fail(
471
+ `box cloud-init source not found — no infra/${BOX_INFRA_TEMPLATE} in any candidate location `
472
+ + `(<PREFIX>_BOX_INFRA_DIR, <PREFIX>_INSTANCE_ROOT / bongos.config.json, ${path.join(REPO_ROOT, 'infra')}, or a node_modules ancestor). `
473
+ + `A box would boot BARE — refusing to provision. Ship infra/ or point the instance root at it (ADR 0150).`,
474
+ );
475
+ }
476
+ console.log(` cloud-init source: ${infraDir}`);
477
+
360
478
  // Named-tunnel automation (#771, ADR 0038 Part B). OPT-IN: off → the box uses
361
479
  // the tokenless quick tunnel (#760 default) and box.env is left empty. On but
362
480
  // misconfigured → fail loud now (an operator who turned it on meant it).
@@ -366,6 +484,9 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
366
484
  const termHost = namedTunnelEnabled() ? `term-${builder.github_login}.${CF.terminalZone}` : null;
367
485
  if (namedTunnelEnabled()) {
368
486
  console.log(` named tunnel: ${termHost} (Cloudflare zone ${CF.zone})`);
487
+ // Before ANY droplet spend, and on the dry-run path too, so `provision <login>`
488
+ // with no --apply tells the operator the truth for free (task 1002511).
489
+ await preflightCookieStripScope();
369
490
  } else {
370
491
  console.log(' browser terminal: quick tunnel (tokenless trycloudflare.com — set BOX_NAMED_TUNNEL=1 for a stable hostname)');
371
492
  }
@@ -399,7 +520,7 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
399
520
  let terminalCredential = null;
400
521
  if (namedTunnelEnabled()) {
401
522
  try {
402
- const { token, hostname: th, previewHostname: ph } = await makeCf(true).ensureNamedTunnel({
523
+ const { token, hostname: th, previewHostname: ph, edgeRules } = await makeCf(true).ensureNamedTunnel({
403
524
  login: builder.github_login, zone: CF.zone, terminalZone: CF.terminalZone,
404
525
  });
405
526
  terminalHostname = th;
@@ -411,6 +532,18 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
411
532
  boxEnv = `# /etc/otb/box.env — baked by box.js provision (#771/#774, ADR 0038 named tunnel)\nBOX_TUNNEL_TOKEN=${token}\nBOX_TERMINAL_HOSTNAME=${th}\nBOX_TERMINAL_CREDENTIAL=${terminalCredential}\nBOX_PREVIEW_HOSTNAME=${ph}\n`;
412
533
  console.log(` ✓ named tunnel ready — ${th}`);
413
534
  console.log(` ✓ live game preview — https://${ph} (ADR 0044)`);
535
+ // The rule PUT is the ONLY thing that proves edit scope. Report its real
536
+ // outcome next to the ✓ lines so a silent miss lands in the summary rather
537
+ // than mid-log (task 1002511).
538
+ if (edgeRules && !edgeRules.cookieStrip.applied) {
539
+ console.log(` ⚠⚠ SECURITY (ADR 0053 / SR#11): the edge cookie-strip Transform Rule was NOT applied${edgeRules.cookieStrip.error ? ` — ${edgeRules.cookieStrip.error}` : ''}`);
540
+ console.log(` Platform cookies for .${CF.zone} can reach ${th} and ${ph} until it is.`);
541
+ console.log(' Widen CLOUDFLARE_API_TOKEN with zone "Transform Rules → Edit", or apply the rule');
542
+ console.log(' once by hand — see docs/recipes/builder-box-lifecycle.md.');
543
+ }
544
+ if (edgeRules && !edgeRules.cacheBypass.applied) {
545
+ console.log(` ⚠ the sandbox cache-bypass Cache Rule was NOT applied${edgeRules.cacheBypass.error ? ` — ${edgeRules.cacheBypass.error}` : ''} (stale preview risk, #1065)`);
546
+ }
414
547
  } catch (e) {
415
548
  await boxes.setBoxState(pool, builder.id, 'error', { error_note: `named tunnel: ${e.message}` });
416
549
  return fail(`named tunnel setup failed: ${e.message}`);
@@ -443,15 +576,29 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
443
576
  const gdsSession = JSON.stringify({
444
577
  token: sessionToken,
445
578
  builder: { id: builder.id, github_login: builder.github_login, rank: builder.rank },
446
- api_base: process.env.GDS_PUBLIC_ORIGIN || 'https://amazonprimea.com',
579
+ // The instance's OWN public origin (task 1002428) — a hardcoded founder host
580
+ // here would point every newly provisioned box at the wrong instance.
581
+ api_base: process.env.GDS_PUBLIC_ORIGIN || instanceConfig.defaultApiBase(),
447
582
  }, null, 2);
448
583
 
449
584
  const doApi = makeDo(apply);
450
585
  // Render + size-check the cloud-init BEFORE touching DO (#903). DO rejects
451
586
  // user-data over 64 KiB with an opaque error; fail fast with an actionable one
452
587
  // so an over-budget cloud-init is caught here, not as a cryptic API 4xx.
453
- const userData = readCloudInit({ authorizedKeysBlock, boxEnv, gdsSession });
588
+ let userData;
589
+ try {
590
+ userData = readCloudInit({ authorizedKeysBlock, boxEnv, gdsSession, infraDir });
591
+ } catch (e) {
592
+ await boxes.setBoxState(pool, builder.id, 'error', { error_note: `cloud-init: ${e.message}`.slice(0, 480) });
593
+ return fail(e.message);
594
+ }
454
595
  const userDataBytes = userData ? Buffer.byteLength(userData, 'utf8') : 0;
596
+ // Belt-and-braces (ADR 0150): readCloudInit throws above, but NEVER hand DO empty
597
+ // user-data — that IS the bare-box failure this fix closes.
598
+ if (!userDataBytes) {
599
+ await boxes.setBoxState(pool, builder.id, 'error', { error_note: 'cloud-init render produced empty user-data' });
600
+ return fail('cloud-init render produced EMPTY user-data — a box would boot bare (ADR 0150); refusing to provision.');
601
+ }
455
602
  if (userDataBytes > MAX_USERDATA_BYTES) {
456
603
  await boxes.setBoxState(pool, builder.id, 'error', {
457
604
  error_note: `cloud-init ${userDataBytes}B > ${MAX_USERDATA_BYTES}B`,
@@ -461,6 +608,14 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
461
608
  await boxes.setBoxState(pool, builder.id, 'provisioning', {
462
609
  scope, provisioned_rank: builder.rank, region: CONFIG.region, size_slug: CONFIG.size,
463
610
  droplet_name: name, hostname, error_note: null,
611
+ // The droplet about to boot mints FRESH host keys, so anything the row still
612
+ // carries describes a box that is gone. deprovisionOne clears these on teardown
613
+ // (idea 310), but a provision over a row that never went through teardown — an
614
+ // errored/parked box, a re-provision after a manual DO delete — kept serving the
615
+ // predecessor's identity via GET /box/me, and the connect path pinned it: "Host
616
+ // denied (verification failed)". Clear here too; the new box re-reports via
617
+ // POST /box/host-keys, and until then the client falls back to a live keyscan.
618
+ host_keys: null, host_keys_at: null,
464
619
  });
465
620
  try {
466
621
  const { droplet } = await doApi.createDroplet({
@@ -522,8 +677,21 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
522
677
  // box can't fetch its own authorized_keys until the builder has a token ON the
523
678
  // box (chicken-and-egg), onboard REQUIRES at least one registered key by default;
524
679
  // --allow-no-keys overrides for an operator who knows they'll re-provision later.
680
+ // The instance's product name, for the Claude Desktop environment label the
681
+ // connect instructions name (task 1002428). Neutral fallback, never a foreign brand.
682
+ function boxEnvLabel() {
683
+ try {
684
+ const id = require(path.join(REPO_ROOT, 'src/branding')).branding().identity || {};
685
+ if (id.productName) return id.productName;
686
+ } catch (_) { /* fall through */ }
687
+ return 'Cloud Bongos';
688
+ }
689
+
525
690
  function gdsBase() {
526
- return (process.env.GDS_API_BASE || 'https://amazonprimea.com').replace(/\/+$/, '');
691
+ // Session-aware, via the single source of truth (task 1002427/1002428) — the
692
+ // operator's signed-in instance, not a hardcoded founder host. <PREFIX>_API_BASE
693
+ // (incl. legacy GDS_API_BASE) still wins, so existing overrides are unchanged.
694
+ return String(instanceConfig.resolveApiBase()).replace(/\/+$/, '');
527
695
  }
528
696
 
529
697
  function printWelcomePacket(login, scope) {
@@ -540,7 +708,7 @@ function printWelcomePacket(login, scope) {
540
708
  console.log(` Windows: irm ${base}/api/gds/box/connect.ps1 | iex`);
541
709
  console.log(` (guide: ${base}/api/gds/box/connect )`);
542
710
  console.log('');
543
- console.log('Then: restart Claude Desktop → Code → Environment → "Amazonprimea Dev Box".');
711
+ console.log(`Then: restart Claude Desktop → Code → Environment → "${boxEnvLabel()} Dev Box".`);
544
712
  console.log('');
545
713
  console.log('On a Chromebook / browser-only device (no Claude Desktop)? Skip the above —');
546
714
  console.log('your box serves its own terminal in a browser tab (ADR 0038). Once it is');
@@ -1204,6 +1372,10 @@ Named browser-terminal tunnel (#771, ADR 0038 — OPT-IN, default OFF):
1204
1372
  connector token; deprovision tears it down). Off → the
1205
1373
  zero-secret quick tunnel (trycloudflare.com).
1206
1374
  CLOUDFLARE_API_TOKEN token with Account:Cloudflare Tunnel:Edit + Zone:DNS:Edit
1375
+ + Zone:Transform Rules:Edit (the ADR 0053 cookie-strip —
1376
+ without it platform cookies reach the box subdomains)
1377
+ + Zone:Cache Rules:Edit (the sandbox cache bypass, #1065).
1378
+ provision read-probes the ruleset scope before spending.
1207
1379
  CLOUDFLARE_ACCOUNT_ID the Cloudflare account the tunnels live in
1208
1380
  CLOUDFLARE_ZONE DNS zone (default ${CF.zone})
1209
1381
  BOX_TERMINAL_ZONE hostname suffix (default ${CF.terminalZone})`);
@@ -1255,4 +1427,4 @@ async function main() {
1255
1427
 
1256
1428
  if (require.main === module) main();
1257
1429
 
1258
- module.exports = { cmdProvision, deprovisionOne, deleteWithRetry, cmdReconcileDrift, runDnsHook, runDnsHookBlackhole, markErrorUnlessDestroyed };
1430
+ module.exports = { cmdProvision, deprovisionOne, deleteWithRetry, cmdReconcileDrift, runDnsHook, runDnsHookBlackhole, markErrorUnlessDestroyed, resolveBoxInfraDir, boxInfraCandidates, readCloudInit, BOX_INFRA_TEMPLATE };