@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,62 @@
1
+ # 0145 — A free "own-repo" hosted project tier needs domain separation + per-project isolation (not a bare cloudbongos.com subdomain)
2
+
3
+ - **Status:** Accepted — owner decided 2026-07-15: **keep `cloudbongos.com`** (no separate domain), **container** isolation, **all-free** for now. Choosing the literal same domain makes hardening core sign-in a required prerequisite (see "Decisions" + "Required build sequence"). Build unblocked ([task 1002251](https://cloudbongos.com/builders#/task/1002251)); blocker 1000080 resolved.
4
+ - **Date:** 2026-07-15
5
+ - **Deciders:** Lars (owner/Archon) chose the direction after a security review; Claude ran the review + designed.
6
+ - **Task:** [task 1002249](https://cloudbongos.com/builders#/task/1002249) · **Follow-up to** [ADR 0141](<redacted>.md) (federated hub IdP) and [ADR 0143](<redacted>.md) (the `/create` wizard).
7
+
8
+ ## Context
9
+
10
+ Owner design review of the `/create` wizard ([public-landing/create.html](../../public-landing/create.html)) asked that a new project be reachable at a **free `<handle>.<platform>` address that works out of the box** (sign-in included), with **bring-your-own-domain** as the optional upgrade — i.e. the handle becomes a real subdomain, not just an internal name.
11
+
12
+ Today `/create` always provisions the **`standalone`** shape (the owner's own GitHub repo + a pinned `@bongos/core`, run **control-plane-local** on the hub box), and sign-in on a standalone requires the owner to hand-create their own GitHub OAuth app. The natural-looking implementation was: default the address to `<handle>.cloudbongos.com` and extend the hub federation ([ADR 0141](<redacted>.md)) — which already gives co-tenant instances zero-setup sign-in — to standalone instances on that subdomain. A pre-build security review found this **UNSAFE** on two independent counts.
13
+
14
+ ### Finding 1 — the hub session cookie is shared across all `*.cloudbongos.com` (account takeover)
15
+
16
+ The hub's `gds_session` cookie is issued with `Domain=.cloudbongos.com` on the apex/`builders.`/`status.` hosts, by design, so one sign-in is shared across those subdomains ([src/bongos/auth.js](../../src/bongos/auth.js): `SESSION_COOKIE`, `configuredCookieApex`, `sessionCookieDomainOpts`, the `res.cookie(SESSION_COOKIE, …)` call). A cookie with `Domain=.cloudbongos.com` is sent by the browser to **every** `*.cloudbongos.com` host — including an owner's `mercury.cloudbongos.com`. `httpOnly` does not help (it blocks `document.cookie`, not a server reading its own `req.headers.cookie`); `sameSite:lax` does not help (apex and subdomain are the **same registrable domain** = same site). So a standalone instance running **the owner's code** on a cloudbongos.com subdomain would receive the live hub session token of any signed-in visitor and could replay it against the hub → full account takeover. The neutral core ships `cookieDomain: ""` (host-only, safe); the **live hub must** set `.cloudbongos.com` for its own cross-subdomain sign-in, so the risk is real in production.
17
+
18
+ The co-tenant instances that federate on cloudbongos.com subdomains today (e.g. `demo.cloudbongos.com`) are safe **only because they run platform-controlled code** ([modules/provisioning/provisioning.js](../../modules/provisioning/provisioning.js): `co-tenant` = a process on THIS control-plane checkout; `standalone` = the owner's OWN repo checkout). The precedent does **not** extend to owner code — it relies on exactly the first-party-code-on-every-subdomain property that owner code breaks. This is why [ADR 0141](<redacted>.md) §4 scoped federation to co-tenant.
19
+
20
+ ### Finding 2 — control-plane-local standalone runs owner code on the hub box, sudo-capable (pre-existing)
21
+
22
+ A `standalone` runs **on the hub box** ([scripts/gds/provision.js](../../scripts/gds/provision.js): `standaloneBase`, control-plane-local IP) as the **same UID as the hub** (`serviceUnit` hardcodes `User=lars`), and its deploy runs `npm ci` (not `--ignore-scripts`) in a **passwordless-sudo-capable** runner context. A malicious owner could ship a `postinstall` and get code execution as `lars` with sudo on the hub box → the hub's signing key, every instance's secrets, the hub DB. This is a pre-existing property of running owner code on the hub box at all, independent of federation, but any free "run your code for you" tier would expose it at scale.
23
+
24
+ The federation protocol itself is sound: the hub binds the minted audience to the instance's exact origin (the `#1002108` fix; asymmetric Ed25519 so one instance can't forge for another). A leaked per-instance client secret is contained to its own origin. The weakness is **placement** (shared cookie domain) and **execution** (owner code on the hub box), not the protocol.
25
+
26
+ ## Decision
27
+
28
+ **Do not** put an owner-code instance on a bare `cloudbongos.com` subdomain, and **do not** extend federation to `standalone` as it exists today. A free "your-own-repo, works-out-of-the-box" tier is delivered **only** once both conditions the review identified are met:
29
+
30
+ 1. **Domain separation.** Free owner instances live under a **different registrable domain** than the hub (e.g. a dedicated `*.<apps-domain>`), so they are cross-site from cloudbongos.com and **never receive the `gds_session` cookie**. (Alternative: make the hub session cookie host-only on a dedicated auth host — but that breaks today's `builders.`/`status.` shared sign-in and is a larger redesign.)
31
+ 2. **Per-project isolation.** Owner code must not run as a sudo-capable, same-UID-as-hub process on the hub box: isolate each instance (its own droplet — the existing `dedicated` shape — or a container with a distinct UID, `npm ci --ignore-scripts`, verified-pinned-core-only, and no passwordless sudo in the deploy path).
32
+
33
+ Only after (1)+(2) is the origin-bound, asymmetric federation ([ADR 0141](<redacted>.md) §4) safe to extend to these instances, giving the zero-setup sign-in.
34
+
35
+ Points 1 & 2 of the same design review (drop the redundant Home-step "create repo" button; collapse the repo picker on select) were **unrelated to this and already shipped** ([task 1002247](https://cloudbongos.com/builders#/task/1002247)).
36
+
37
+ ## Decisions (owner — resolved 2026-07-15)
38
+
39
+ - **Domain: keep `cloudbongos.com`** (no separate domain). The owner accepts the "same-site" tradeoff: because tenants (`<handle>.cloudbongos.com`) share the registrable domain with the hub, the browser's same-site protections between them are off, so **Finding 1 is closed by hardening core sign-in** (below) rather than by domain separation. This is the fragile/expert path — it needs strict, permanent cookie/CSRF/CORS hygiene, and one lapse reopens the takeover hole. (Rejected: the separate cloudbongos-branded sibling domain, e.g. `cloudbongos.app`, which the browser would treat as a different site and isolate for free — the industry-standard approach, à la `github.io`.)
40
+ - **Isolation: containers.** Each tenant runs in its own container (distinct UID, `npm ci --ignore-scripts`, no passwordless sudo, not as `lars` on the hub box) — closes Finding 2.
41
+ - **Cost: all-free for now.** Containers make many-instances-per-box cheap; a spend cap is revisited at scale ([§4](../../CLAUDE.md)).
42
+
43
+ ## Required build sequence (task 1002251)
44
+
45
+ Keeping `cloudbongos.com` makes the sign-in hardening a **hard, security-critical prerequisite** — it changes sign-in for *every* user of the hub, so it lands and is verified on its own before any tenant is placed on a subdomain:
46
+
47
+ 1. **Harden core sign-in (FIRST).** Make `gds_session` a host-only `__Host-` cookie so tenant subdomains never receive it (Finding 1) and cannot toss a parent-domain cookie at the hub; **rework the `builders.`/`status.` shared sign-in** that currently depends on the `.cloudbongos.com`-scoped cookie ([src/bongos/auth.js](../../src/bongos/auth.js) `APEX_COOKIE_HOSTS`); add **CSRF tokens** across all state-changing hub endpoints (SameSite no longer defends against same-site tenants). Audit every hub cookie + CORS rule for `.cloudbongos.com` scope.
48
+ 2. **Container isolation.** Provision tenant instances in per-instance containers (distinct UID, `--ignore-scripts`, no sudo in the deploy path) — closes Finding 2.
49
+ 3. **Extend federation.** With 1+2 done, extend the origin-bound Ed25519 federation ([ADR 0141](<redacted>.md) §4) to these isolated same-domain instances for zero-setup sign-in.
50
+ 4. **Frontend.** Rebuild the `/create` Address step (the stashed redesign is the starting point) + the summary + the federated done-panel.
51
+
52
+ ## Consequences
53
+
54
+ - The `/create` "free `<handle>.cloudbongos.com`, out of the box" flow is **not built**; the frontend redesign that assumed it is stashed, not shipped. Until this tier exists, `/create` stays own-repo + bring-your-own-domain (safe today).
55
+ - Finding 2 (owner code as sudo-capable `lars` on the hub box) is a **standing risk for the current control-plane-local `standalone` shape** and should be hardened regardless of this tier.
56
+ - If the cost model lands as "platform-subsidized free," a hard spend cap + a cheaper fallback (container isolation, or the co-tenant tier below) is required per [§4](../../CLAUDE.md).
57
+
58
+ ## Alternatives considered
59
+
60
+ - **(A) Free co-tenant tier (rejected by owner for this ask).** A free `<handle>.cloudbongos.com` running **platform** code on the shared box (like `demo.cloudbongos.com`) is safe + $0 + already federates — but gives the user no separate repo of their own. Closest safe match to "free + out of the box," at the cost of "your own repo."
61
+ - **(C) Keep today's model, honest copy (deferred).** Own repo + bring-your-own-domain, with the `/create` copy clarified (handle = platform name; domain = the real address, needed for sign-in). Safe and shippable now; delivers no free address.
62
+ - **(B) — chosen:** free address + own repo, made safe via domain separation + isolation, accepting the cost. This ADR records why B is required to be safe and what it depends on.
@@ -0,0 +1,53 @@
1
+ # ADR 0146 — Configurable reward mode: `cost-plus-only` (retire the per-task estimate payout per-instance)
2
+
3
+ **Date:** 2026-07-16
4
+ **Status:** Accepted
5
+ **Discipline:** engineer (GDS / economy)
6
+ **Task:** [#1002270](https://cloudbongos.com/builders#/task/1002270) (BONGOS-V1, under goal 1000037)
7
+
8
+ ## Context — the problem
9
+
10
+ A shipped task pays a builder from **two reward streams that land on the same balance**:
11
+
12
+ 1. **The per-task ESTIMATE** — `credits_reward × kind multiplier` ([ADR 0023](<redacted>.md)). A **unitless author estimate** set when the task is authored (or auto-assigned by the no-zero-reward backstop, ADR 0096). It is what a builder sees advertised at claim time, and — since [ADR 0120](<redacted>.md) — it books at ship as `credit_log.reason = 'task.shipped'`, plus a 25% idea-promotion slice ([ADR 0023](<redacted>.md)) to the idea's original capturer.
13
+ 2. **The cost-plus SESSION reward** — `round(true_cost_usd × 1.20)`, 1 drachma = $1 ([ADR 0054](<redacted>.md)). Grounded in the session's **real API spend**, booked as `session.token_reward`.
14
+
15
+ [ADR 0054](<redacted>.md) named this the **"two drachma conventions, one ledger"** tension and explicitly flagged reconciling them as a deferred future-review trigger: one stream is unitless bounty points, the other is dollars, and they are summed into one `total_credits`. Goal 1000037 changed only *when* both pay (on land), never *whether* both pay.
16
+
17
+ The owner's stated intent for Cloud Bongos is that **a credit means one dollar of cost-plus value and nothing else**. Under the two-stream model that is not true — roughly half of a shipped task's payout is the arbitrary estimate. The owner asked to make the cost-plus reward the **sole** equity reward for Cloud Bongos, and for that choice to be **configurable in the economy module** (so other instances are unaffected and can choose differently).
18
+
19
+ ## Decision
20
+
21
+ Add a per-instance reward **mode** to the existing reward policy, resolved by the economy module.
22
+
23
+ - **Config:** `branding.reward.mode` — a new field beside the existing `{ marginPct, usdPeg }` ([the branding contract](../branding-contract.md)):
24
+ - `"cost-plus-and-estimate"` — **the default.** Pay both streams, exactly as before. Every existing instance keeps its current behavior on upgrade with no config change.
25
+ - `"cost-plus-only"` — the cost-plus session reward is the **sole equity reward**; the per-task estimate payout is withheld.
26
+ - **Resolution + gate — entirely inside the economy module** (`modules/economy/credits.js`):
27
+ - a new **pure** `rewardPolicy(rawReward)` maps the raw branding object → `{ marginPct, usdPeg, mode, perTaskCredit }`, where `perTaskCredit = mode !== 'cost-plus-only'`. `rewardConfig()` delegates to it. (Mirrors the pure/impure split of `computeSessionTokenReward`, so the mapping is unit-testable without touching the memoized `branding()` cache.)
28
+ - `insertMultipliedConfirmCredit(...)` — the **single** writer of the per-task estimate credit — short-circuits to a zero return (**no `credit_log` INSERT**) when `perTaskCredit` is false. Its `config` argument defaults to the resolved `rewardConfig()`; the sole live caller (`lifecycle` `awardLandingCredit`) omits it and gets the resolved instance policy per call.
29
+ - **The idea-promotion slice cascades automatically.** `awardLandingCredit` passes the (now `0`) `creditsAwarded` into `maybeAwardIdeaPromotionBonus`, which already no-ops on a non-positive base — so a fraction of an unpaid estimate is correctly `0`, with **zero lifecycle changes**.
30
+ - **The estimate is preserved as an advertised guide, not a payout.** `tasks.credits_reward` still rides the row: it is shown at claim time and still drives the no-zero-reward claim gate (ADR 0096). Only the *payout* is suppressed.
31
+ - **Fail-safe default.** An unknown / garbage `mode` resolves to the default (pay both). A config typo can never silently zero out every builder's per-task reward.
32
+
33
+ **Cloud Bongos selects `cost-plus-only`.** Other instances (including the reference founder instance) keep the default and are untouched.
34
+
35
+ ## What this does NOT change
36
+
37
+ - **The cost-plus session reward** — untouched. Same formula, same `marginPct`/`usdPeg`, same `session.token_reward` path. In `cost-plus-only` mode it is simply the only equity stream.
38
+ - **Achievements and the net-negative (+1 simplification) ship bonus** — deliberately **out of scope**. They are a separate, small behavioral-nudge category (badges + a symbolic +1), not the per-task estimate. If a future owner wants literally *nothing but* cost-plus, gating those is a one-line follow-up; this ADR does not.
39
+ - **The `KIND_MULTIPLIERS` table and `credits_reward` semantics** — unchanged. The multiplier is still computed (for the return shape / audit) even when the payout is withheld.
40
+
41
+ ## Consequences
42
+
43
+ - **Backward-compatible by construction.** Absence of `mode` ⇒ default ⇒ current behavior. No migration, no data change.
44
+ - **Reversible.** Flipping `mode` back to the default resumes per-task payouts from the next ship; nothing is destroyed. (Historical rows are untouched — this is not a clawback; see [ADR 0120](<redacted>.md) part 4 for the separate one-time backlog re-gate.)
45
+ - **Builder-facing expectation gap (follow-up).** ~~In `cost-plus-only` mode the "Est." shown at claim time is a *guide*, not a promise — the builder is actually paid the cost-plus amount. Surfacing "this instance pays cost-plus only" in the hall / claim UI is a captured follow-up idea, not part of this task.~~ **Resolved by task [#1002469](https://cloudbongos.com/builders#/task/1002469).** The gap was not theoretical: it cost a real session on 2026-07-26, where a correct `Credits +0` was diagnosed as a broken reward gate. The resolved policy is now exposed on the `reward` port (`rewardConfig`), projected onto `clientBranding()` for the hall and returned on `GET /me` + `POST /claims` for the CLI — **the CLI must take it from the server**, since an instance's `config/branding.json` is not in a builder's checkout and a local read always reports the paying default. Claim, `/builder-start`, the ship card, and the hall now label the estimate as an estimate and name the real payer. The hall fix also closed a live misread: `taskCreditValue` falls back to the estimate when a settled task has `credits_awarded === 0` — which under `cost-plus-only` is *every* shipped task — so the plain label was presenting an unpaid estimate as the actual ledger payout.
46
+ - **The two-conventions tension is resolved for `cost-plus-only` instances** (one dollar-denominated stream) and left intact for default instances — the ADR 0054 trigger is addressed by *config*, not by forcing one model on everyone.
47
+
48
+ ## Alternatives considered
49
+
50
+ - **A boolean `reward.perTaskCredit`** instead of a `mode` enum. Rejected: `"cost-plus-only"` reads self-documentingly at the config site and in prose (it is the vocabulary the decision was made in), and the enum extends cleanly if a third policy is ever wanted. The code derives the boolean internally.
51
+ - **Gate in the lifecycle (`awardLandingCredit`) via a port predicate.** Rejected: it would spread economy policy across the kernel↔module boundary. The knob belongs *inside the economy module* (the owner's explicit ask); gating the single credit writer keeps it there and needs no lifecycle change.
52
+ - **Remove `credits_reward` / stop advertising an estimate entirely.** Rejected: the estimate is still useful as a claim-time guide and is load-bearing for the no-zero-reward claim gate. Suppress the *payout*, keep the *signal*.
53
+ - **Make the cost-plus reward the only stream globally (no knob).** Rejected: the core ships to instances that may legitimately want the bounty-style per-task reward. A per-instance knob with a backward-compatible default respects the core ↔ host split (ADR 0062 §7).
@@ -0,0 +1,39 @@
1
+ # ADR 0147 — Carry ADR 0110's deploy-regen of generated docs to STANDALONE instances
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-07-17
5
+ - **Track:** internal
6
+ - **Builds on:** [ADR 0110](<redacted>.md) (untrack + deploy-regen the whole-file nav docs), [ADR 0108](<redacted>.md) (standalone instance = own repo + pinned `@bongos/core`), [ADR 0062](<redacted>.md) §8 (`gen-session-index.js`), [ADR 0063](<redacted>.md) (`gen-repo-map.js`)
7
+ - **Task:** [#1002314](https://cloudbongos.com/builders#/task/1002314)
8
+
9
+ ## Context
10
+
11
+ The owner's report: *"All of the docs in cloudbongos.com did not transfer well from primea. Make sure any new project is also set up with live-updating docs."*
12
+
13
+ The generated navigation docs (`repo-map.md`, `session-log-index.md`, `file-map.md`, the onboarding diagrams, the API reference) stay fresh on the **example monorepo** because ADR 0110's regen-on-deploy is wired into `scripts/deploy/deploy-prod.sh` + `infra/box-source-fetch.sh`. **STANDALONE instances (ADR 0108) never got that step.** A standalone instance is its own lean repo with `@bongos/core` vendored in `node_modules`; it deploys via a bespoke pull script and updates the core via `bongos upgrade`. Two concrete defects left its `docs/` frozen and nearly empty (cloudbongos.com had only the 3 API-doc files):
14
+
15
+ 1. **`gen-session-index.js` hardcoded the core root** — `REPO_ROOT = path.resolve(__dirname,'..','..')`, the exact ADR 0108 configurable-root bug `gen-repo-map.js`/`gen-file-map.js` warn against. On a standalone instance it read/wrote `node_modules/@bongos/core/docs/session-logs`, never the instance's own sessions. It was left behind in the configurable-root migration.
16
+ 2. **No standalone regen-on-deploy path.** The bespoke pull-deploy (`git reset --hard origin/main` → migrate → restart) regenerated *nothing*; `bongos init` + `bongos upgrade` only regenerated the API docs (`regenerateApiArtifacts`). So the whole-file nav docs were never seeded and never refreshed on a standalone box.
17
+
18
+ There is no `scripts/deploy/` in the neutral core — `deploy-prod.sh` is the monorepo's, not something a standalone instance runs.
19
+
20
+ ## Decision
21
+
22
+ Carry ADR 0110's model to the standalone path — the standalone analogue of the `deploy-prod.sh` regen block.
23
+
24
+ 1. **Fix the root bug.** `gen-session-index.js` resolves `REPO_ROOT = resolveInstanceRoot()` (mirrors `gen-file-map.js`), so it indexes the instance's own `docs/session-logs/` and writes the instance's `docs/session-log-index.md` + the instance-root `CLAUDE.md` §13 snippet. On the monorepo/at-ship it still resolves via the cwd fallback.
25
+ 2. **A shared regen seam.** `upgrade.regenerateNavDocs({instanceDir, coreRoot})` runs the whole-file nav generators against the instance root, best-effort + LOUD (a broken generator prints `::ERROR::` but never fails the deploy/bump — ADR 0110 §2 "accepted staleness"). A thin CLI, `scripts/gds/regen-instance-docs.js`, is the one-liner a standalone deploy calls.
26
+ 3. **Wired at every refresh point.** `bongos init` seeds it at standup; `bongos upgrade` refreshes it on a core bump; `provision.js`'s standalone deploy runs it after migrate; a bespoke pull-deploy (e.g. cloudbongos.com's `deploy.sh`) calls the CLI after `git reset --hard` + migrate.
27
+ 4. **Gitignored on the instance.** `provision.js`'s `INSTANCE_GITIGNORE` ignores `docs/session-log-index.md` (+ `docs/repo-map.md` for parity), so `git reset --hard` never wipes the regenerated file and no branch can ever diff it — the same reasoning ADR 0110 applies on the monorepo.
28
+
29
+ **Scope — deliberately `session-index` only.** It is the one *gitignored, whole-file, instance-content* nav doc. Explicitly excluded:
30
+
31
+ - **`repo-map.md`** — CORE-scoped: `gen-repo-map` writes to `resolveCoreRoot()` (the platform-code map), not the instance. A pure standalone instance runs the core from `node_modules` and has ~no code of its own to map, so an instance-level repo-map is empty/meaningless; the core ships its own. An instance-scoped map for **brownfield/adopted** repos (which *do* carry their own code) is a separate enhancement (task 1001655).
32
+ - **`file-map.md` + the API docs** — TRACKED/committed (seeded at init, refreshed at ship/upgrade). Regenerating them on the box would perpetually dirty the tree that `deploy.sh` `git reset --hard`es. `file-map` is also HYBRID (hand-written prose + generated sections) so it cannot leave git; **seeding a fresh instance's `file-map.md` at standup is a follow-up** (it needs a scaffolded `file-map.md` skeleton with the section markers + a `file-map.notes.json`).
33
+ - **Diagrams** — the headless-browser + authed-GDS-session exception (same as ADR 0110 / task 1001641). They do not migrate to the box; they are rendered at package/CI time and shipped.
34
+
35
+ ## Consequences
36
+
37
+ - A freshly-init'd standalone instance seeds `session-log-index.md`, and its deploy + `bongos upgrade` keep it live against the *instance's* sessions — the "docs didn't transfer" gap is closed for every new project, not just cloudbongos.com.
38
+ - **Accepted staleness (inherited from ADR 0110 §2):** the served whole-file doc is fresh only after a deploy/upgrade, not at merge — tolerable for a navigation index. A generator that breaks leaves a stale/absent file with no *blocking* backstop; the `::ERROR::` line is the alarm.
39
+ - **Follow-ups:** (a) seed `file-map.md` at standup (scaffold skeleton + notes); (b) an instance-scoped `repo-map` for brownfield/adopted repos (task 1001655); (c) productize a continuous pull-deploy timer for standalone instances (today only cloudbongos.com has a bespoke one) so content-only commits redeploy without a manual pull.
@@ -0,0 +1,48 @@
1
+ # 0148 — Task-scoped box source access (rank-scope → claim-scope)
2
+
3
+ - **Status:** Accepted (spike R90 / task 1002378 — the architecture pass for goal 1000051). Confirms the build shape of R92–R95.
4
+ - **Date:** 2026-07-18
5
+ - **Deciders:** Lars (owner) — "claim a task → the box pulls only that module's code, on a base Bongos instance; the GitHub token stays on the server." Corrected the earlier "bring-your-own-GitHub" framing.
6
+ - **Extends:** ADR 0031 §6 (box source-access, task 600), ADR 0086 (goals + `scope_modules` scope wall), ADR 0093 (the `lifecycle` port), ADR 0052 (sandbox-for-everyone starter scope). Spec: [`docs/specs/<redacted>.md`](../specs/<redacted>.md).
7
+
8
+ ## Context
9
+
10
+ A builder's dev box already pulls **scoped** source from the control plane — the GitHub token stays server-side (`box-credential.js`), the box calls `GET /box/source-access` authenticated as the builder, and **git cone-mode sparse-checkout** materializes only the returned folders (`infra/box-source-fetch.sh`, cron `*/10`). Today the scope is a coarse **rank** bucket: `boxScopeForRank(rank)` → `full` (Metic+, whole repo) or `starter` (a fixed `STARTER_SPARSE_PATHS` slice). We want the box to instead carry **the code the builder's claimed task needs**, on top of a base instance — moving the scope from *rank* to *task/module*.
11
+
12
+ The question this spike answers: **how does `box-access` derive the sparse paths from the builder's active claim(s) without breaking the dev-box↔core module boundary**, and what exactly do R92–R95 build.
13
+
14
+ ## Decision
15
+
16
+ **Scope = `BASE ∪ (union of the builder's active claims' goals' `scope_modules`, mapped to globs)`.** The claimed task's **goal** is the unit of "what code this work needs" — its `scope_modules` is the already-declared module footprint (the ADR 0086 scope wall), so we reuse it rather than inventing a new per-task field. Union across active claims (owner decision). The transport (`/box/source-access` → `cloneSpecForScope` → sparse-checkout) is unchanged; only *what scope is computed* changes.
17
+
18
+ **The seam — four small pieces, each already has a home:**
19
+
20
+ 1. **The map is already on the doorway.** `src/module-api.js` exposes `moduleScopeMap` (since doorway `1.12.0`, BV1.R86). So `box-access` reads `api.moduleScopeMap.scopeForModule(key).globs` directly — **no new kernel edit**. The only change is dev-box's `module.json` `coreVersion` `^1.2.0` → `^1.12.0` (core is 1.18.x; satisfied).
21
+
22
+ 2. **The claims read is a small lifecycle-port add.** The `lifecycle` port already exposes `<redacted>`; add a sibling **`<redacted>(builderId)`** that JOINs `claims → tasks → goals` for the builder's active claims and returns the **set-union of `goals.scope_modules`** (empty array when no active claim). dev-box resolves it via `seams.resolveOptional('lifecycle')` — the ADR 0093 module-consumes-port pattern — and adds `lifecycle` to its `consumes` (optional; a vanilla instance without it degrades gracefully to the rank fallback).
23
+
24
+ 3. **`box-access` gains a pure scope→paths function.** `sparsePathsForScopeKeys(scopeKeys, moduleScopeMap)` = `BASE_SPARSE_PATHS ∪ ⋃ directoryGlobs(scopeForModule(key))`. `decideSourceAccess({rank, status, scopeKeys})` gains the optional `scopeKeys`: **present → task-scope spec; absent/empty → base-only; no lifecycle port → the existing rank fallback.** Rank still governs ALLOW/DENY and the provision floor (a below-floor/inactive builder is still 403); rank no longer picks the module *breadth* once a claim drives it.
25
+
26
+ 4. **The route resolves + passes.** In `GET /box/source-access` (which already reads `gdsDb.getBuilderById` via the doorway), resolve the lifecycle port, `const scopeKeys = await lifecycle?.<redacted>(req.builder.id)`, and pass it into `decideSourceAccess`. One added read on an existing handler.
27
+
28
+ **`BASE_SPARSE_PATHS`** (the "pre-existing Bongos instance" — always present so Claude has context + can run `/builder-*`): the non-product subset of today's `STARTER_SPARSE_PATHS` — `.devcontainer`, `.claude`, `scripts`, `src/bongos`, `docs`, `infra` (+ top-level files, auto by cone mode). Task-module globs layer on top.
29
+
30
+ ### The cone-mode constraint (load-bearing)
31
+
32
+ Git **cone-mode** sparse-checkout accepts only **directory** entries — a file path aborts the whole `sparse-checkout set` (already documented in `box-access.js`). Module `scope_modules` → globs are of two shapes:
33
+ - **Directory globs** (`modules/dev-box/`, `devbox-app/`, `modules/onboarding/`, `modules/hall-ui/`) — cone-friendly, added directly.
34
+ - **File-list globs** (the `kernel` key = individual `src/bongos/*.js` files) — NOT cone-friendly, BUT their parent `src/bongos` is already in `BASE`, so a `kernel`-scoped task needs no extra sparse entry.
35
+
36
+ So R93 filters task globs to directory entries; file-list keys are covered by base. (A future move to non-cone sparse patterns could pull individual files, but it is out of scope — cone mode stays.)
37
+
38
+ ## Consequences
39
+
40
+ - **Confirmed R92–R95 shapes** (the spike's deliverable):
41
+ - **R92** — dev-box `module.json` `coreVersion` → `^1.12.0` + `consumes:['lifecycle']`; add `lifecycle` port read `<redacted>(builderId)` (db.js query + `lifecycle.js` delegation + a DB-integration test). *Smaller than planned — the map is already exposed.*
42
+ - **R93** — `box-access.js`: `BASE_SPARSE_PATHS`, pure `sparsePathsForScopeKeys()`, `decideSourceAccess({…, scopeKeys})` with the three-way branch; unit tests (0/1/N keys, cone-filter, fallback).
43
+ - **R94** — `routes/box.js`: resolve lifecycle + pass `scopeKeys`; graceful when the port is absent.
44
+ - **R95** — folded into R93's pure decision: 0 claims → base-only; N claims → base ∪ all N goals' scopes. Kept as its own test-focused task.
45
+ - **Behavior change to flag:** an **idle Metic+** (no active claim) now gets **base-only**, not the whole-repo `full` clone they get today. That is the intent (the box is task-scoped), but it is a regression for a Metic+ who relied on an idle box holding everything. Preserving idle full-access would be a config toggle — **deferred**, not built here.
46
+ - **Security posture unchanged (C4):** the credential is still server-issued per fetch from `box-credential.js`; the box never stores the GitHub token durably. This spike does not touch the token model. The deferred hardening (short-lived per-claim credential + hard "can't over-pull" enforcement) is unaffected and still deferred — task-scope here is what the box *asks for*, not yet an *enforced* ceiling.
47
+ - **C4 verified (R102 / task 1002390):** the server-side-token posture is pinned by [`tests/box_credential.mjs`](../../tests/box_credential.mjs) (7 assertions): the credential is sourced only from the prod env (unset → `configured:false`, no fabricated token — ADR 0022); the secret lands in **no** field but `.token` (so the `source_grant` audit — now `scope=<sparse> cred=<rank> mode=…` — carries no secret); the credential scope stays **rank**-based (a separate `BOX_SOURCE_STARTER_REPO_URL` is a hard boundary that only `full` crosses, and an unexpected `task`/`base` label fail-closes to the non-full repo, never the crown jewels); the **claim-scoped `decideSourceAccess` decision carries no secret** (the token is minted *separately* by `box-credential.js`, so changing the sparse breadth never touches the token model); and an inactive/below-floor builder is denied **before** any credential is issued (instant, credential-layer revocation). The **deferred hardening** — a genuinely short-lived, per-claim-*minted* credential plus a hard "can't over-pull" isolation (physically separate per-task materialization) — remains explicitly OUT of scope for this goal; task-scope is the box's *ask*, and the token model is unchanged.
48
+ - **No new migration, no kernel/doorway change, no protected-path edit** beyond the additive lifecycle-port read — R92–R95 all sit inside the `dev-box` + `lifecycle` surface the goal is already scoped to (plus the R91 scope-map prep for the separate C2 chain).
@@ -0,0 +1,57 @@
1
+ # 0149 — `bongos dev`: a one-command local launcher for a standalone instance
2
+
3
+ - **Status:** Accepted (owner decision — "we need a better option to stand up the local host").
4
+ - **Date:** 2026-07-19
5
+ - **Deciders:** Lars (owner) — chose a one-command local launcher over a cloud dev box or a recipe-only fix.
6
+ - **Extends:** [ADR 0108](<redacted>.md) (a standalone instance repo pins the core as a dependency), [ADR 0062 §5](<redacted>.md) (the core owns its own pool + branding). Sibling of `local-preview-lib.js` (task 1056 — the game-only local sandbox) and `onboard`/`provision.js` (the *cloud* standup path).
7
+
8
+ ## Context
9
+
10
+ The project-creation flow scaffolds a repo whose `config/branding.json` points at `http://localhost:<port>` (each new project gets its own auto-incremented port — the trial produced test3→`:3005`, test4→`:3006`). The repo is **configured** to run on the owner's own machine, but the platform shipped **no command that actually starts it there**.
11
+
12
+ An owner who cloned a fresh project hit a wall (session export, 2026-07-19): `npm install` works, but then standing the instance up by hand is a six-part assembly — install + start Postgres, `createdb`, run migrations, export `PORT`/`PGDATABASE`/`PGHOST`/OAuth env, launch the server — before `bongos login` can even connect. The guided command that *does* exist, `bongos onboard`, provisions a **cloud** server (needs a public domain + DigitalOcean/Cloudflare tokens); the Docker path (`docs/recipes/self-host.md`) runs a **vanilla** brand on `:3000`, not the repo's brand on its own port. So *local-on-your-Mac* — exactly what a freshly-created project is configured for — was the one unsupported path.
13
+
14
+ Three silent mismatches made the manual path especially error-prone:
15
+
16
+ 1. **Port.** `src/platform-server.js:44` reads `PORT` from env and **ignores branding** — so the server comes up on `3000` while branding (and every printed link) says `:3006`.
17
+ 2. **DB name.** The server pool falls back to the OS username (`src/bongos/pool.js:47` via `instanceDbName()`, which is `null` in a fresh pack), while `scripts/migrate.sh:52` defaults to `example`. They only agree if `PGDATABASE` is set for **both**.
18
+ 3. **Socket.** node-pg defaults to the Linux peer socket `/var/run/postgresql` (`pool.js:46`), which does not exist under Homebrew Postgres on a Mac — you must set `PGHOST`.
19
+
20
+ A fourth failure was operational: the manual server ran **unmanaged in the background**, so a stale server for one project (`:3005`) was still answering when the owner tried the next project — they signed into the wrong instance by accident.
21
+
22
+ ## Decision
23
+
24
+ Ship **`bongos dev`** — one idempotent command that stands up *this* instance locally and hands off to sign-in.
25
+
26
+ ```
27
+ bongos dev up: Postgres → createdb → migrate → server → sign-in
28
+ bongos dev stop stop the background server this launched
29
+ bongos dev status is it up? what URL? is sign-in configured?
30
+ ```
31
+
32
+ `bongos dev` (default) runs, in order:
33
+
34
+ 1. **Preflight** — `psql`/`createdb`/`pg_isready` present (clear `brew install` guidance if not); `bash` present for migrations.
35
+ 2. **Postgres up** — if not running, start it (Homebrew `postgresql@N`, else `pg_ctl` with `PGDATA`), poll until ready.
36
+ 3. **`createdb`** the instance's database if absent (migrate.sh does not create it).
37
+ 4. **Migrate** — run the core `migrate.sh` with `MEDUSA_GDS_ONLY=1` (a fresh local instance never wants OTB's instance seeds).
38
+ 5. **Start the server** DETACHED but **managed** (pid/log keyed per instance), poll `/healthz` + `/api/gds/healthz`.
39
+ 6. **Sign in** — if creds resolve and the machine isn't already signed in to this instance, run `bongos login <origin>`; otherwise print the exact command (`--no-login` skips).
40
+
41
+ **It resolves the three mismatches from one source of truth (branding):** `PORT` = the port in `domains.publicOrigin`; `PGDATABASE` = `db.database` → repo dir name → `cloudbongos_dev`, exported for **both** migrate and the server; `PGHOST` defaults to TCP `localhost` (an explicit env override still wins). The pure resolution + process management live in `scripts/gds/dev-lib.js` (fully injectable, unit-tested in `tests/dev_lib.mjs`); the side-effecting orchestration is `scripts/gds/dev.js`.
42
+
43
+ **Managed-not-orphaned** is the fix for the wrong-instance accident: the background server has a pid/log keyed per `(dbName, port)`, so `bongos dev status`/`stop` make it visible and killable and a re-run is a no-op. `--foreground` runs it attached (Ctrl-C lifecycle) for those who prefer it.
44
+
45
+ **The one irreducibly-manual step** — a GitHub OAuth App — is only surfaced when no creds resolve (env `<PREFIX>_GITHUB_CLIENT_ID/SECRET` or `~/.config/<configDir>/gds-github.json`, mirroring `auth.js:loadGithubCreds`). A machine that already ran another instance carries usable creds and skips it entirely.
46
+
47
+ ## Alternatives considered
48
+
49
+ - **Cloud dev box** (`bongos box` + `bongos code`) — the platform's blessed remote-dev model, but it needs the project *hosted* first and adds cloud cost; overkill for "try this project on my Mac." Rejected by the owner for the local case (still the right answer for remote/multi-machine work).
50
+ - **Docker** (`self-host.md`) — real, but serves the *vanilla* brand on `:3000`; matching the repo's brand + port needs a `docker-compose.override.yml` and a port change, and Docker Desktop is heavy for a non-technical owner. Kept as the Windows / container path.
51
+ - **Recipe only** (no command) — fastest, but leaves the six-step assembly and the three mismatches in the owner's hands every time. Rejected; the recipe ([`docs/recipes/local-dev.md`](../recipes/local-dev.md)) now just documents `bongos dev`.
52
+
53
+ ## Consequences
54
+
55
+ - A freshly-created, localhost-configured project goes from `git clone` to a running, signed-in instance with `npm install` then `bongos dev` — no manual DB/env/port work.
56
+ - Windows is not covered by the native path (migrations need `bash`); those users take the Docker path. Noted in help + the recipe.
57
+ - `bongos dev` runs a repo verb (needs the installed core), so it is **not** bundled into the standalone on-ramp binary — it needs a real checkout + `npm install`, which the flow already has.
@@ -0,0 +1,68 @@
1
+ # 0150 — Box first-boot bring-up on vendored instances: resolve `infra/` from the instance root, fail loud on empty user-data
2
+
3
+ - **Status:** Accepted (spike BV1.R97 / task 1002385 — the C2 diagnosis for goal 1000051 / 1000052). Confirms the build shape of R98 (task 1002386) + R99 (task 1002387).
4
+ - **Date:** 2026-07-19
5
+ - **Deciders:** Lars (owner) — "continue with C2" (make cloudbongos boxes stop coming up bare). Claude diagnosed + designed the fix.
6
+ - **Extends:** [ADR 0144](<redacted>.md) (its first follow-up item root-caused this), [ADR 0108](<redacted>.md) (the `resolveCoreRoot()`/`resolveInstanceRoot()` split this fix rides), [ADR 0031](<redacted>.md) (box lifecycle), [ADR 0148](<redacted>.md) (task-scoped source access — which only matters once a box actually boots).
7
+
8
+ ## Context
9
+
10
+ "cloudbongos boxes come up bare" — a freshly provisioned box reaches `active` but has **no clone, no toolchain, no browser terminal** (`term-<login>` returns Cloudflare 530). The task-scoped source-access mechanism (ADR 0148, C1) is irrelevant until a box actually boots with the base instance present — so C2 is the practical foundation.
11
+
12
+ There are two provisioning paths in the fleet's history:
13
+
14
+ - **The reference (example) path** — `scripts/gds/box.js` renders a DigitalOcean cloud-init from `infra/builder-box-cloud-init.yaml` (+ ~12 gz+b64-inlined scripts: `box-source-fetch.sh`, the cloudflared/ttyd units, `box-heartbeat.sh`, …). The cloud-init is what clones the source, installs the toolchain, and starts the terminal. A box is "non-bare" **because** its user-data ran.
15
+ - **The cloudbongos path** ([ADR 0144](<redacted>.md)) — native `cloudbongos-box-*` runners against the local DB, but the **same `box.js`** renders the same cloud-init.
16
+
17
+ ## Root cause (code-grounded + live-confirmed)
18
+
19
+ The box boots bare because **`box.js` renders EMPTY user-data and no one notices**:
20
+
21
+ 1. `box.js` sets `REPO_ROOT = path.resolve(__dirname, '..', '..')` ([`scripts/gds/box.js:47`](../../scripts/gds/box.js)). On a **vendored** instance `box.js` lives at `node_modules/@bongos/core/scripts/gds/box.js`, so `REPO_ROOT` resolves to the **vendored-core package root**.
22
+ 2. The R89 packaging split moved `scripts/gds/` **into** `@bongos/core` but **kept `infra/` as instance content** — `infra/` is **not** in the core publish-manifest, so `node_modules/@bongos/core/infra/` **does not exist**.
23
+ 3. `readCloudInit()` reads the template at `REPO_ROOT/infra/builder-box-cloud-init.yaml` ([`box.js:258`](../../scripts/gds/box.js)). Missing dir → `readFileSync` **throws** → the surrounding `try { … } catch { return null }` ([`box.js:313`](../../scripts/gds/box.js)) swallows it → **`readCloudInit` returns `null`**.
24
+ 4. The caller treats `null` as *zero bytes, fine*: `const userDataBytes = userData ? … : 0` then `if (userDataBytes > MAX_USERDATA_BYTES) fail()` ([`box.js:453-455`](../../scripts/gds/box.js)). `0 > 64KiB` is false, so the **only** size guard passes and provisioning proceeds with empty user-data. DigitalOcean logs "No user-data found"; the box is bare Ubuntu.
25
+ 5. Even a **partial** `infra/` half-wires a box: `fileGzB64()` ([`box.js:268-275`](../../scripts/gds/box.js)) warns on a missing inlined file but still **returns `''`** (fail-soft), so the placeholder is substituted with nothing and that component silently never boots.
26
+
27
+ **Live re-confirmation (2026-07-19, on the cloudbongos droplet):**
28
+
29
+ - `infra/` **is present at the instance root** — `/home/lars/cloudbongos/infra/builder-box-cloud-init.yaml` exists.
30
+ - `infra/` **is absent in the vendored core** — `/home/lars/cloudbongos/node_modules/@bongos/core/infra/` is gone. The ADR 0144 stopgap (hand-copying `infra/` into `node_modules`) was **wiped by a core reinstall** since 2026-07-15, exactly as that ADR warned.
31
+ - `bongos.config.json` at the instance root already declares `{"instanceRoot": "."}` — so `resolveInstanceRootExplicit()` deterministically resolves to `/home/lars/cloudbongos`, where the real `infra/` lives.
32
+ - Fleet is idle (1 box, `destroyed`), so the bug is **latent but live**: nothing is broken this instant, but the **next** provision boots bare.
33
+
34
+ So the failure is a **packaging-locality bug**, not a logic bug: `box.js` looks for `infra/` in the one place it isn't (the core package) instead of the one place it is (the instance root).
35
+
36
+ ## Decision
37
+
38
+ ### R98 — make the box clone the base instance on boot
39
+
40
+ **Prong A — resolve `infra/` from the instance root** (ride [ADR 0108](<redacted>.md), exactly like [`scripts/migrate.sh:35`](../../scripts/migrate.sh) resolves its roots). A new `resolveBoxInfraDir()` in `box.js` returns the first candidate that actually contains `builder-box-cloud-init.yaml`, in order:
41
+
42
+ 1. an explicit `<PREFIX>_BOX_INFRA_DIR` env override (escape hatch);
43
+ 2. `resolveInstanceRootExplicit()/infra` — the env / `bongos.config.json` pointer (cloudbongos: `{"instanceRoot":"."}` → `/home/lars/cloudbongos/infra`);
44
+ 3. `REPO_ROOT/infra` — the dev single-checkout case (and the old node_modules workaround location, for continuity);
45
+ 4. a bounded walk **up out of `node_modules`** from the core root, so a vendored `…/node_modules/@bongos/core` finds its host instance root even with no pointer set.
46
+
47
+ All `infra/` reads (`readCloudInit`'s template + every `fileGzB64`) route through this resolved dir. **The neutral core still ships no `infra/`** — this changes only how `box.js` *locates* instance-owned infra, not what the core packages.
48
+
49
+ **Prong B — fail loud, never provision empty.** Two backstops so a bare box can never be created silently again:
50
+
51
+ - `readCloudInit()` **throws** an actionable error when no `infra/` dir with the template is found (stop returning `null`);
52
+ - `cmdProvision` aborts with `fail(...)` if the rendered user-data is empty / 0 bytes, naming the candidate dirs searched and pointing at this ADR — **before** touching DigitalOcean.
53
+
54
+ (Optionally escalate `fileGzB64` to hard-fail for the load-bearing inlined files, or assert no `__…_B64__` placeholders survive the render, so a *partial* `infra/` also surfaces rather than half-wiring.)
55
+
56
+ ### R99 — toolchain builds on the box
57
+
58
+ Once `infra/` resolves and `box-source-fetch.sh` runs on first boot, the base instance clones. R99 then ensures the **runtimes** are present so a pulled module is actually buildable/runnable by Claude on the box: verify the cloud-init (or the devcontainer/`box-source-fetch` bring-up) installs Node + the common build tools, and that on a fresh box a pulled module's `npm ci` / build / test commands run. Concrete scope is R99's own; this ADR only fixes *getting the code + bootstrap onto the box*.
59
+
60
+ ## Consequences
61
+
62
+ - **Reinstall-proof.** The fix reads the instance's own `infra/` (or an explicit pointer), so a core `npm ci` / `bongos upgrade` can never wipe it — the fragile ADR 0144 workaround-in-`node_modules` is retired.
63
+ - **Portable.** Every vendored instance benefits: `box.js` stops assuming `infra/` ships inside the core package. This is a **core** fix (lands on core `main`), not a cloudbongos-only patch.
64
+ - **Fail-loud tradeoff (intended).** A genuinely infra-less instance can no longer provision a box — it errors clearly instead of creating a useless bare one. A bare box is worse than a legible failure.
65
+ - **Go-live gap.** The fix lands on core `main`; it reaches cloudbongos.com only via the separate release (package-core + `bongos upgrade`). The fleet is idle today, so nothing is broken this instant; the next box provision needs the fix live (or the `node_modules` workaround re-applied as a one-off) — flagged to the owner in the session handoff.
66
+ - **No new migration, no doorway/kernel change.** The fix is contained to `scripts/gds/box.js` + a unit test; it reuses the existing `src/instance-config.js` resolvers.
67
+ - **R99 landed (task 1002387) — toolchain + test-runnability.** Core-side audit: the `.devcontainer` toolchain is complete (Node 22 via features + `build-essential`/`pkg-config`/Python 3.12 in the Dockerfile, with a build-time integrity healthcheck); the root `package.json` declares **no npm workspaces**, so a task-scoped *sparse* checkout can't break `npm ci`; and the task-scoped BASE already carries the top-level `package.json` + lockfile (cone-mode ancestor inclusion), so `npm ci` has what it needs. The one real gap: a module's **tests live in the top-level `tests/` dir** (`tests/box_*.mjs` + `tests/helpers.mjs`), which was in **neither** `BASE_SPARSE_PATHS` nor `STARTER_SPARSE_PATHS` — so a task-scoped or starter box pulled the module source but **not its tests**, and "a pulled module's build/test commands run" failed. Fix: add `tests` to both scope lists (`modules/dev-box/box-access.js`) — a cone-mode dir carrying only test code, no product runtime. Pinned by the updated `tests/box_access.mjs` + `tests/box_task_scope.mjs`. **Deferred to the live proof (R104, task 1002392):** the end-to-end "fresh box → `npm ci` → a pulled module's build/test actually runs" on the cloudbongos plane — the toolchain *install* rides the instance-only cloud-init (now reachable thanks to R98) and provisioning a box is owner-gated (spends money), so the live demonstration belongs to the C5 proof, not this core change.
68
+ - **R98 landed (task 1002386).** `scripts/gds/box.js` gained `boxInfraCandidates()`/`resolveBoxInfraDir()` (the ordered instance-root-first search) + `BOX_INFRA_TEMPLATE`; `readCloudInit` now routes every `infra/` read through the resolved dir and **throws** on a missing/incomplete `infra/` instead of returning `null`; `cmdProvision` resolves the source up front (dry-run + apply, printing `cloud-init source: <dir>`), catches the throw into a clean `fail()` with box state `error`, and refuses to provision on empty user-data. Pinned by [`tests/box_infra_resolve.mjs`](../../tests/box_infra_resolve.mjs) (8 assertions: candidate ordering incl. the node_modules walk-up, first-with-template selection, `null` when none, and the two fail-loud throws). Backward-compatible: a dev single-checkout still resolves `REPO_ROOT/infra`.