@bongos/core 1.18.3 → 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 (469) hide show
  1. package/.bongos-core.json +1575 -285
  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 +4 -2
  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 +6 -1
  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/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
  27. package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
  28. package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
  29. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
  30. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
  31. package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
  32. package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
  33. package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
  34. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
  35. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
  36. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
  37. package/docs/adr/0152-metic-task-abandonment.md +67 -0
  38. package/docs/adr/0153-sequential-chain-runner.md +51 -0
  39. package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
  40. package/docs/adr/README.md +10 -0
  41. package/docs/api/openapi.json +843 -17
  42. package/docs/api-reference.md +23 -10
  43. package/docs/architecture.md +4 -2
  44. package/docs/branding-contract.md +3 -1
  45. package/docs/canonical-permissions.md +21 -8
  46. package/docs/file-map.md +8 -0
  47. package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
  48. package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
  49. package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
  50. package/docs/onboarding/diagrams/assertions.json +4 -7
  51. package/docs/onboarding/drachmae-and-karma.md +3 -3
  52. package/docs/onboarding/primer.md +17 -16
  53. package/docs/onboarding/slash-commands.md +124 -0
  54. package/docs/recipes/builder-box-lifecycle.md +23 -0
  55. package/docs/recipes/devbox-app-release.md +14 -4
  56. package/docs/recipes/instance-first-run.md +16 -0
  57. package/docs/recipes/local-dev.md +81 -0
  58. package/docs/recipes/ops-gotchas.md +19 -1
  59. package/docs/recipes/overnight-builder.md +1 -1
  60. package/docs/recipes/parallel-goal-waves.md +174 -0
  61. package/docs/recipes/ship-and-regrade.md +2 -2
  62. package/docs/recipes/standalone-live-docs.md +48 -0
  63. package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
  64. package/migrations/003_pms.sql +6 -7
  65. package/migrations/006_pms_v2.sql +3 -31
  66. package/migrations/core_193_task_needs_rebase.sql +28 -0
  67. package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
  68. package/migrations/core_195_idea_triage_provenance.sql +57 -0
  69. package/migrations/core_196_session_reward_topup.sql +69 -0
  70. package/modules/autonomy/pollers/routine-timers.js +9 -8
  71. package/modules/builder-settings/wandering-prefs.js +61 -18
  72. package/modules/dev-box/app/README.md +73 -0
  73. package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
  74. package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
  75. package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
  76. package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
  77. package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
  78. package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
  79. package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
  80. package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
  81. package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
  82. package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
  83. package/modules/dev-box/app/assets/tray/win-off.png +0 -0
  84. package/modules/dev-box/app/assets/tray/win-on.png +0 -0
  85. package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
  86. package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
  87. package/modules/dev-box/app/build-resources/icon.png +0 -0
  88. package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
  89. package/modules/dev-box/app/electron-builder.yml +95 -0
  90. package/modules/dev-box/app/package-lock.json +5291 -0
  91. package/modules/dev-box/app/package.json +30 -0
  92. package/modules/dev-box/app/renderer/app.js +319 -0
  93. package/modules/dev-box/app/renderer/index.html +45 -0
  94. package/modules/dev-box/app/renderer/styles.css +246 -0
  95. package/modules/dev-box/app/renderer/theme.generated.css +16 -0
  96. package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
  97. package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
  98. package/modules/dev-box/app/scripts/gen-brand.js +138 -0
  99. package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
  100. package/modules/dev-box/app/scripts/gen-icons.js +286 -0
  101. package/modules/dev-box/app/scripts/vendor-client.js +23 -0
  102. package/modules/dev-box/app/src/box-controller.js +287 -0
  103. package/modules/dev-box/app/src/brand.generated.json +13 -0
  104. package/modules/dev-box/app/src/brand.js +16 -0
  105. package/modules/dev-box/app/src/config.js +19 -0
  106. package/modules/dev-box/app/src/gds-client.js +90 -0
  107. package/modules/dev-box/app/src/main.js +610 -0
  108. package/modules/dev-box/app/src/pairing.js +57 -0
  109. package/modules/dev-box/app/src/preload.js +29 -0
  110. package/modules/dev-box/app/src/session-store.js +58 -0
  111. package/modules/dev-box/app/src/ssh-setup.js +569 -0
  112. package/modules/dev-box/app/src/task-picker.js +70 -0
  113. package/modules/dev-box/app/src/tray.js +99 -0
  114. package/modules/dev-box/app/src/updater.js +55 -0
  115. package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
  116. package/modules/dev-box/app/test/unit.mjs +526 -0
  117. package/modules/dev-box/box-access.js +121 -4
  118. package/modules/dev-box/boxes.js +20 -0
  119. package/modules/dev-box/module.json +1 -1
  120. package/modules/dev-box/routes/box.js +87 -3
  121. package/modules/economy/credits.js +116 -25
  122. package/modules/economy/reward.js +8 -0
  123. package/modules/economy/streak.js +2 -1
  124. package/modules/governance/CLAUDE.md +15 -0
  125. package/modules/governance/catalog.js +419 -0
  126. package/modules/governance/config.js +174 -0
  127. package/modules/governance/db.js +125 -0
  128. package/modules/governance/migrations/governance_001_tables.sql +81 -0
  129. package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
  130. package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
  131. package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
  132. package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
  133. package/modules/governance/module.json +10 -0
  134. package/modules/governance/protected-surfaces.js +88 -0
  135. package/modules/governance/protected-surfaces.json +80 -0
  136. package/modules/governance/rank-roles.js +83 -0
  137. package/modules/governance/resolver.js +221 -0
  138. package/modules/governance/routes/governance.js +89 -0
  139. package/modules/governance/routes/role-sync.js +49 -0
  140. package/modules/governance/session-scopes.js +127 -0
  141. package/modules/governance/session-scopes.json +38 -0
  142. package/modules/hall-ui/public/atlas.css +117 -0
  143. package/modules/hall-ui/public/atlas.html +3 -114
  144. package/modules/hall-ui/public/brand-apply.js +34 -6
  145. package/modules/hall-ui/public/builders.js +187 -219
  146. package/modules/hall-ui/public/diagrams.html +2 -0
  147. package/modules/hall-ui/public/dom-utils.js +27 -0
  148. package/modules/hall-ui/public/drachmae.html +2 -0
  149. package/modules/hall-ui/public/drachmae.js +54 -1
  150. package/modules/hall-ui/public/gate.css +98 -0
  151. package/modules/hall-ui/public/gate.html +3 -95
  152. package/modules/hall-ui/public/goal-inbox.js +4 -1
  153. package/modules/hall-ui/public/goals-page.js +626 -114
  154. package/modules/hall-ui/public/goals.css +79 -1
  155. package/modules/hall-ui/public/goals.html +38 -11
  156. package/modules/hall-ui/public/hall-widgets.js +62 -52
  157. package/modules/hall-ui/public/harbor.css +123 -0
  158. package/modules/hall-ui/public/harbor.html +3 -120
  159. package/modules/hall-ui/public/index.html +11 -4
  160. package/modules/hall-ui/public/instances-panel.js +4 -1
  161. package/modules/hall-ui/public/modules.css +63 -0
  162. package/modules/hall-ui/public/modules.html +3 -60
  163. package/modules/hall-ui/public/new-project-wizard.js +7 -3
  164. package/modules/hall-ui/public/not-ready.html +2 -0
  165. package/modules/hall-ui/public/pair.css +73 -0
  166. package/modules/hall-ui/public/pair.html +3 -70
  167. package/modules/hall-ui/public/palette.js +302 -0
  168. package/modules/hall-ui/public/primer.css +13 -0
  169. package/modules/hall-ui/public/primer.html +2 -0
  170. package/modules/hall-ui/public/profile.html +2 -0
  171. package/modules/hall-ui/public/ranks.html +2 -0
  172. package/modules/hall-ui/public/roadmap.css +179 -0
  173. package/modules/hall-ui/public/roadmap.html +9 -172
  174. package/modules/hall-ui/public/roadmap.js +329 -316
  175. package/modules/hall-ui/public/sessions.css +16 -0
  176. package/modules/hall-ui/public/sessions.html +3 -13
  177. package/modules/hall-ui/public/settings.html +2 -0
  178. package/modules/hall-ui/public/settings.js +4 -4
  179. package/modules/hall-ui/public/shell.js +143 -18
  180. package/modules/hall-ui/public/style.css +231 -39
  181. package/modules/hall-ui/public/task-detail.js +347 -26
  182. package/modules/hall-ui/public/task.html +6 -1
  183. package/modules/hall-ui/public/watch.html +2 -0
  184. package/modules/hall-ui/public/work.html +8 -6
  185. package/modules/hall-ui/public/work.js +1 -1
  186. package/modules/ideas/inbox.js +61 -11
  187. package/modules/ideas/routes/inbox.js +23 -0
  188. package/modules/lifecycle/claim-eligibility.js +72 -0
  189. package/modules/lifecycle/db.js +519 -139
  190. package/modules/lifecycle/done-when.js +18 -3
  191. package/modules/lifecycle/github-push.js +19 -2
  192. package/modules/lifecycle/goal-advisory.js +114 -0
  193. package/modules/lifecycle/goal-authz.js +62 -0
  194. package/modules/lifecycle/lifecycle.js +9 -0
  195. package/modules/lifecycle/publish-reconciler.js +109 -6
  196. package/modules/lifecycle/routes/claims.js +68 -23
  197. package/modules/lifecycle/routes/done-when.js +110 -15
  198. package/modules/lifecycle/routes/goals.js +8 -38
  199. package/modules/lifecycle/routes/tasks.js +38 -6
  200. package/modules/lifecycle/ship-card.js +6 -0
  201. package/modules/onboarding/onboarding-state.js +1 -1
  202. package/modules/platform-identity/CLAUDE.md +15 -0
  203. package/modules/platform-identity/hub-device.js +147 -0
  204. package/modules/platform-identity/hub-keys.js +151 -0
  205. package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
  206. package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
  207. package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
  208. package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
  209. package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
  210. package/modules/platform-identity/module.json +18 -0
  211. package/modules/platform-identity/platform-identity.js +531 -0
  212. package/modules/platform-identity/pollers/code-sweep.js +43 -0
  213. package/modules/platform-identity/routes/my-projects.js +140 -0
  214. package/modules/platform-identity/routes/projects.js +107 -0
  215. package/modules/platform-identity/routes/scouting.js +35 -0
  216. package/modules/platform-identity/routes/sso.js +402 -0
  217. package/modules/platform-identity/tests/platform-identity.mjs +388 -0
  218. package/modules/platform-identity/ui/.gitkeep +0 -0
  219. package/modules/security/routes/reports.js +10 -0
  220. package/modules/sessions/db.js +166 -21
  221. package/modules/sessions/module.json +1 -1
  222. package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
  223. package/modules/sessions/routes/sessions.js +33 -0
  224. package/package-lock.json +2 -2
  225. package/package.json +1 -1
  226. package/public-landing/assets/anim/idle/frame_00.webp +0 -0
  227. package/public-landing/assets/anim/idle/frame_01.webp +0 -0
  228. package/public-landing/assets/anim/idle/frame_02.webp +0 -0
  229. package/public-landing/assets/anim/idle/frame_03.webp +0 -0
  230. package/public-landing/assets/anim/idle/frame_04.webp +0 -0
  231. package/public-landing/assets/anim/idle/frame_05.webp +0 -0
  232. package/public-landing/assets/anim/idle/frame_06.webp +0 -0
  233. package/public-landing/assets/anim/idle/frame_07.webp +0 -0
  234. package/public-landing/assets/anim/idle/frame_08.webp +0 -0
  235. package/public-landing/assets/anim/idle/frame_09.webp +0 -0
  236. package/public-landing/assets/anim/idle/frame_10.webp +0 -0
  237. package/public-landing/assets/anim/idle/frame_11.webp +0 -0
  238. package/public-landing/assets/anim/idle/frame_12.webp +0 -0
  239. package/public-landing/assets/anim/idle/frame_13.webp +0 -0
  240. package/public-landing/assets/anim/idle/frame_14.webp +0 -0
  241. package/public-landing/assets/anim/idle/frame_15.webp +0 -0
  242. package/public-landing/assets/anim/idle/frame_16.webp +0 -0
  243. package/public-landing/assets/anim/idle/frame_17.webp +0 -0
  244. package/public-landing/assets/anim/idle/frame_18.webp +0 -0
  245. package/public-landing/assets/anim/idle/frame_19.webp +0 -0
  246. package/public-landing/assets/anim/idle/frame_20.webp +0 -0
  247. package/public-landing/assets/anim/idle/frame_21.webp +0 -0
  248. package/public-landing/assets/anim/idle/frame_22.webp +0 -0
  249. package/public-landing/assets/anim/idle/frame_23.webp +0 -0
  250. package/public-landing/assets/anim/idle/frame_24.webp +0 -0
  251. package/public-landing/assets/anim/idle/frame_25.webp +0 -0
  252. package/public-landing/assets/anim/idle/frame_26.webp +0 -0
  253. package/public-landing/assets/anim/idle/frame_27.webp +0 -0
  254. package/public-landing/assets/anim/idle/frame_28.webp +0 -0
  255. package/public-landing/assets/anim/idle/frame_29.webp +0 -0
  256. package/public-landing/assets/anim/idle/frame_30.webp +0 -0
  257. package/public-landing/assets/anim/idle/frame_31.webp +0 -0
  258. package/public-landing/assets/anim/idle/frame_32.webp +0 -0
  259. package/public-landing/assets/anim/idle/frame_33.webp +0 -0
  260. package/public-landing/assets/anim/idle/frame_34.webp +0 -0
  261. package/public-landing/assets/anim/idle/frame_35.webp +0 -0
  262. package/public-landing/assets/anim/idle/frame_36.webp +0 -0
  263. package/public-landing/assets/anim/idle/frame_37.webp +0 -0
  264. package/public-landing/assets/anim/idle/frame_38.webp +0 -0
  265. package/public-landing/assets/anim/idle/frame_39.webp +0 -0
  266. package/public-landing/assets/anim/idle/frame_40.webp +0 -0
  267. package/public-landing/assets/anim/idle/frame_41.webp +0 -0
  268. package/public-landing/assets/anim/idle/frame_42.webp +0 -0
  269. package/public-landing/assets/anim/idle/frame_43.webp +0 -0
  270. package/public-landing/create.html +51 -58
  271. package/public-landing/gate/README.md +137 -0
  272. package/public-landing/gate/dispmaps/bar.png +0 -0
  273. package/public-landing/gate/dispmaps/flat.png +0 -0
  274. package/public-landing/gate/dispmaps/key.png +0 -0
  275. package/public-landing/gate/index.html +587 -0
  276. package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
  277. package/public-landing/gate/previews/off-the-boats.jpg +0 -0
  278. package/public-landing/gate/space.css +799 -0
  279. package/public-landing/gate/space.js +1826 -0
  280. package/public-landing/gate/sprites/enter.png +0 -0
  281. package/public-landing/gate/sprites/search.png +0 -0
  282. package/public-landing/gate/sprites/terminal-blank.png +0 -0
  283. package/public-landing/gate/sprites/tile.png +0 -0
  284. package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
  285. package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
  286. package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
  287. package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
  288. package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
  289. package/public-landing/world/assets/plates/scene-establishing.png +0 -0
  290. package/scripts/gds/abandon.js +6 -4
  291. package/scripts/gds/api.js +10 -5
  292. package/scripts/gds/api.ps1 +26 -0
  293. package/scripts/gds/api.sh +6 -0
  294. package/scripts/gds/audit-ships.js +7 -1
  295. package/scripts/gds/backfill-sessions.js +78 -18
  296. package/scripts/gds/backup.js +23 -1
  297. package/scripts/gds/box-connect-lib.js +124 -31
  298. package/scripts/gds/box-sync.js +66 -0
  299. package/scripts/gds/box.js +225 -53
  300. package/scripts/gds/cf-tunnel.js +145 -6
  301. package/scripts/gds/claim.js +34 -3
  302. package/scripts/gds/cli-lib.js +161 -27
  303. package/scripts/gds/code.js +6 -2
  304. package/scripts/gds/deploy-divergence.js +209 -0
  305. package/scripts/gds/dev-lib.js +309 -0
  306. package/scripts/gds/dev.js +322 -0
  307. package/scripts/gds/doctor.js +90 -61
  308. package/scripts/gds/fetch-art-key.js +1 -1
  309. package/scripts/gds/fetch-sound-prefs.js +2 -2
  310. package/scripts/gds/fitness.js +228 -12
  311. package/scripts/gds/gds-db-backup.service +16 -1
  312. package/scripts/gds/gds-db-offsite.service +9 -1
  313. package/scripts/gds/gen-api-client.js +1 -1
  314. package/scripts/gds/gen-api-docs.js +36 -14
  315. package/scripts/gds/gen-diagrams.js +45 -28
  316. package/scripts/gds/gen-file-map.js +2 -1
  317. package/scripts/gds/gen-freshness.js +32 -0
  318. package/scripts/gds/gen-repo-map.js +3 -2
  319. package/scripts/gds/gen-session-index.js +14 -5
  320. package/scripts/gds/hooks/pre-push.js +2 -2
  321. package/scripts/gds/init.js +60 -3
  322. package/scripts/gds/install-git-hooks.js +1 -1
  323. package/scripts/gds/key-card.js +15 -6
  324. package/scripts/gds/key-status.js +1 -1
  325. package/scripts/gds/linkify-refs.js +35 -9
  326. package/scripts/gds/login.js +16 -1
  327. package/scripts/gds/paste-token.js +7 -1
  328. package/scripts/gds/preflight.js +126 -0
  329. package/scripts/gds/provision.js +123 -12
  330. package/scripts/gds/publish-manifest.js +7 -1
  331. package/scripts/gds/reauth.js +7 -3
  332. package/scripts/gds/regen-instance-docs.js +47 -0
  333. package/scripts/gds/run-unit-tests.js +4 -2
  334. package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
  335. package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
  336. package/scripts/gds/sequence.js +425 -0
  337. package/scripts/gds/session-cost.js +10 -1
  338. package/scripts/gds/setup.js +31 -5
  339. package/scripts/gds/shell.js +5 -3
  340. package/scripts/gds/ship.js +319 -29
  341. package/scripts/gds/smoke-gds.js +139 -8
  342. package/scripts/gds/start.js +171 -22
  343. package/scripts/gds/status.js +1 -1
  344. package/scripts/gds/triage.js +10 -9
  345. package/scripts/gds/upgrade.js +124 -2
  346. package/scripts/status-mirror-sync.js +114 -84
  347. package/src/bongos/api-errors.js +1 -0
  348. package/src/bongos/auth.js +447 -102
  349. package/src/bongos/db-kernel.js +27 -0
  350. package/src/bongos/db.js +39 -0
  351. package/src/bongos/module-scope-map.js +27 -1
  352. package/src/bongos/permission-path-check.js +136 -102
  353. package/src/bongos/repo-info.js +47 -11
  354. package/src/bongos/route-rank-check.js +429 -46
  355. package/src/bongos/routes/_helpers.js +12 -1
  356. package/src/bongos/routes/auth.js +219 -2
  357. package/src/bongos/routes/backup.js +58 -2
  358. package/src/bongos/routes/me.js +35 -1
  359. package/src/bongos/serve-internal.js +84 -15
  360. package/src/branding.js +32 -0
  361. package/src/instance-config.js +30 -0
  362. package/src/module-api.js +112 -1
  363. package/src/platform-server.js +86 -9
  364. package/tests/abandon_task.mjs +59 -3
  365. package/tests/api_cli_flags.mjs +53 -0
  366. package/tests/auth_page_gate.mjs +13 -0
  367. package/tests/backfill_sessions.mjs +127 -1
  368. package/tests/backup_cli.mjs +54 -0
  369. package/tests/bongos_box_connect.mjs +77 -0
  370. package/tests/box_access.mjs +130 -1
  371. package/tests/box_credential.mjs +125 -0
  372. package/tests/box_host_keys.mjs +24 -0
  373. package/tests/box_infra_resolve.mjs +149 -0
  374. package/tests/box_scope_session.mjs +6 -2
  375. package/tests/box_sync.mjs +55 -0
  376. package/tests/box_task_scope.mjs +99 -0
  377. package/tests/branding.mjs +12 -0
  378. package/tests/cf_ruleset_preflight.mjs +142 -0
  379. package/tests/cf_tunnel.mjs +37 -0
  380. package/tests/claim_gate_rebase.mjs +98 -0
  381. package/tests/clawback_confirmed_backlog.mjs +103 -0
  382. package/tests/cli_lib_formatters.mjs +20 -0
  383. package/tests/confirm_attribution.mjs +30 -46
  384. package/tests/credit_grant.mjs +198 -10
  385. package/tests/criterion_authoring_gate.mjs +272 -0
  386. package/tests/criterion_progress.mjs +33 -1
  387. package/tests/dev_lib.mjs +253 -0
  388. package/tests/devbox_artifact_gate.mjs +75 -0
  389. package/tests/devbox_dmg_backdrop.mjs +137 -0
  390. package/tests/devbox_icons.mjs +304 -0
  391. package/tests/devbox_installer_brand.mjs +232 -0
  392. package/tests/devbox_key_register.mjs +234 -0
  393. package/tests/docs_openapi_route.mjs +110 -0
  394. package/tests/doctor_hooks.mjs +141 -0
  395. package/tests/extract_json.mjs +140 -0
  396. package/tests/fitness.mjs +89 -0
  397. package/tests/gen_freshness.mjs +96 -0
  398. package/tests/goal_advisory.mjs +251 -0
  399. package/tests/goal_criterion_confirm_ui.mjs +269 -0
  400. package/tests/goal_routes.mjs +59 -4
  401. package/tests/goal_tier.mjs +12 -3
  402. package/tests/governance_catalog.mjs +129 -0
  403. package/tests/governance_claim_eligibility.mjs +262 -0
  404. package/tests/governance_config.mjs +187 -0
  405. package/tests/governance_ownership_scope.mjs +410 -0
  406. package/tests/governance_page_gates.mjs +324 -0
  407. package/tests/governance_parity.mjs +336 -0
  408. package/tests/governance_principals.mjs +232 -0
  409. package/tests/governance_protected_surfaces.mjs +208 -0
  410. package/tests/governance_require_permission.mjs +223 -0
  411. package/tests/governance_resolver.mjs +84 -0
  412. package/tests/governance_role_sync.mjs +231 -0
  413. package/tests/governance_seed.mjs +127 -0
  414. package/tests/governance_session_scope.mjs +434 -0
  415. package/tests/governance_vanilla_boot.mjs +203 -0
  416. package/tests/grade_attribution.mjs +7 -7
  417. package/tests/hall_audit.mjs +223 -0
  418. package/tests/hall_nav.mjs +191 -0
  419. package/tests/hall_palette.mjs +206 -0
  420. package/tests/hall_signout.mjs +124 -0
  421. package/tests/hook_user_agents.mjs +10 -0
  422. package/tests/idea_triage.mjs +162 -0
  423. package/tests/idp_device_exchange.mjs +93 -0
  424. package/tests/init.mjs +51 -0
  425. package/tests/instance_config.mjs +65 -0
  426. package/tests/instance_mismatch_guard.mjs +23 -6
  427. package/tests/instance_release_config.mjs +104 -0
  428. package/tests/linkify_refs.mjs +15 -2
  429. package/tests/module_contributions.mjs +55 -0
  430. package/tests/module_loader.mjs +10 -5
  431. package/tests/module_scope_active_claims.mjs +94 -0
  432. package/tests/needs_rebase.mjs +92 -0
  433. package/tests/one_active_claim_per_session.mjs +76 -0
  434. package/tests/override_attribution.mjs +10 -8
  435. package/tests/override_request_ownership.mjs +8 -4
  436. package/tests/parse_cookie_decode.mjs +42 -0
  437. package/tests/pay_on_land.mjs +129 -0
  438. package/tests/platform_boot.mjs +100 -1
  439. package/tests/post_login_landing.mjs +42 -0
  440. package/tests/preflight_checks.mjs +72 -0
  441. package/tests/provision.mjs +132 -7
  442. package/tests/publish_branch_route.mjs +53 -0
  443. package/tests/publish_reconciler.mjs +187 -2
  444. package/tests/repo_info_binding.mjs +248 -0
  445. package/tests/reward_gate.mjs +11 -2
  446. package/tests/reward_mode_cost_plus_only.mjs +116 -0
  447. package/tests/reward_mode_surfacing.mjs +168 -0
  448. package/tests/route_permission_check.mjs +301 -0
  449. package/tests/route_rank_check.mjs +254 -12
  450. package/tests/route_validation.mjs +30 -0
  451. package/tests/sequence.mjs +267 -0
  452. package/tests/session_digest.mjs +44 -0
  453. package/tests/session_index.mjs +18 -6
  454. package/tests/session_records.mjs +39 -1
  455. package/tests/session_reward_on_land.mjs +381 -0
  456. package/tests/session_reward_reconcile.mjs +229 -0
  457. package/tests/ship_api_client_pathspec.mjs +100 -0
  458. package/tests/ship_base_flag.mjs +93 -0
  459. package/tests/ship_ci_deploy.mjs +80 -3
  460. package/tests/ship_upstream_deletion.mjs +57 -0
  461. package/tests/smoke_target.mjs +233 -0
  462. package/tests/start_env_guard.mjs +87 -0
  463. package/tests/start_rebase_warning.mjs +103 -0
  464. package/tests/status_mirror_sync.mjs +122 -0
  465. package/tests/stop_refcheck.mjs +19 -1
  466. package/tests/task_goal_link.mjs +15 -6
  467. package/tests/upgrade.mjs +139 -0
  468. package/tests/web_admission_status.mjs +162 -0
  469. package/tests/write_validation_gaps.mjs +110 -0
@@ -111,8 +111,12 @@ export function createClient(opts = {}) {
111
111
  postAuthAppPairPoll: (args) => request("POST", "/auth/app-pair/poll", { hasBody: true }, args),
112
112
  // POST /auth/app-pair/start — rank: public — POST /auth/app-pair/start
113
113
  postAuthAppPairStart: (args) => request("POST", "/auth/app-pair/start", { hasBody: true }, args),
114
+ // POST /auth/backchannel-logout — rank: public — POST /auth/backchannel-logout
115
+ postAuthBackchannelLogout: (args) => request("POST", "/auth/backchannel-logout", { hasBody: true }, args),
114
116
  // POST /auth/cli-token/issue — rank: any-builder — POST /auth/cli-token/issue
115
117
  postAuthCliTokenIssue: (args) => request("POST", "/auth/cli-token/issue", { hasBody: true }, args),
118
+ // POST /auth/device/exchange — rank: public — POST /auth/device/exchange
119
+ postAuthDeviceExchange: (args) => request("POST", "/auth/device/exchange", { hasBody: true }, args),
116
120
  // POST /auth/device/poll — rank: public — POST /auth/device/poll
117
121
  postAuthDevicePoll: (args) => request("POST", "/auth/device/poll", { hasBody: true }, args),
118
122
  // POST /auth/device/start — rank: public — POST /auth/device/start
@@ -129,6 +133,8 @@ export function createClient(opts = {}) {
129
133
  postAuthRevoke: (args) => request("POST", "/auth/revoke", { hasBody: true }, args),
130
134
  // POST /auth/revoke-all — rank: archon — POST /auth/revoke-all
131
135
  postAuthRevokeAll: (args) => request("POST", "/auth/revoke-all", { hasBody: true }, args),
136
+ // GET /auth/web/admission-status — rank: public — GET /auth/web/admission-status
137
+ getAuthWebAdmissionStatus: (args) => request("GET", "/auth/web/admission-status", { hasBody: false }, args),
132
138
  // GET /auth/web/callback — rank: public — GET /auth/web/callback
133
139
  getAuthWebCallback: (args) => request("GET", "/auth/web/callback", { hasBody: false }, args),
134
140
  // GET /auth/web/start — rank: public — GET /auth/web/start
@@ -177,6 +183,8 @@ export function createClient(opts = {}) {
177
183
  getBoxManagedSettings: (args) => request("GET", "/box/managed-settings", { hasBody: false }, args),
178
184
  // GET /box/me — rank: any-builder — GET /box/me
179
185
  getBoxMe: (args) => request("GET", "/box/me", { hasBody: false }, args),
186
+ // GET /box/scope — rank: any-builder — GET /box/scope
187
+ getBoxScope: (args) => request("GET", "/box/scope", { hasBody: false }, args),
180
188
  // GET /box/source-access — rank: any-builder — GET /box/source-access
181
189
  getBoxSourceAccess: (args) => request("GET", "/box/source-access", { hasBody: false }, args),
182
190
  // GET /box/ssh-key — rank: any-builder — GET /box/ssh-key
@@ -244,12 +252,6 @@ export function createClient(opts = {}) {
244
252
  // POST /cost — rank: any-builder — POST /cost
245
253
  postCost: (args) => request("POST", "/cost", { hasBody: true }, args),
246
254
  },
247
- "demo": {
248
- // GET /demo/health — rank: public — GET /demo/health
249
- getDemoHealth: (args) => request("GET", "/demo/health", { hasBody: false }, args),
250
- // POST /demo/ping — rank: metic+archon — POST /demo/ping
251
- postDemoPing: (args) => request("POST", "/demo/ping", { hasBody: true }, args),
252
- },
253
255
  "dependencies": {
254
256
  // DELETE /dependencies — rank: archon — DELETE /dependencies
255
257
  deleteDependencies: (args) => request("DELETE", "/dependencies", { hasBody: false }, args),
@@ -276,6 +278,12 @@ export function createClient(opts = {}) {
276
278
  // POST /gate-approvals/{pr}/approve — rank: archon — POST /gate-approvals/:pr/approve
277
279
  postGateApprovalsPrApprove: (args) => request("POST", "/gate-approvals/{pr}/approve", { hasBody: true }, args),
278
280
  },
281
+ "github": {
282
+ // GET /github/repos — rank: any-builder — GET /github/repos
283
+ getGithubRepos: (args) => request("GET", "/github/repos", { hasBody: false }, args),
284
+ // POST /github/repos — rank: any-builder — POST /github/repos
285
+ postGithubRepos: (args) => request("POST", "/github/repos", { hasBody: true }, args),
286
+ },
279
287
  "goals": {
280
288
  // GET /goals — rank: any-builder — GET /goals
281
289
  getGoals: (args) => request("GET", "/goals", { hasBody: false }, args),
@@ -343,6 +351,8 @@ export function createClient(opts = {}) {
343
351
  getInboxId: (args) => request("GET", "/inbox/{id}", { hasBody: false }, args),
344
352
  // PATCH /inbox/{id} — rank: metic+archon — PATCH /inbox/:id
345
353
  patchInboxId: (args) => request("PATCH", "/inbox/{id}", { hasBody: true }, args),
354
+ // POST /inbox/{id}/ratify — rank: archon — POST /inbox/:id/ratify
355
+ postInboxIdRatify: (args) => request("POST", "/inbox/{id}/ratify", { hasBody: true }, args),
346
356
  },
347
357
  "instance": {
348
358
  // GET /instance — rank: public — GET /instance
@@ -375,16 +385,6 @@ export function createClient(opts = {}) {
375
385
  getMeAchievements: (args) => request("GET", "/me/achievements", { hasBody: false }, args),
376
386
  // POST /me/anthropic-link — rank: any-builder — POST /me/anthropic-link
377
387
  postMeAnthropicLink: (args) => request("POST", "/me/anthropic-link", { hasBody: true }, args),
378
- // GET /me/art-key — rank: any-builder — GET /me/art-key
379
- getMeArtKey: (args) => request("GET", "/me/art-key", { hasBody: false }, args),
380
- // DELETE /me/art-key/own — rank: any-builder — DELETE /me/art-key/own
381
- deleteMeArtKeyOwn: (args) => request("DELETE", "/me/art-key/own", { hasBody: false }, args),
382
- // GET /me/art-key/own — rank: any-builder — GET /me/art-key/own
383
- getMeArtKeyOwn: (args) => request("GET", "/me/art-key/own", { hasBody: false }, args),
384
- // PUT /me/art-key/own — rank: any-builder — PUT /me/art-key/own
385
- putMeArtKeyOwn: (args) => request("PUT", "/me/art-key/own", { hasBody: true }, args),
386
- // POST /me/art-key/storage-init — rank: archon — POST /me/art-key/storage-init
387
- postMeArtKeyStorageInit: (args) => request("POST", "/me/art-key/storage-init", { hasBody: true }, args),
388
388
  // PATCH /me/disciplines — rank: any-builder — PATCH /me/disciplines
389
389
  patchMeDisciplines: (args) => request("PATCH", "/me/disciplines", { hasBody: true }, args),
390
390
  // GET /me/display-name — rank: any-builder — GET /me/display-name
@@ -429,6 +429,10 @@ export function createClient(opts = {}) {
429
429
  getMemoryBfgBuildersBuilderIdFiles: (args) => request("GET", "/memory/bfg/builders/{builderId}/files", { hasBody: false }, args),
430
430
  // POST /memory/builders/{builderId}/bfg-write — rank: bfg-principal — POST /memory/builders/:builderId/bfg-write
431
431
  postMemoryBuildersBuilderIdBfgWrite: (args) => request("POST", "/memory/builders/{builderId}/bfg-write", { hasBody: true }, args),
432
+ // GET /memory/builders/{builderId}/file — rank: archon — GET /memory/builders/:builderId/file
433
+ getMemoryBuildersBuilderIdFile: (args) => request("GET", "/memory/builders/{builderId}/file", { hasBody: false }, args),
434
+ // GET /memory/builders/{builderId}/files — rank: archon — GET /memory/builders/:builderId/files
435
+ getMemoryBuildersBuilderIdFiles: (args) => request("GET", "/memory/builders/{builderId}/files", { hasBody: false }, args),
432
436
  // DELETE /memory/file — rank: any-builder — DELETE /memory/file
433
437
  deleteMemoryFile: (args) => request("DELETE", "/memory/file", { hasBody: true }, args),
434
438
  // GET /memory/file — rank: any-builder — GET /memory/file
@@ -448,12 +452,38 @@ export function createClient(opts = {}) {
448
452
  // GET /modules/submissions — rank: metic+archon — GET /modules/submissions
449
453
  getModulesSubmissions: (args) => request("GET", "/modules/submissions", { hasBody: false }, args),
450
454
  },
455
+ "myProjects": {
456
+ // GET /my-projects — rank: any-builder — GET /my-projects
457
+ getMyProjects: (args) => request("GET", "/my-projects", { hasBody: false }, args),
458
+ // POST /my-projects/{clientId}/leave — rank: any-builder — POST /my-projects/:clientId/leave
459
+ postMyProjectsClientIdLeave: (args) => request("POST", "/my-projects/{clientId}/leave", { hasBody: true }, args),
460
+ // GET /my-projects/invites — rank: any-builder — GET /my-projects/invites
461
+ getMyProjectsInvites: (args) => request("GET", "/my-projects/invites", { hasBody: false }, args),
462
+ // POST /my-projects/invites/{clientId}/accept — rank: any-builder — POST /my-projects/invites/:clientId/accept
463
+ postMyProjectsInvitesClientIdAccept: (args) => request("POST", "/my-projects/invites/{clientId}/accept", { hasBody: true }, args),
464
+ // POST /my-projects/invites/{clientId}/decline — rank: any-builder — POST /my-projects/invites/:clientId/decline
465
+ postMyProjectsInvitesClientIdDecline: (args) => request("POST", "/my-projects/invites/{clientId}/decline", { hasBody: true }, args),
466
+ // POST /my-projects/join — rank: any-builder — POST /my-projects/join
467
+ postMyProjectsJoin: (args) => request("POST", "/my-projects/join", { hasBody: true }, args),
468
+ },
451
469
  "overrideRequests": {
452
470
  // GET /override-requests — rank: archon — GET /override-requests
453
471
  getOverrideRequests: (args) => request("GET", "/override-requests", { hasBody: false }, args),
454
472
  // POST /override-requests/{id}/decide — rank: archon — POST /override-requests/:id/decide
455
473
  postOverrideRequestsIdDecide: (args) => request("POST", "/override-requests/{id}/decide", { hasBody: true }, args),
456
474
  },
475
+ "projects": {
476
+ // GET /projects — rank: archon — GET /projects
477
+ getProjects: (args) => request("GET", "/projects", { hasBody: false }, args),
478
+ // POST /projects — rank: archon — POST /projects
479
+ postProjects: (args) => request("POST", "/projects", { hasBody: true }, args),
480
+ // PATCH /projects/{id}/featured — rank: archon — PATCH /projects/:id/featured
481
+ patchProjectsIdFeatured: (args) => request("PATCH", "/projects/{id}/featured", { hasBody: true }, args),
482
+ // GET /projects/featured — rank: public — GET /projects/featured
483
+ getProjectsFeatured: (args) => request("GET", "/projects/featured", { hasBody: false }, args),
484
+ // POST /projects/invite — rank: archon — POST /projects/invite
485
+ postProjectsInvite: (args) => request("POST", "/projects/invite", { hasBody: true }, args),
486
+ },
457
487
  "provisioning": {
458
488
  // GET /provisioning/fleet — rank: archon — GET /provisioning/fleet
459
489
  getProvisioningFleet: (args) => request("GET", "/provisioning/fleet", { hasBody: false }, args),
@@ -520,6 +550,10 @@ export function createClient(opts = {}) {
520
550
  // GET /public/versions — rank: public — GET /public/versions
521
551
  getPublicVersions: (args) => request("GET", "/public/versions", { hasBody: false }, args),
522
552
  },
553
+ "scouting": {
554
+ // GET /scouting — rank: archon — GET /scouting
555
+ getScouting: (args) => request("GET", "/scouting", { hasBody: false }, args),
556
+ },
523
557
  "search": {
524
558
  // POST /search — rank: any-builder — POST /search
525
559
  postSearch: (args) => request("POST", "/search", { hasBody: true }, args),
@@ -527,12 +561,26 @@ export function createClient(opts = {}) {
527
561
  postSearchMark: (args) => request("POST", "/search/mark", { hasBody: true }, args),
528
562
  },
529
563
  "security": {
564
+ // GET /security/adr — rank: metic+archon — GET /security/adr
565
+ getSecurityAdr: (args) => request("GET", "/security/adr", { hasBody: false }, args),
530
566
  // GET /security/docs — rank: archon — GET /security/docs
531
567
  getSecurityDocs: (args) => request("GET", "/security/docs", { hasBody: false }, args),
532
568
  // POST /security/docs — rank: archon — POST /security/docs
533
569
  postSecurityDocs: (args) => request("POST", "/security/docs", { hasBody: true }, args),
534
570
  // GET /security/docs/{slug} — rank: archon — GET /security/docs/:slug
535
571
  getSecurityDocsSlug: (args) => request("GET", "/security/docs/{slug}", { hasBody: false }, args),
572
+ // GET /security/reports — rank: archon — GET /security/reports
573
+ getSecurityReports: (args) => request("GET", "/security/reports", { hasBody: false }, args),
574
+ // POST /security/reports — rank: metic+archon — POST /security/reports
575
+ postSecurityReports: (args) => request("POST", "/security/reports", { hasBody: true }, args),
576
+ // POST /security/reports/{id}/confirm — rank: archon — POST /security/reports/:id/confirm
577
+ postSecurityReportsIdConfirm: (args) => request("POST", "/security/reports/{id}/confirm", { hasBody: true }, args),
578
+ // POST /security/reports/{id}/dispute — rank: archon — POST /security/reports/:id/dispute
579
+ postSecurityReportsIdDispute: (args) => request("POST", "/security/reports/{id}/dispute", { hasBody: true }, args),
580
+ // POST /security/reports/{id}/fix — rank: archon — POST /security/reports/:id/fix
581
+ postSecurityReportsIdFix: (args) => request("POST", "/security/reports/{id}/fix", { hasBody: true }, args),
582
+ // POST /security/reports/{id}/link-fix — rank: archon — POST /security/reports/:id/link-fix
583
+ postSecurityReportsIdLinkFix: (args) => request("POST", "/security/reports/{id}/link-fix", { hasBody: true }, args),
536
584
  },
537
585
  "session": {
538
586
  // POST /session/optimize — rank: any-builder — POST /session/optimize
@@ -548,6 +596,22 @@ export function createClient(opts = {}) {
548
596
  // GET /sessions/search — rank: archon — GET /sessions/search
549
597
  getSessionsSearch: (args) => request("GET", "/sessions/search", { hasBody: false }, args),
550
598
  },
599
+ "sso": {
600
+ // POST /sso/activity/rollup — rank: public — POST /sso/activity/rollup
601
+ postSsoActivityRollup: (args) => request("POST", "/sso/activity/rollup", { hasBody: true }, args),
602
+ // GET /sso/authorize — rank: public — GET /sso/authorize
603
+ getSsoAuthorize: (args) => request("GET", "/sso/authorize", { hasBody: false }, args),
604
+ // POST /sso/device/poll — rank: public — POST /sso/device/poll
605
+ postSsoDevicePoll: (args) => request("POST", "/sso/device/poll", { hasBody: true }, args),
606
+ // POST /sso/device/start — rank: public — POST /sso/device/start
607
+ postSsoDeviceStart: (args) => request("POST", "/sso/device/start", { hasBody: true }, args),
608
+ // POST /sso/membership/check-in — rank: public — POST /sso/membership/check-in
609
+ postSsoMembershipCheckIn: (args) => request("POST", "/sso/membership/check-in", { hasBody: true }, args),
610
+ // GET /sso/pubkey — rank: public — GET /sso/pubkey
611
+ getSsoPubkey: (args) => request("GET", "/sso/pubkey", { hasBody: false }, args),
612
+ // POST /sso/token — rank: public — POST /sso/token
613
+ postSsoToken: (args) => request("POST", "/sso/token", { hasBody: true }, args),
614
+ },
551
615
  "tasks": {
552
616
  // GET /tasks — rank: any-builder — GET /tasks
553
617
  getTasks: (args) => request("GET", "/tasks", { hasBody: false }, args),
@@ -557,7 +621,7 @@ export function createClient(opts = {}) {
557
621
  getTasksId: (args) => request("GET", "/tasks/{id}", { hasBody: false }, args),
558
622
  // PATCH /tasks/{id} — rank: metic+archon — PATCH /tasks/:id
559
623
  patchTasksId: (args) => request("PATCH", "/tasks/{id}", { hasBody: true }, args),
560
- // POST /tasks/{id}/abandon — rank: archon — POST /tasks/:id/abandon
624
+ // POST /tasks/{id}/abandon — rank: metic+archon — POST /tasks/:id/abandon
561
625
  postTasksIdAbandon: (args) => request("POST", "/tasks/{id}/abandon", { hasBody: true }, args),
562
626
  // POST /tasks/{id}/attest-gate — rank: any-builder — POST /tasks/:id/attest-gate
563
627
  postTasksIdAttestGate: (args) => request("POST", "/tasks/{id}/attest-gate", { hasBody: true }, args),
@@ -611,7 +675,7 @@ export function createClient(opts = {}) {
611
675
  postVersions: (args) => request("POST", "/versions", { hasBody: true }, args),
612
676
  // GET /versions/{id}/done-when — rank: public — GET /versions/:id/done-when
613
677
  getVersionsIdDoneWhen: (args) => request("GET", "/versions/{id}/done-when", { hasBody: false }, args),
614
- // POST /versions/{id}/done-when — rank: archon — POST /versions/:id/done-when
678
+ // POST /versions/{id}/done-when — rank: metic+archon — POST /versions/:id/done-when
615
679
  postVersionsIdDoneWhen: (args) => request("POST", "/versions/{id}/done-when", { hasBody: true }, args),
616
680
  // GET /versions/{id}/progress — rank: public — GET /versions/:id/progress
617
681
  getVersionsIdProgress: (args) => request("GET", "/versions/{id}/progress", { hasBody: false }, args),
@@ -10,6 +10,7 @@
10
10
  "publicOrigin": "http://localhost:3000",
11
11
  "buildersOrigin": "http://localhost:3000",
12
12
  "statusOrigin": "http://localhost:3000",
13
+ "statusDataUrl": "",
13
14
  "cookieDomain": "",
14
15
  "oauthOrigin": "http://localhost:3000",
15
16
  "devBoxBase": "",
@@ -26,7 +27,7 @@
26
27
  "configDir": "cloudbongos",
27
28
  "firstAdmin": "",
28
29
  "currency": { "label": "credits", "symbol": "" },
29
- "reward": { "marginPct": 20, "usdPeg": 1 },
30
+ "reward": { "marginPct": 20, "usdPeg": 1, "mode": "cost-plus-and-estimate" },
30
31
  "models": { "main": "opus", "subagentDefault": "opus", "subagentRoutine": "haiku" },
31
32
  "theme": {
32
33
  "palette": "",
@@ -60,5 +61,9 @@
60
61
  "ctaLabel": "Try signing in again"
61
62
  }
62
63
  },
64
+ "landing": {
65
+ "_note": "Where a successful sign-in lands when no explicit ?return= / same-apex Referer was carried (auth.js postLoginLanding). Portable default '/builders' — the hall always exists. A platform-home instance points this at its own front door (cloudbongos.com → '/gate/', the orbs). Validated through the open-redirect allowlist; env override <PREFIX>_POST_LOGIN_LANDING. Task 1002240.",
66
+ "postLogin": "/builders"
67
+ },
63
68
  "envPrefix": "CLOUDBONGOS"
64
69
  }
@@ -0,0 +1,21 @@
1
+ {
2
+ "_note": "Neutral governance starter — the role TEMPLATES a fresh instance inherits (BV1.R96, ADR 0151). Resolved by modules/governance/config.js with the branding precedence: env override -> config/governance.json (instance) -> this file. TEMPLATES AND DEFAULTS ONLY. Live roles, grants, and per-builder assignments are DB rows (governance_roles / governance_role_permissions / governance_builder_roles), resolved server-side per request by resolver.js — putting a live grant here would make config a second source of authority truth and break ADR 0016. The four seeded rank-roles carry label + description only: their grants derive from catalog.js floors and are materialised by governance_002, so config may relabel a seeded role, never re-grant it. A CUSTOM role template may propose a `permissions` array, but a `system: true` key or a key the catalog does not define is stripped fail-closed — a config file is editable by anyone who can write the checkout, so it is not a trust boundary. Carries NO host identity: a vanilla instance boots sane from this file alone (proved by R102). To customise, copy to config/governance.json and edit there.",
3
+ "roles": {
4
+ "xenos": {
5
+ "label": "Xenos",
6
+ "description": "Seeded rank-role — the sandboxed entry rank. Own-work + public read."
7
+ },
8
+ "thetes": {
9
+ "label": "Thetes",
10
+ "description": "Seeded rank-role — graduated newcomer. Adds the general claim queue."
11
+ },
12
+ "metic": {
13
+ "label": "Metic",
14
+ "description": "Seeded rank-role — the trusted working rank. Adds review/triage/authoring."
15
+ },
16
+ "archon": {
17
+ "label": "Archon",
18
+ "description": "Seeded rank-role — top authority. Adds the admin + system (non-grantable) powers."
19
+ }
20
+ }
21
+ }
@@ -69,6 +69,30 @@ After shipping, the fix was adversarially red-teamed (a 4-dimension panel + per-
69
69
 
70
70
  **Not yet done (finding `#7`, follow-up):** the permission-path pre-pass (layer 2) runs only client-side in `ship.js`; the server `/grade` and `/confirm` routes don't re-run it. Layer 3 is the server-side backstop today; re-running the pre-pass server-side (the server holds the live rank in `req.builder.rank`) would harden the cooperative path.
71
71
 
72
+ ## Branch-protection status: re-verified 2026-07-24 (task [#1001840](https://cloudbongos.com/builders#/task/1001840))
73
+
74
+ The 2026-07-03 authority sweep reported that `main` had since gained live branch protection (required
75
+ checks + code-owner review, direct push blocked), which would have demoted `main-audit.js` from
76
+ *authoritative* to *defense-in-depth*. **That report was wrong.** Re-checked against the live GitHub
77
+ API — every probe agrees there is no protection on `main`:
78
+
79
+ | Probe | Result |
80
+ |---|---|
81
+ | `GET /repos/:o/:r/branches/main` | `protected: false` |
82
+ | `GET /repos/:o/:r/branches/main/protection` | `404` (a *protected* branch returns `403` to a non-admin, not `404`) |
83
+ | `GET /repos/:o/:r/rulesets` | `[]` |
84
+ | `GET /repos/:o/:r/rules/branches/main` (effective rules; readable by non-admins) | `[]` |
85
+ | `.github/CODEOWNERS` | absent |
86
+ | PR #19, merged 2026-07-24 by a **non-admin** collaborator | `statusCheckRollup: []`, no review decision — a merge that required checks + code-owner review would forbid |
87
+
88
+ So §"The gap" ¶1 and the layer table above stand **as written**: the repo is still a personal-account
89
+ private repo with no protection available, and **layer 3 (`main-audit.js`) remains the authoritative
90
+ enforcement**, not a redundant second net. Option B (blocker
91
+ [#17](https://cloudbongos.com/builders#/blocker/17)) is still open and still the durable closure.
92
+
93
+ Treat this as the freshness stamp: re-run the probes above before asserting the claim has changed —
94
+ a plan upgrade or a new ruleset would flip it, and this section is what a future sweep should update.
95
+
72
96
  ## Notes
73
97
 
74
98
  We did **not** take immediate action against Builder 25 — Lars was overseeing the session and the change itself was a legitimate bug fix. The point of this ADR is the *mechanism*, not the person: the system must make the bypass hard and visible regardless of intent.
@@ -1,6 +1,6 @@
1
1
  # 0090 — Metic gains task authoring: create / promote / edit + dependency & criteria shaping
2
2
 
3
- - **Status:** Accepted
3
+ - **Status:** Accepted — one line amended by [ADR 0152](<redacted>.md) (2026-07-27): `POST /tasks/:id/abandon`, listed below under "What stays Archon-only", moved to Metic+ alongside create/promote/edit, paired with an `abandoned → backlog` restore on `/promote`. The rest of this ADR stands.
4
4
  - **Date:** 2026-06-26
5
5
  - **Deciders:** Lars (Archon), Claude
6
6
 
@@ -0,0 +1,41 @@
1
+ # 0144 — Re-home the dev-box fleet onto the cloudbongos.com plane (native runners, local DB, isolated fleet tag)
2
+
3
+ - **Status:** Accepted — Phases 1–3 built in task 1002239. **Phase 4 part (a) (stop legacy `cohost-box-*` runners) done in task 1002248** (2026-07-15); part (b) (disable dev-box on example.com) flagged to owner — not reachable from the cloudbongos control plane.
4
+ - **Date:** 2026-07-15
5
+ - **Deciders:** Lars (owner/Archon) chose "re-home properly, not a legacy band-aid" + pre-approved box spend; Claude designed + executed autonomously.
6
+ - **Task:** [#1002239](https://cloudbongos.com/builders#/task/1002239) · supersedes stale incident [#1002031](https://cloudbongos.com/builders#/task/1002031) · **Goal:** 1000025 (builder experience — hall, dev box, skills).
7
+
8
+ ## Context
9
+
10
+ "Get the dev box up and running again for cloud bongos" surfaced a split that was invisible from the platform:
11
+
12
+ - **The dev-box feature was disabled on cloudbongos.com** — `config/modules.json` listed only `provisioning` + `platform-identity`, so `/api/gds/box/*` 404'd. The instance note said dev-box stays off "until box infra is confirmed present."
13
+ - **The live dev-box data plane was the *legacy* `example` Postgres** (on the co-hosting box), drained by the cloudbongos control plane's `cohost-box-*` systemd runners over an SSH DB tunnel ([ADR 0132](<redacted>.md)). Builder identities even diverged: `example-owner` is builder **1** in the example DB, **3** on cloudbongos.com.
14
+ - **The fleet was dormant** — all 9 boxes `destroyed`, control-plane timers green but idle (`ran=0`). So nothing was *broken*; the box capability simply lived on the old plane and was never brought onto the platform after the [ADR 0139] work-history migration.
15
+
16
+ The owner chose to **re-home the box feature onto the cloudbongos.com plane** (its own DB, its own domains) rather than revive a box on the legacy `example` infra. Feasibility was high: the box tables already exist in the cloudbongos DB, `cloudbongos.com` is a live Cloudflare zone on the same account the control plane already uses, the DO/CF tokens are already on the control plane, and box domains are branding-driven (`domains.devBoxBase`/`sshBoxBase`) — not hardcoded.
17
+
18
+ ## Decision
19
+
20
+ Stand up a **cloudbongos-native box plane** alongside (not replacing) the legacy one, in four moves:
21
+
22
+ 1. **Enable the feature (persisted).** `config/modules.json` → `dev-box: true`; `config/branding.json` → `domains.devBoxBase=cloudbongos.com`, `sshBoxBase=dev.cloudbongos.com`. **Persistence is by pushing to the instance `origin/main`, not a local commit** — `deploy.sh` (the ~3-min pull-deploy, ADR 0129) does `git reset --hard origin/main`, which silently reverts any local-only change. (Learned the hard way: a local commit was reset away within one deploy cycle.)
23
+
24
+ 2. **Native lifecycle runners against the LOCAL DB.** Four systemd service+timer pairs (`cloudbongos-box-{intent-runner,idle-suspend,drift-reconcile,dormant-reclaim}`) modeled on the `cohost-box-*` units but pointed at the **local `cloudbongos` Postgres** (peer auth, `PGDATABASE=cloudbongos`, **no tunnel, no `DATABASE_URL`**). Box domain/zone come from env, not branding, for the CLI: `CLOUDFLARE_ZONE`, `BOX_TERMINAL_ZONE`, `BOX_DNS_ZONE`, `GDS_PUBLIC_ORIGIN` (all cloudbongos.com). Overrides live in `/etc/cloudbongos/cloudbongos-box.env`, layered over `/etc/cloudbongos/box.env` (which holds the tokens).
25
+
26
+ 3. **Fleet isolation by a DISTINCT tag — the load-bearing safety decision.** Both planes share one DigitalOcean + one Cloudflare account. `boxes.js planDropletDrift` treats any fleet-tagged droplet **not in its own DB as a "zombie" and force-deletes it** (30-min grace). With a shared `otb-builder-box` tag, the legacy plane's hourly drift-reconcile would reap a cloudbongos box within ~30–90 min. So the native plane uses **`BOX_TAG=cloudbongos-builder-box`**. This is watertight because `cmdReconcileDrift` lists droplets `{tag: CONFIG.tag}`, and the sweeps act only on their own DB's rows — so each plane manages exactly its own fleet.
27
+
28
+ 4. **Legacy plane untouched.** The `example` (cohost) plane keeps running, fully isolated by tag/DB. Retiring it (disable dev-box on example.com, stop the cohost tunnel/runners) is **Phase 4** — deferred to an explicit owner go-ahead because it is the one hard-to-reverse step and is unnecessary for the cloudbongos plane to work.
29
+
30
+ ## Consequences
31
+
32
+ - cloudbongos.com now serves `/box/*` and provisions boxes on its own plane, at `term-<login>.cloudbongos.com` / `sandbox-<login>.cloudbongos.com`, tracked in the cloudbongos DB, isolated from the legacy fleet.
33
+ - **Verified end-to-end (2026-07-15):** web `POST /box/ensure` → intent in the cloudbongos DB → native `cloudbongos-box-intent-runner` drained it (`ran=1 ok=1`) → droplet `584756519 @ REDACTED_IP`, tunnel + DNS on cloudbongos.com, box `active`. Drift-reconcile confirmed `fleet tag=cloudbongos-builder-box: 0 droplets` before provisioning — proving isolation.
34
+ - Two planes now exist until Phase 4. That is intentional and safe (tag + DB isolation), but means dev-box is "on" in two places.
35
+
36
+ ## Follow-ups (open)
37
+
38
+ 1. **Box first-boot convergence — ROOT-CAUSED + fixed (workaround live; proper fix open, task 1002243).** Symptom: provisioned boxes reached `active` but `cloudflared`/terminal never came up (`term-…` = CF 530) and SSH keys weren't applied. Root cause: **`box.js` provisioned droplets with EMPTY user-data** (`/var/lib/cloud/instance/user-data.txt` = 0 bytes; cloud-init logged "No user-data found"). `box.js` renders the box cloud-init from `<REPO_ROOT>/infra/` (the 26 KB `builder-box-cloud-init.yaml` + ~12 gz+b64-inlined scripts), and `fileGzB64` **silently returns `''` on a missing file**, so a missing `infra/` yields empty user-data with no error. The R89 split moved `scripts/gds/` into the `@bongos/core` package but **kept `infra/` in the instance repo** — yet `box.js` still resolves `infra/` relative to *its own* location (the vendored core), where `infra/` **isn't shipped at all** (0 files). So every box on any vendored/installed instance booted with only DigitalOcean's default user-data. **Workaround applied (temporary):** populated the vendored core's `infra/` from the instance's `infra/` (`/home/lars/cloudbongos/node_modules/@bongos/core/infra/`); re-provision then rendered a 62 KB user-data and the box converged — `cloudflared` active, `term-example-owner.cloudbongos.com` → 200 (authed), builder SSH key baked + working. **Proper fix (task 1002243):** ship `infra/` in the core publish-manifest, OR make `box.js` resolve `infra/` from the instance root (like `core-node.sh`'s candidate-base search), AND make `fileGzB64`/the template render **fail loudly** instead of emitting empty user-data. The workaround is under `node_modules` and will be lost on the next `npm ci`/core reinstall until the proper fix lands.
39
+ 2. **Cloudflare token scope.** Provision warned the ADR 0053 cookie-strip Transform Rule was **not** applied — `CLOUDFLARE_API_TOKEN` lacks "Transform Rules" edit scope on the cloudbongos.com zone. Add the scope (or apply the rule once in the dashboard) **before** other builders get boxes on `*.cloudbongos.com`, so platform cookies (`cookieDomain=<redacted> aren't sent to box subdomains.
40
+ 3. **Cosmetic `otb-` prefix.** Tunnel names (`otb-term-<login>`) and the droplet name (`otb-box-<login>`) are still hardcoded in `box-onboard.js`/`box.js`. Harmless (fleet separation is by tag, and DNS is per-zone), but a branding leak worth parameterizing.
41
+ 4. **Phase 4 — retire the legacy example plane** (owner decision) — **part (a) DONE in task 1002248 (2026-07-15).** Pre-checked read-only that the legacy fleet was empty (0 `otb-builder-box` droplets on DigitalOcean; cohost intent queue `ran=0`), then `systemctl disable --now` on the four `cohost-box-*` timers (intent-runner / idle-suspend / drift-reconcile / dormant-reclaim) on the control-plane host — now `disabled`/`inactive`; the native `cloudbongos-box-*` timers were left `enabled`/`active`. **Part (b) — disabling the dev-box module on example.com itself — NOT reachable from the control plane, flagged to owner:** example's web app + instance checkout are not co-located on the cloudbongos droplet (no local checkout, no ssh alias) and the local example GDS token is expired, so a durable module-disable needs owner access to example's own config/host. Harmless meanwhile: with the runners stopped there is no cost/security impact — worst case is a box request on the legacy instance that now hangs unfulfilled.
@@ -0,0 +1,42 @@
1
+ # 0144 — Federated single-logout: hub-initiated back-channel session revocation
2
+
3
+ - **Status:** Accepted — built in task 1002238.
4
+ - **Date:** 2026-07-15
5
+ - **Deciders:** Lars (owner/Archon) chose the back-channel approach; Claude designed + built.
6
+ - **Task:** [#1002238](https://cloudbongos.com/builders#/task/1002238) · **Goal:** 41 (cross-site identity). Follow-up to [ADR 0141](<redacted>.md).
7
+
8
+ ## Context
9
+
10
+ Cross-site identity ([ADR 0141](<redacted>.md)) gave single sign-**on**: one hub login (cloudbongos.com) reaches every federated project's hall with no second GitHub sign-in. But sign-**off** stayed local. When a federated instance completes the SSO handshake it mints its **own** local session (`completeIdpFlow` → `createSession`) with its own cookie on its own domain. `POST /auth/logout` revokes only *that* instance's session + clears *that* apex cookie — so signing out of the hub left every federated project (e.g. demo) still signed in. Owner request (2026-07-14): make sign-out universal.
11
+
12
+ Three approaches were weighed (the task's options):
13
+
14
+ - **(a) Back-channel SLO** — on hub logout the hub server calls each entered instance's revoke endpoint (server-to-server).
15
+ - **(b) Front-channel SLO** — the browser is bounced through each instance's logout URL (classic OIDC front-channel).
16
+ - **(c) Soft sessions** — federated instances stop holding durable sessions and re-check the hub continuously.
17
+
18
+ ## Decision
19
+
20
+ **Take (a) — back-channel single-logout — per the owner's explicit call.** It is the robust, browser-independent choice for a session-lifecycle security feature, and it reuses the federation machinery ADR 0141 already built.
21
+
22
+ 1. **A hub-signed, purpose-tagged EdDSA logout token.** The hub mints (`platform-identity/hub-keys.js` `mintHubLogoutToken`) a compact Ed25519 JWT — the **same envelope + asymmetric key + ~120s TTL + audience-binding** as the login assertion (`mintHubAssertion`), so a token minted for instance A can't be replayed against instance B, and a compromised instance (holding only the public key) can't forge one. The payload carries **only** `{ iss, aud:<client_id>, sub:<github_id>, purpose:"backchannel-logout", iat, exp, jti }` — never identity, a rank, or a session token. The **`purpose` marker is load-bearing**: a login assertion has no `purpose` (rejected by the logout verifier) and a logout token has no `github_login` (rejected by the login verifier `verifyHubAssertion`), so the two token types are **mutually non-interchangeable** — a captured login assertion can't trigger a logout, and a logout token can't mint a session. The instance verifier (`auth.js verifyHubLogoutToken`) is the boundary-mirrored sibling of `verifyHubAssertion` (ADR 0091 §2), alg **hardcoded to Ed25519** (no algorithm-confusion), signature verified **before** any claim is trusted.
23
+
24
+ 2. **Kernel-mediated seam event, so core stays module-agnostic.** `POST /auth/logout` (core) revokes the local session as before, then **emits** a `builder.logout` seam event (fire-and-forget). On a non-hub instance nothing listens → no-op. On the hub, the `platform-identity` module (enabled only there) reacts. Core never references the module (ADR 0083 one-way rule); the switchboard (`src/module-seams.js`) is the seam.
25
+
26
+ 3. **The hub fans out; the instance revokes.** The module's listener looks up the builder's entered projects and POSTs a per-project logout token to each `{origin}/api/gds/auth/backchannel-logout`. That endpoint (core `auth.js`/`routes/auth.js`, **dormant unless the instance federates** — `idpConfigured()`, else a plain 404) verifies the token against the hub public key it already trusts (`/sso/pubkey`), resolves the builder by `github_id`, and calls `revokeAllSessionsForBuilder` — killing **all** of that builder's local sessions (web + CLI).
27
+
28
+ 4. **Entered-instance list = memberships (ADR 0141 §3).** The hub uses `listMembershipsForAccount(github_id)` — the projects the builder is known to build on — as the fan-out target list, per the task's sanctioned V1 signal. **Over-notifying is safe:** an instance with no live session for that builder just revokes nothing (revoke is idempotent). A precise per-SSO-session entry ledger (notify *only* instances with a live federated session) is a deliberate follow-up, not V1.
29
+
30
+ 5. **Best-effort, bounded, isolated fan-out.** The user's hub session is revoked **synchronously** (the part they experience); the cross-instance sweep runs server-side off the fire-and-forget event, each POST bounded by a 4s timeout, all under `Promise.allSettled` — a slow or down project can never wedge the sweep or the logout, and the fan-out never throws. **Accepted limitation:** if an instance is unreachable at logout time, its local session survives until its own TTL; a retry/queue is out of scope for V1 (logged, not retried).
31
+
32
+ 6. **Anti-replay on the instance side.** A logout token's `jti` is honored **once** (`logoutTokenJtiSeenAndRecord`, symmetric with the CLI-assertion path): the first use revokes, a replay within the ~120s TTL is an idempotent no-op. Without this, a captured token would stay a repeatable "log this builder out" primitive for its whole life — it could keep killing sessions the builder legitimately creates *after* the token was minted (revoking already-dead sessions is otherwise harmless).
33
+
34
+ ## Consequences
35
+
36
+ - **Trust boundary preserved (ADR 0016 / 0141).** The logout token, like every other hub→instance message, carries **identity only** (a subject to sign out) — never authority. The instance decides locally what "sign this github_id out" means (revoke its own sessions). Authority stays local.
37
+ - **Deploys to both sides.** The instance half (`/auth/backchannel-logout`, `verifyHubLogoutToken`, the `builder.logout` emit) ships in **core** (dormant on non-federated instances); the hub fan-out ships in the **platform-identity** module. So this go-live re-vendors **both** the hub (cloudbongos.com) and every federated instance (demo). Core `1.18.5 → 1.18.7` (the `1.18.6` cut on `main` is the unrelated task 1002241 `?return=` fix, carried along).
38
+ - **No new schema.** Reuses the existing `<redacted>` + `sso_clients` (origin) rows and the existing Ed25519 hub keypair. No migration.
39
+ - **Not front-channel (b):** modern third-party-cookie / iframe restrictions (Safari ITP, Chrome's trajectory) make browser-driven cross-site logout unreliable, and it doesn't complete if the tab closes mid-chain. Rejected for a security guarantee.
40
+ - **Not soft sessions (c):** re-checking the hub per-request adds latency to every request and makes a federated instance's sign-in fail whenever the hub is down (a hard runtime dependency) — too heavy for a bootstrapped setup.
41
+ - **V1 scope:** hub-initiated logout only. A federated instance's own logout does **not** yet propagate back to the hub + siblings (it has no client registry); that's a follow-up if wanted.
42
+ - **Known limitation — a membership-spoof presence oracle (security review, medium).** The fan-out targets a builder's `<redacted>`, and `POST /sso/membership/check-in` (ADR 0141 §3) trusts a caller-named `github_id` under any active `client_secret`. So a client-secret holder can assert a membership for an arbitrary `github_id` and thereafter receive a hub-signed logout callback naming that builder on each of their hub logouts — a cross-account presence/existence oracle. It is **contained** (the token is `aud`-bound to the attacker's own `client_id`, so it can't be replayed against the victim's real instances — no session compromise) and **gated** (an `sso_client` is registered only by the operator-run `provision.js`, never a public route, so today every secret-holder is an operator-provisioned, trusted instance — the only client is `demo`). The root cause predates this ADR; this feature newly turns it into an active signed callback. **The proper fix — bind the fan-out to evidence of a real federated session for that `github_id` (a per-SSO-session ledger), or prove user consent at check-in — is the deferred follow-up (task [#1002244](https://cloudbongos.com/builders#/task/1002244)); it must land before federation opens to untrusted third-party clients.**
@@ -0,0 +1,44 @@
1
+ # 0145 — The Dev Box app becomes a branding-driven module (one source, per-instance builds)
2
+
3
+ - **Status:** Accepted — Phase 1 built in task [#1002255](https://cloudbongos.com/builders#/task/1002255). Downloads (1002257), CI + releases repo + signing (1002258), and end-to-end verify (1002256) follow.
4
+ - **Date:** 2026-07-15
5
+ - **Deciders:** Lars (owner) — "re-set up the dev box app so it's properly installable for cloudbongos; the design should not be linked to a specific project but to the Cloud Bongos design; it should live on cloudbongos and be maintained there as a module, and Example should borrow it."
6
+ - **Extends:** ADR 0045 (the Dev Box desktop app), ADR 0072 (app signing posture), ADR 0083 (module system), ADR 0004/0062 (brand discipline — no hardcoded host identity), [branding contract](../branding-contract.md).
7
+
8
+ ## Context
9
+
10
+ The Dev Box desktop app (ADR 0045 — an Electron menu-bar/tray switch that pairs sign-in, flips the box on/off, and wires the local SSH + Claude-Desktop last mile) was **built for Example and lived outside the Cloud Bongos core**:
11
+
12
+ - Its source was only in the OTB monolith (`~/dev/Claude-OTB/devbox-app/`), never in the neutral core.
13
+ - It was **hardcoded to Example/OTB**: `src/brand.js` (`CIV_NAME: 'Example'`, `PRODUCT_NAME: 'OTB Dev Box'`, `example.com` origins), `appId com.offtheboats.devbox`, artifact names `OTB-Dev-Box-*`, the renderer's parchment/ancient-Greek theme (Cinzel/EB Garamond, tan `#ece0c2`) and an archaic "thy/thee" voice ("a gift of Hermes").
14
+ - cloudbongos already ships the *download-redirect server* (`src/devbox-downloads.js`) but with no `config/devbox-app.json` and no Cloud-Bongos app to point it at — so a cloudbongos builder had **no installable app**, only the browser terminal + manual SSH.
15
+
16
+ ## Decision
17
+
18
+ Vendor the app into the dev-box module and make **every brand input a build-time value read from the instance branding**, so one source tree builds any instance's app.
19
+
20
+ 1. **House it in the module:** `modules/dev-box/app/` (40 files, ~600K; `node_modules`/`dist` excluded). The desktop app is not a formal module `contributes` type (the manifest schema has no "app" — routes/pollers/uiSections/webSurfaces only), so it lives as **source inside the module**; `module.json` is unchanged. The generic `src/devbox-downloads.js` stays in core and reads the instance's `config/devbox-app.json` (Phase 2).
21
+
22
+ 2. **Build-time brand generator** — `scripts/gen-brand.js` reads `config/branding.json` (→ `branding.neutral.json` fallback, the Cloud Bongos pack) and emits three generated, committed artifacts:
23
+ - `src/brand.generated.json` — `PRODUCT_NAME`, `APP_ID` (`com.<worldslug>.devbox`), `PRODUCT_SLUG` (artifact filenames), origins, `DESKTOP_ENV_NAME`, `COPYRIGHT`, `CONFIG_DIR`, `BG_COLOR`, `TAGLINE`. `src/brand.js` is now just `module.exports = require('./brand.generated.json')`.
24
+ - `renderer/theme.generated.css` — the `:root` design tokens (palette + fonts) from `theme.ui`; `styles.css` keeps only layout and consumes the vars, so the panel re-skins entirely from branding.
25
+ - `electron-builder.yml` — rendered from `electron-builder.tmpl.yml` (appId/productName/copyright/artifact slug substituted; electron-builder's own `${version}/${arch}` left literal).
26
+ It runs in `prep` (→ `prestart`/`pretest`/`predist:*`), so every build regenerates for the building instance. Same source → **"Cloud Bongos Dev Box" here, "Example Dev Box" there** — the ADR 0004 posture generalised to every instance, enforced by the `no hardcoded host identity` fitness gate (which this now passes).
27
+
28
+ 3. **Example borrows it as a module** — it enables `dev-box` and its build runs `gen-brand` against its own `config/branding.json`, producing the OTB-branded app from the shared source. No forked copy.
29
+
30
+ 4. **Functional cross-component constants stay** (not "design", so not rebranded): the ttyd terminal user `otb` and the SSH key filename `~/.ssh/otb_builder` are contracts with the box image (`box-terminal.service`, the authorized-keys fetch, `/builder-connect`); the `window.otb` renderer↔preload bridge is an internal namespace. The session file dir, however, **is** branding-driven now (`~/.config/<configDir>/gds-session.json`) so pairing the app still signs in the instance's own `/builder-*` CLI.
31
+
32
+ ## Consequences
33
+
34
+ - The neutral core now carries a **Cloud-Bongos-branded** Dev Box app by default (warm palette, Baloo 2/Quicksand, plain modern voice, `com.cloudbongos.devbox`). Committed generated defaults come from `branding.neutral.json` (Cloud Bongos brand pack; `localhost` origins are the neutral dev default — a real instance build bakes its own origins + the runtime session/API base overrides them anyway).
35
+ - Verified Phase 1: `gen-brand` renders cleanly; the app's 56 unit tests pass (the one OTB-hardcoded assertion is now brand-agnostic); `node --check` clean; the client-vendor path fixed for the two-levels-deeper location; **all architecture fitness checks pass** (module→doorway-only, no hardcoded host identity, repo-map fresh).
36
+ - The **API/builders origins the distributed app pairs against must be baked from the instance branding at build time** — so the real cloudbongos installer must be built with the cloudbongos instance `config/branding.json` (`cloudbongos.com`), not the neutral core's `localhost`. This shapes Phase 3 (where the branded build runs).
37
+
38
+ ## Follow-ups (open)
39
+
40
+ 1. **Downloads (task 1002257) — DONE 2026-07-17.** `config/devbox-app.json` pushed to the instance repo → `cloudbongos.com/downloads/devbox/*` 302 to the v1.0.10 release assets (all targets verified through Caddy). ⚠ **Vendored-config quirk:** `serve-internal.js` reads the config via a naive `require('../../config/devbox-app.json')` that in a *vendored* instance resolves to `node_modules/@bongos/core/config/`, **not** the instance root where the file lives (unlike branding/modules). Workaround: copied the config there on the host + restarted `cloudbongos.service` — **lost on next core upgrade / `npm ci`**. Proper instance-root-resolve fix = **task 1002371** (same class as ADR 0144's `infra/`).
41
+ 2. **Distribution (task 1002258) — DONE 2026-07-17.** CI `.github/workflows/build-devbox-app.yml` builds **signed + notarized** mac (arm64+x64) + win (x64+arm64); public releases repo `example-owner/cloudbongos-devbox-releases` hosts them; **v1.0.10 published**. Release origins baked via CI repo variables `DEVBOX_PUBLIC_ORIGIN`/`_BUILDERS_ORIGIN` (= cloudbongos.com). Gotcha found live: notarization 403'd until the owner accepted a lapsed **Apple Developer Program License Agreement** — then the signed+notarized build went green. Publishing = download CI artifacts → `gh release create/upload` (manual, no cross-repo CI token; ADR 0045).
42
+ 3. **App icon still OTB art.** `scripts/gen-icons.js` draws the app/tray/DMG icons from **pixel grids in the script** (currently the OTB design); the committed PNGs are OTB. Regenerating them from the Cloud Bongos bongo motif (edit the grids + re-run `npm run icons`) is a distinct art follow-up — the app's *name/colors/voice* are Cloud Bongos, but the *icon* is not yet.
43
+ 4. **Dev/CI env-var names** (`OTB_DEVBOX_API_BASE`/`_SCREENSHOT_DIR`/`_AUTOFLOW`) keep the `OTB_` prefix — harmless dev hooks, not user-visible; prefix-neutralising them is optional polish.
44
+ 5. **ADR number collision:** `0144` is used twice on this branch (`devbox-rehome` + `federated-single-logout`, from a parallel merge). Cosmetic; a renumber is worth a follow-up.
@@ -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).