@bongos/core 1.23.21 → 1.24.1

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 (457) hide show
  1. package/.bongos-core.json +983 -428
  2. package/.claude/skills/blocker-review/SKILL.md +9 -1
  3. package/.claude/skills/blocker-solve/SKILL.md +1 -1
  4. package/.claude/skills/builder-claim/SKILL.md +1 -1
  5. package/.claude/skills/builder-reauth/SKILL.md +1 -1
  6. package/.claude/skills/builder-redteam/SKILL.md +1 -1
  7. package/.claude/skills/builder-sequence/SKILL.md +1 -1
  8. package/.claude/skills/builder-setup/SKILL.md +1 -1
  9. package/.claude/skills/builder-ship/SKILL.md +1 -1
  10. package/.claude/skills/collab-review/SKILL.md +5 -2
  11. package/.claude/skills/design/SKILL.md +1 -1
  12. package/.claude/skills/goal-create/SKILL.md +6 -3
  13. package/.claude/skills/grade-recover/SKILL.md +1 -1
  14. package/.claude/skills/merge-mode/SKILL.md +1 -1
  15. package/.claude/skills/owner-review/SKILL.md +1 -1
  16. package/.claude/skills/planning-session/SKILL.md +11 -11
  17. package/.claude/skills/read-session-export/SKILL.md +1 -1
  18. package/.claude/skills/recall/SKILL.md +1 -1
  19. package/.claude/skills/scan-before-install/SKILL.md +1 -1
  20. package/.claude/skills/session-handoff/SKILL.md +1 -1
  21. package/.claude/skills/strand-fix/SKILL.md +13 -0
  22. package/.claude/skills/worktree-clean/SKILL.md +1 -1
  23. package/AGENTS.md +15 -0
  24. package/GEMINI.md +5 -0
  25. package/clients/bongos-client/README.md +1 -1
  26. package/clients/bongos-client/bongos-client.global.js +94 -0
  27. package/clients/bongos-client/index.cjs +94 -0
  28. package/clients/bongos-client/index.d.ts +154 -4
  29. package/clients/bongos-client/index.mjs +94 -0
  30. package/docs/adr/0054-session-token-reward-cost-plus.md +3 -1
  31. package/docs/adr/0055-server-mediated-branch-publish.md +1 -1
  32. package/docs/adr/0385-money-lives-with-a-processor-credits-stay-a-project-meter.md +87 -0
  33. package/docs/adr/0386-the-funding-wall-is-a-per-project-rule-claim-time-hold-by-default.md +37 -0
  34. package/docs/adr/0387-two-ledgers-credits-measure-work-money-stays-with-the-processor.md +39 -0
  35. package/docs/adr/0388-a-module-sale-is-split-by-the-project-the-buyer-pays-the-platform-fee.md +42 -0
  36. package/docs/adr/0392-ai-generation-runs-on-a-processor-held-balance-and-the-builders-own-keys.md +33 -0
  37. package/docs/adr/0393-a-platform-account-is-a-person-and-github-is-one-way-in.md +37 -0
  38. package/docs/adr/0394-market-watch-searches-through-the-messages-api-on-the-projects-key-and-runs-from-its-own-poller.md +102 -0
  39. package/docs/adr/0395-a-task-says-what-it-needs-and-lists-show-only-what-you-can-do.md +36 -0
  40. package/docs/adr/0396-google-sign-in-a-stand-in-login-and-a-browser-approved-cli-sign-in.md +41 -0
  41. package/docs/adr/README.md +10 -1
  42. package/docs/api/openapi.json +8619 -5885
  43. package/docs/api-reference.md +68 -9
  44. package/docs/architecture.md +37 -1
  45. package/docs/canonical-permissions.md +1 -1
  46. package/docs/copy-inventory.md +420 -337
  47. package/docs/copy-registry.json +1321 -500
  48. package/docs/file-map.md +4 -0
  49. package/docs/module-api-changelog.md +7 -0
  50. package/docs/onboarding/diagrams/03-drachmae-karma.mmd +3 -3
  51. package/docs/onboarding/diagrams/README.md +1 -1
  52. package/docs/onboarding/drachmae-and-karma.md +15 -15
  53. package/docs/onboarding/primer.md +2 -2
  54. package/docs/page-inventory.json +55 -6
  55. package/docs/page-readings.json +1185 -1011
  56. package/docs/recipes/google-sign-in.md +60 -0
  57. package/docs/recipes/ops-gotchas.md +1 -1
  58. package/migrations/core_282_builders_lower_login_idx.sql +33 -0
  59. package/migrations/core_283_claimable_ignores_page_markers.sql +53 -0
  60. package/migrations/core_283_payout_approvals.sql +183 -0
  61. package/migrations/core_284_payout_gates.sql +179 -0
  62. package/migrations/core_285_task_needs.sql +74 -0
  63. package/modules/ai-studio/CLAUDE.md +15 -0
  64. package/modules/ai-studio/billing.js +67 -0
  65. package/modules/ai-studio/context.js +29 -0
  66. package/modules/ai-studio/db.js +172 -0
  67. package/modules/ai-studio/gemini.js +75 -0
  68. package/modules/ai-studio/generate.js +119 -0
  69. package/modules/ai-studio/keys.js +73 -0
  70. package/modules/ai-studio/migrations/ai_studio_001_keys.sql +22 -0
  71. package/modules/ai-studio/migrations/ai_studio_002_ledger.sql +48 -0
  72. package/modules/ai-studio/migrations/ai_studio_003_requests.sql +55 -0
  73. package/modules/ai-studio/module.json +26 -0
  74. package/modules/ai-studio/openrouter.js +90 -0
  75. package/modules/ai-studio/payments.js +105 -0
  76. package/modules/ai-studio/pricing.js +92 -0
  77. package/modules/ai-studio/routes/ai-studio.js +252 -0
  78. package/modules/ai-studio/studio.js +121 -0
  79. package/modules/autonomy/fence.js +64 -1
  80. package/modules/builder-settings/builder-needs.js +27 -1
  81. package/modules/copy-desk/routes/copy-desk.js +5 -0
  82. package/modules/economy/achievements.js +22 -10
  83. package/modules/economy/cost-classify.js +9 -0
  84. package/modules/economy/cost-write-guard.js +52 -0
  85. package/modules/economy/cost.js +2 -1
  86. package/modules/economy/credits.js +18 -15
  87. package/modules/economy/goal-reward.js +161 -0
  88. package/modules/economy/reward.js +4 -0
  89. package/modules/economy/routes/cost.js +33 -7
  90. package/modules/government/catalog.js +6 -0
  91. package/modules/government/db.js +108 -0
  92. package/modules/government/docket.js +36 -0
  93. package/modules/government/migrations/government_035_payout_approve.sql +29 -0
  94. package/modules/government/pollers/board-expiry.js +108 -20
  95. package/modules/government/protected-surfaces.json +1 -0
  96. package/modules/government/routes/government.js +50 -18
  97. package/modules/government/routing.js +39 -5
  98. package/modules/government/waiting.js +74 -0
  99. package/modules/grading/grader-subagent.js +30 -11
  100. package/modules/hall-ui/public/ai-studio-lib.js +268 -0
  101. package/modules/hall-ui/public/ai-studio.css +193 -0
  102. package/modules/hall-ui/public/ai-studio.html +98 -0
  103. package/modules/hall-ui/public/ai-studio.js +556 -0
  104. package/modules/hall-ui/public/ai-studio.states.json +94 -0
  105. package/modules/hall-ui/public/blockers-by-person.js +156 -0
  106. package/modules/hall-ui/public/blockers-lib.js +15 -1
  107. package/modules/hall-ui/public/blockers.html +8 -0
  108. package/modules/hall-ui/public/blockers.js +1 -1
  109. package/modules/hall-ui/public/city.html +15 -15
  110. package/modules/hall-ui/public/claim-action.js +3 -0
  111. package/modules/hall-ui/public/collab.html +12 -0
  112. package/modules/hall-ui/public/craft-mode.js +4 -1
  113. package/modules/hall-ui/public/criterion-uat-ui.js +4 -1
  114. package/modules/hall-ui/public/drachmae.html +1 -1
  115. package/modules/hall-ui/public/genesis/r5-life-1-primordial-sea.jpg +0 -0
  116. package/modules/hall-ui/public/genesis/r5-life-2-first-cells-cut.webp +0 -0
  117. package/modules/hall-ui/public/genesis/r5-life-2-first-cells.jpg +0 -0
  118. package/modules/hall-ui/public/genesis/r5-life-3-genome-cut.webp +0 -0
  119. package/modules/hall-ui/public/genesis/r5-life-3-genome.jpg +0 -0
  120. package/modules/hall-ui/public/genesis/r5-life-4-membrane-cut.webp +0 -0
  121. package/modules/hall-ui/public/genesis/r5-life-4-membrane.jpg +0 -0
  122. package/modules/hall-ui/public/genesis/r5-life-5-nervous-system-cut.webp +0 -0
  123. package/modules/hall-ui/public/genesis/r5-life-5-nervous-system.jpg +0 -0
  124. package/modules/hall-ui/public/goal-inbox.js +1 -1
  125. package/modules/hall-ui/public/goals-render.js +4 -0
  126. package/modules/hall-ui/public/goals.html +8 -0
  127. package/modules/hall-ui/public/gov-living.js +6 -2
  128. package/modules/hall-ui/public/hall-ask.css +5 -0
  129. package/modules/hall-ui/public/hall-ask.js +39 -3
  130. package/modules/hall-ui/public/notes-panel.css +20 -0
  131. package/modules/hall-ui/public/notes-panel.js +146 -0
  132. package/modules/hall-ui/public/project-settings.html +1 -1
  133. package/modules/hall-ui/public/project-settings.js +7 -5
  134. package/modules/hall-ui/public/settings-ai-keys.js +124 -0
  135. package/modules/hall-ui/public/settings.html +15 -0
  136. package/modules/hall-ui/public/street-deck.js +12 -3
  137. package/modules/hall-ui/public/studio.css +90 -8
  138. package/modules/hall-ui/public/studio.html +39 -10
  139. package/modules/hall-ui/public/studio.js +148 -18
  140. package/modules/hall-ui/public/task.html +2 -0
  141. package/modules/hall-ui/public/task.js +4 -0
  142. package/modules/hall-ui/public/tweak-editor.js +12 -3
  143. package/modules/hall-ui/public/welcome-boot.css +42 -6
  144. package/modules/hall-ui/public/welcome-boot.js +20 -6
  145. package/modules/hall-ui/public/work.js +20 -0
  146. package/modules/hall-ui/records/ai-studio.md +36 -0
  147. package/modules/hall-ui/records/blockers-people.md +24 -0
  148. package/modules/lifecycle/db-analytics.js +10 -3
  149. package/modules/lifecycle/db-claims.js +32 -4
  150. package/modules/lifecycle/db-goals.js +48 -18
  151. package/modules/lifecycle/db-shared.js +9 -0
  152. package/modules/lifecycle/db-tasks.js +15 -6
  153. package/modules/lifecycle/db-versions.js +21 -8
  154. package/modules/lifecycle/done-when.js +23 -1
  155. package/modules/lifecycle/lifecycle.js +5 -0
  156. package/modules/lifecycle/migrations/lifecycle_017_notes.sql +46 -0
  157. package/modules/lifecycle/module.json +2 -1
  158. package/modules/lifecycle/notes.js +219 -0
  159. package/modules/lifecycle/page-notice.js +124 -0
  160. package/modules/lifecycle/page-tweak-artist.js +18 -1
  161. package/modules/lifecycle/publish-reconciler.js +67 -20
  162. package/modules/lifecycle/routes/claims.js +5 -2
  163. package/modules/lifecycle/routes/goals.js +3 -1
  164. package/modules/lifecycle/routes/notes.js +91 -0
  165. package/modules/lifecycle/routes/project-settings.js +43 -35
  166. package/modules/lifecycle/routes/task-route-helpers.js +21 -0
  167. package/modules/lifecycle/routes/task-write-routes.js +21 -3
  168. package/modules/lifecycle/routes/tasks.js +12 -12
  169. package/modules/lifecycle/ship-assurance.js +4 -1
  170. package/modules/lifecycle/ship-card.js +8 -2
  171. package/modules/lifecycle/task-needs-gate.js +70 -0
  172. package/modules/market-watch/CLAUDE.md +15 -0
  173. package/modules/market-watch/db.js +340 -0
  174. package/modules/market-watch/key.js +58 -0
  175. package/modules/market-watch/migrations/market_watch_001_tables.sql +249 -0
  176. package/modules/market-watch/migrations/market_watch_002_run.sql +46 -0
  177. package/modules/market-watch/module.json +26 -0
  178. package/modules/market-watch/pollers/hub-seed.js +54 -0
  179. package/modules/market-watch/pollers/weekly-run.js +100 -0
  180. package/modules/market-watch/research.js +350 -0
  181. package/modules/market-watch/run.js +245 -0
  182. package/modules/market-watch/seed/market-data.json +38 -0
  183. package/modules/market-watch/seed/partners-data.json +497 -0
  184. package/modules/market-watch/seed.js +252 -0
  185. package/modules/market-watch/store.js +230 -0
  186. package/modules/memory/routes/memory.js +14 -2
  187. package/modules/memory/routes/search.js +8 -2
  188. package/modules/onboarding/howto-moments.js +8 -1
  189. package/modules/platform-identity/account-logins.js +192 -0
  190. package/modules/platform-identity/migrations/platform_identity_034_account_logins.sql +96 -0
  191. package/modules/platform-identity/platform-identity.js +5 -4
  192. package/modules/platform-identity/routes/my-projects.js +6 -0
  193. package/modules/platform-identity/routes/profile.js +18 -0
  194. package/modules/platform-identity/routes/public-profile.js +10 -1
  195. package/modules/platform-identity/routes/sso.js +26 -1
  196. package/modules/platform-identity/tests/platform-identity.mjs +2 -1
  197. package/modules/provisioning/credential-preflight.js +13 -3
  198. package/modules/provisioning/pollers/app-liveness.js +25 -5
  199. package/modules/provisioning/routes/provisioning.js +1 -1
  200. package/modules/provisioning/starter-bundles.js +8 -2
  201. package/modules/public-landing/public/account.html +10 -2
  202. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg +0 -0
  203. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
  204. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
  205. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +1 -1
  206. package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp +0 -0
  207. package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp.json +1 -1
  208. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg +0 -0
  209. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
  210. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
  211. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +1 -1
  212. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg +0 -0
  213. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
  214. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
  215. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +1 -1
  216. package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp +0 -0
  217. package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp.json +1 -1
  218. package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp +0 -0
  219. package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp.json +1 -1
  220. package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp +0 -0
  221. package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp.json +1 -1
  222. package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp +0 -0
  223. package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp.json +1 -1
  224. package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp +0 -0
  225. package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp.json +1 -1
  226. package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp +0 -0
  227. package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp.json +1 -1
  228. package/modules/public-landing/public/assets/world/r3-space-nebula.jpg +0 -0
  229. package/modules/public-landing/public/assets/world/r3-space-nebula.jpg.json +1 -1
  230. package/modules/public-landing/public/assets/world/r4-card-chrome.jpg +0 -0
  231. package/modules/public-landing/public/assets/world/r4-card-chrome.jpg.json +1 -1
  232. package/modules/public-landing/public/assets/world/r4-card-nebula.jpg +0 -0
  233. package/modules/public-landing/public/assets/world/r4-card-nebula.jpg.json +1 -1
  234. package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg +0 -0
  235. package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg.json +1 -1
  236. package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp +0 -0
  237. package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp.json +1 -1
  238. package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg +0 -0
  239. package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg.json +1 -1
  240. package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp +0 -0
  241. package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp.json +1 -1
  242. package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg +0 -0
  243. package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg.json +1 -1
  244. package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp +0 -0
  245. package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp.json +1 -1
  246. package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg +0 -0
  247. package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg.json +1 -1
  248. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp +0 -0
  249. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp.json +1 -1
  250. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg +0 -0
  251. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg.json +1 -1
  252. package/modules/public-landing/public/community.html +1 -1
  253. package/modules/public-landing/public/connections.html +2 -2
  254. package/modules/public-landing/public/guild.html +1 -1
  255. package/modules/public-landing/public/invitations.html +2 -2
  256. package/modules/public-landing/public/projects.html +5 -5
  257. package/modules/raise/views/nda.html +2 -0
  258. package/modules/specialities/db.js +32 -1
  259. package/modules/specialities/routes/specialities.js +10 -6
  260. package/modules/status-ui/public/index.html +1 -1
  261. package/package-lock.json +2 -2
  262. package/package.json +1 -1
  263. package/release-notes.json +286 -0
  264. package/scripts/bongos/autobongos-tweak-lane.js +46 -1
  265. package/scripts/bongos/backfill-subagent-rewards.js +2 -2
  266. package/scripts/bongos/claim.js +22 -1
  267. package/scripts/bongos/db-backup-nightly.sh +29 -0
  268. package/scripts/bongos/deploy-divergence.js +7 -0
  269. package/scripts/bongos/doc-cli-guard.js +6 -0
  270. package/scripts/bongos/fitness.js +3 -2
  271. package/scripts/bongos/gen-diagrams.js +3 -3
  272. package/scripts/bongos/lib.sh +4 -5
  273. package/scripts/bongos/migration-namespace.js +13 -1
  274. package/scripts/bongos/plain-cards.js +4 -0
  275. package/scripts/bongos/provision-github-setup.js +3 -1
  276. package/scripts/bongos/provision-units.js +6 -1
  277. package/scripts/bongos/publish-manifest.js +4 -0
  278. package/scripts/bongos/release.js +2 -0
  279. package/scripts/bongos/run-unit-tests.js +268 -82
  280. package/scripts/bongos/seed-tasks.example.json +56 -0
  281. package/scripts/bongos/seed-tasks.js +222 -0
  282. package/scripts/bongos/seed-verify-criteria.js +1 -2
  283. package/scripts/bongos/ship-finish.js +2 -0
  284. package/scripts/bongos/ship-resume.js +2 -0
  285. package/scripts/bongos/strand-lease.js +250 -0
  286. package/scripts/bongos/strand-watch.js +129 -12
  287. package/scripts/bongos/version-literal-guard.js +2 -2
  288. package/scripts/discord/post.js +4 -3
  289. package/scripts/gds/seed-tasks.js +7 -0
  290. package/scripts/gds/{build-bongos.js → strand-lease.js} +3 -3
  291. package/scripts/hall-preview/server.js +21 -0
  292. package/src/bongos/auth-config.js +29 -1
  293. package/src/bongos/auth-discord.js +3 -3
  294. package/src/bongos/auth-github.js +3 -6
  295. package/src/bongos/auth-google.js +217 -0
  296. package/src/bongos/device-approval.js +140 -0
  297. package/src/bongos/llm-pricing.js +17 -1
  298. package/src/bongos/metrics.js +48 -2
  299. package/src/bongos/middleware/rate-limit.js +30 -11
  300. package/src/bongos/module-scope-map.js +16 -1
  301. package/src/bongos/optimizer.js +15 -1
  302. package/src/bongos/payout-approvals.js +342 -0
  303. package/src/bongos/payout-gates.js +364 -0
  304. package/src/bongos/prelaunch-gate.js +7 -9
  305. package/src/bongos/prelaunch-switch.js +20 -0
  306. package/src/bongos/route-rank-check.js +3 -0
  307. package/src/bongos/routes/auth-google.js +246 -0
  308. package/src/bongos/routes/auth.js +38 -1
  309. package/src/bongos/routes/backup.js +33 -9
  310. package/src/bongos/routes/builders.js +57 -30
  311. package/src/bongos/routes/github.js +9 -0
  312. package/src/bongos/routes/me.js +18 -0
  313. package/src/bongos/routes/my-payouts.js +133 -0
  314. package/src/bongos/routes/payout-approvals.js +312 -0
  315. package/src/bongos/routes/payout-gates.js +179 -0
  316. package/src/bongos/routes.js +15 -1
  317. package/src/bongos/secret-box.js +18 -3
  318. package/src/bongos/serve-internal.js +47 -19
  319. package/src/bongos/sign-in-pages.js +138 -0
  320. package/src/bongos/task-needs.js +146 -0
  321. package/src/bongos-downloads.js +18 -1
  322. package/src/instance-config.js +28 -0
  323. package/src/module-api.js +14 -1
  324. package/src/platform-server.js +27 -1
  325. package/tests/account_logins.mjs +326 -0
  326. package/tests/account_logins_db.mjs +161 -0
  327. package/tests/ai_studio_billing.mjs +253 -0
  328. package/tests/ai_studio_generate.mjs +282 -0
  329. package/tests/ai_studio_keys.mjs +159 -0
  330. package/tests/ai_studio_requests.mjs +319 -0
  331. package/tests/auth_backchannel_logout_route.mjs +201 -0
  332. package/tests/auto_satisfy_criteria.mjs +10 -1
  333. package/tests/autobongos_tweak_lane.mjs +144 -0
  334. package/tests/autobongos_tweak_pin_identity.mjs +137 -0
  335. package/tests/backfill_subagent_rewards.mjs +17 -1
  336. package/tests/backup_trigger_lock.mjs +119 -0
  337. package/tests/batched_writes_parity.mjs +340 -0
  338. package/tests/blockers_people.mjs +198 -0
  339. package/tests/bounded_rate_maps.mjs +184 -0
  340. package/tests/claimable_blocker_status.mjs +1 -1
  341. package/tests/claims_batch_routes.mjs +421 -0
  342. package/tests/conductor_main_e2e.mjs +4 -0
  343. package/tests/config_read_cache.mjs +171 -0
  344. package/tests/copy_desk_page_editor.mjs +12 -0
  345. package/tests/cost_write_guard.mjs +200 -0
  346. package/tests/craft_achievements.mjs +33 -10
  347. package/tests/criterion_goal_attach.mjs +1 -1
  348. package/tests/db_backup_nightly.mjs +73 -1
  349. package/tests/deploy_divergence_line.mjs +7 -0
  350. package/tests/effective_visibility_predicate.mjs +12 -0
  351. package/tests/fitness.mjs +19 -0
  352. package/tests/fixtures/per-row-loops-legacy.cjs +245 -0
  353. package/tests/fixtures/seed-tasks/bongos-v1-idea-routing.json +256 -0
  354. package/tests/fixtures/seed-tasks/bv1-profiles.json +592 -0
  355. package/tests/fixtures/seed-tasks/bv2-ideator-surface.json +373 -0
  356. package/tests/fixtures/seed-tasks/fake-api.mjs +80 -0
  357. package/tests/github_one_trip.mjs +34 -0
  358. package/tests/github_repo_visibility.mjs +20 -0
  359. package/tests/goal_achievement.mjs +30 -1
  360. package/tests/goal_achievement_reward.mjs +205 -0
  361. package/tests/goal_archive_disposition.mjs +5 -5
  362. package/tests/goal_sort_staleness.mjs +2 -1
  363. package/tests/goal_tier.mjs +11 -1
  364. package/tests/google_fonts_nonblocking.mjs +84 -0
  365. package/tests/google_sign_in.mjs +401 -0
  366. package/tests/government_board_expiry.mjs +94 -0
  367. package/tests/government_protected_surfaces.mjs +1 -1
  368. package/tests/government_routes.mjs +146 -23
  369. package/tests/grader.mjs +31 -0
  370. package/tests/hall_ai_studio.mjs +347 -0
  371. package/tests/hall_city.mjs +8 -0
  372. package/tests/hall_page_gate_map.mjs +3 -0
  373. package/tests/hall_studio_home.mjs +61 -2
  374. package/tests/home_attention_cards.mjs +1 -1
  375. package/tests/hook_shell_tools.mjs +8 -0
  376. package/tests/hooks_drain.mjs +1 -1
  377. package/tests/host_topology_skips.mjs +2 -0
  378. package/tests/howto_moments.mjs +17 -0
  379. package/tests/human_signal.mjs +141 -0
  380. package/tests/install_script_reader.mjs +51 -0
  381. package/tests/integration_fixture_ids.mjs +82 -0
  382. package/tests/invite_expiry_db.mjs +13 -1
  383. package/tests/lib_sh_resolution.mjs +0 -4
  384. package/tests/market_watch_data.mjs +244 -0
  385. package/tests/market_watch_data_db.mjs +308 -0
  386. package/tests/market_watch_module.mjs +114 -0
  387. package/tests/market_watch_run.mjs +503 -0
  388. package/tests/metrics.mjs +124 -0
  389. package/tests/migration_namespace.mjs +14 -0
  390. package/tests/module_api.mjs +2 -0
  391. package/tests/module_loader.mjs +1 -1
  392. package/tests/module_skills_hook.mjs +69 -0
  393. package/tests/notes.mjs +182 -0
  394. package/tests/own_scope_guard.mjs +5 -0
  395. package/tests/page_tweak_marker_sql.mjs +76 -0
  396. package/tests/page_tweak_no_block.mjs +135 -0
  397. package/tests/page_tweak_notice.mjs +103 -0
  398. package/tests/payout_approvals.mjs +253 -0
  399. package/tests/payout_approvals_db.mjs +106 -0
  400. package/tests/payout_gates.mjs +322 -0
  401. package/tests/payout_gates_db.mjs +113 -0
  402. package/tests/planning_session_skill.mjs +13 -3
  403. package/tests/project_settings_page.mjs +29 -5
  404. package/tests/project_settings_spend_cap_route.mjs +235 -0
  405. package/tests/promotion_sweep.mjs +29 -1
  406. package/tests/provision.mjs +3 -2
  407. package/tests/provisioning_app_status.mjs +18 -0
  408. package/tests/provisioning_credential_preflight.mjs +53 -0
  409. package/tests/provisioning_env_manifest_edit_delete.mjs +223 -0
  410. package/tests/public_landing_projects.mjs +37 -0
  411. package/tests/publish_reconciler_overlap.mjs +60 -0
  412. package/tests/rank_change_broadcast_port.mjs +151 -0
  413. package/tests/seed_tasks.mjs +176 -0
  414. package/tests/seed_verify_criteria.mjs +2 -6
  415. package/tests/session_start_freshness.mjs +66 -1
  416. package/tests/shell_hooks.mjs +209 -0
  417. package/tests/ship_assurance.mjs +28 -0
  418. package/tests/ship_card.mjs +20 -5
  419. package/tests/site_classified_once.mjs +142 -0
  420. package/tests/speciality_db_shape.mjs +23 -0
  421. package/tests/speciality_session_skills.mjs +48 -2
  422. package/tests/strand_lease.mjs +249 -0
  423. package/tests/strand_watch.mjs +58 -0
  424. package/tests/task_criteria_routes.mjs +275 -0
  425. package/tests/task_criteria_unlink_db.mjs +123 -0
  426. package/tests/task_needs.mjs +233 -0
  427. package/tests/task_needs_db.mjs +110 -0
  428. package/tests/unit_runner_serial.mjs +266 -43
  429. package/tests/version_close_route.mjs +1 -1
  430. package/tests/version_literals.mjs +5 -7
  431. package/tests/welcome_boot.mjs +16 -5
  432. package/tests/welcome_boot_browser.mjs +36 -4
  433. package/docs/recipes/bongos-cli-release.md +0 -66
  434. package/scripts/bongos/backfill-disciplines.js +0 -144
  435. package/scripts/bongos/backfill-session-earnings.js +0 -123
  436. package/scripts/bongos/backfill-session-types.js +0 -87
  437. package/scripts/bongos/build-bongos.js +0 -94
  438. package/scripts/bongos/cleanup-orphans.js +0 -74
  439. package/scripts/bongos/seed-bongos-v1-idea-routing-tasks.js +0 -152
  440. package/scripts/bongos/seed-bv1-profiles-tasks.js +0 -135
  441. package/scripts/bongos/seed-bv2-ideator-surface-tasks.js +0 -390
  442. package/scripts/bongos/smoke-batch-claim.sh +0 -238
  443. package/scripts/bongos/smoke-batch-release.sh +0 -316
  444. package/scripts/bongos/smoke-claim-consistency.sh +0 -222
  445. package/scripts/bongos/smoke-dependencies.sh +0 -143
  446. package/scripts/gds/backfill-disciplines.js +0 -7
  447. package/scripts/gds/backfill-session-earnings.js +0 -7
  448. package/scripts/gds/backfill-session-types.js +0 -7
  449. package/scripts/gds/cleanup-orphans.js +0 -7
  450. package/scripts/gds/seed-bongos-v1-idea-routing-tasks.js +0 -6
  451. package/scripts/gds/seed-bv1-profiles-tasks.js +0 -6
  452. package/scripts/gds/seed-bv2-ideator-surface-tasks.js +0 -6
  453. package/scripts/gds/smoke-batch-claim.sh +0 -8
  454. package/scripts/gds/smoke-batch-release.sh +0 -8
  455. package/scripts/gds/smoke-claim-consistency.sh +0 -8
  456. package/scripts/gds/smoke-dependencies.sh +0 -8
  457. package/tests/smoke_dependencies_witness.mjs +0 -46
@@ -0,0 +1,102 @@
1
+ # 0394 — Market Watch searches the web through the Messages API on the project's own key, and runs from its own clock-anchored poller
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-10-29
5
+ - **Task:** [task 1004934](https://cloudbongos.com/builders#/task/1004934) (goal 1000137, the R01 spike)
6
+ - **Deciders:** Claude investigated and chose; the owner set the constraints (on/off per project, a monthly AI cap, paid by the project, fail closed)
7
+ - **Related:** ADR 0016 (trust boundary), 0054 (credits), 0073 (keys in Settings, secret box), 0083 (modules), 0089 §3 (ModelProvider port), **0385 (Bongos holds no money)**, **0392 (AI generation and own keys)**
8
+
9
+ ## Context
10
+
11
+ Market Watch (goal 1000137) is an opt-in, weekly, AI-run watch on a project's competitors, partners and topics. Each tracked item gets a dated log of sourced findings and a score history. The owner's rules: it is switched on per project, paid for by the project, capped monthly, and it fails closed. Before R04 ([task 1004937](https://cloudbongos.com/builders#/task/1004937)) builds the weekly run, two questions need an answer: **how does a run reach the web**, and **what fires it each week**.
12
+
13
+ "Per project" means per instance. Every project is its own instance, with its own database and server process (`modules/provisioning`; the hub is one of them). A run that lives in a project's server process cannot write to another project, because it holds no connection to one.
14
+
15
+ ### What exists today (read from the code, 2026-10-29)
16
+
17
+ **The grade port, `runSubagentCached`** (`modules/grading/grader-subagent.js`). It spawns the `claude` CLI: `claude -p --tools <opts.tools ?? 'Read'> --strict-mcp-config --permission-mode bypassPermissions`. The tool list is a passthrough string (task 1001691), so a caller *could* ask for `WebSearch,WebFetch`. Three things stop that from being the answer:
18
+ - **The server image has no `claude` CLI.** The `Dockerfile` installs `postgresql-client bash tini curl ca-certificates git` and `npm ci --omit=dev`. `package.json` carries `@anthropic-ai/sdk`, not the CLI. On a deployed instance the spawn fails with `SUBAGENT_SPAWN_FAILED` unless an operator adds the binary, sets `CLAUDE_BIN`, and signs it in. The port works where `ship.js` runs it: on a builder's machine.
19
+ - **The agents module never passes tools.** `modules/agents/spawn.js` sends `opts: { model }` only. Its header says v1 agents are "read-only by plumbing", and a write- or web-capable spawn needs a change in `grading`, which is protected.
20
+ - **The port's cache is wrong for news.** `runSubagentCached` replays a stored answer at $0 on a key hit. A watch that replays last week's search has found nothing.
21
+
22
+ **The agents module and the cron trigger.** `agents_005_cron_trigger.sql` made `'cron'` storable on `agents_definitions` and `agents_runs`, with a required `trigger_spec.cadence` (`weekly` among the allowed values). Nothing selects cron agents yet. Also, `spawn.js plannedRow` maps every trigger that is not `'on-demand'` to `'event'`. A cron fire through today's spawn path would be recorded in the ledger as an event fire. The agents module is `default: false`, and on the hub `GET /api/bongos/agents` answers **404**: it is not enabled there (checked 2026-10-29). Its spend guards (`lib/spend-ceiling.js`: per payer and per definition, both fail closed) bound agent fires. They do not express "this project's Market Watch budget".
23
+
24
+ **Why the cron-router poller was abandoned.** Task 1002498 (`modules/agents/pollers/cron-router.js`, one timer per cadence bucket) is `abandoned` with the note "WA6 review 2026-09-28: not necessary now (owner call)". There was no technical blocker. Its enabling bug (task 1004008, no `'cron'` type) shipped afterwards as agents_005, so the poller would select real rows today. But it would fire them through the grade port, which has no CLI on the server.
25
+
26
+ **The autonomy module's timers** (`modules/autonomy/pollers/`). Two patterns sit side by side:
27
+ - `routine-timers.js` uses a **boot-anchored** `setInterval` (peer-vote-tally, weekly). It first fires about 90 s after each boot. The hub redeploys on every merge, so in practice it runs once per deploy and almost never completes a 7-day interval. That is harmless for an idempotent tally and wrong for a paid run, which would be charged once per deploy.
28
+ - `daily-checks.js` uses a **clock-anchored** timer: an hourly unref'd tick asks the ledger whether the last run is due ("DAILY BY THE CLOCK, NOT BY THE BOOT"), with a 30-minute slack so runs do not drift. It is kill-switched by `ROUTINE_TIMERS_DISABLED=1` and never throws into the server.
29
+
30
+ Any module can contribute a poller of its own: `contributes.pollers` in `module.json`, started by the loader at boot (docs/modules-contract.md §Start pollers).
31
+
32
+ **How the server calls an LLM with tools today.** It doesn't. `modules/discord/discord-support.js` makes the one server-side Messages API call, through `@anthropic-ai/sdk`, with **no tools**, on an env-only, namespaced key (`DISCORD_SUPPORT_ANTHROPIC_API_KEY`). `modules/ai-studio` calls OpenRouter and Gemini over HTTPS.
33
+
34
+ **Keys in Settings.** Task 1004919 shipped `ai_studio_provider_keys`: **per-builder** keys for `openrouter` and `gemini` only (a CHECK constraint), encrypted with `api.secretBox`, never returned. There is no project-level key, no Anthropic key, and no search-API key in Settings.
35
+
36
+ ### Cost measurements
37
+
38
+ **Measured (n=2, 2026-10-29), the tool-enabled CLI path.** I ran the grade port's exact spawn shape (`claude -p --model claude-sonnet-5 --output-format json --tools WebSearch,WebFetch --strict-mcp-config`) on one real watch item each: "find this week's news on X, at most 2 searches and 2 fetches, answer as JSON entries with a source URL and a confidence level". The CLI's own `total_cost_usd` envelope:
39
+
40
+ | Item | Cost | Wall time | Searches | Sourced entries |
41
+ |---|---|---|---|---|
42
+ | Linear | $0.1156 | 41 s | 2 | 2 |
43
+ | Replit | $0.0964 | 23 s | 2 | 3+ |
44
+
45
+ Mean **$0.106 per item**, so **≈ $4.24 for a 40-item run**. About half of each item's cost is the CLI's own overhead: 34–43k cache-read tokens of its system prompt, plus a Haiku 4.5 sub-call that reads the search results.
46
+
47
+ **Estimated, the direct Messages API path chosen below.** These are the repo's own rates (`src/bongos/llm-pricing.js`: sonnet = Claude Sonnet 5, $2 in / $10 out per million tokens) and Anthropic's published web-search price, **$10 per 1,000 searches**, with web fetch billed as tokens only (from the claude-api reference, cached 2026-10-06; re-verify before R04 prices a cap). Per item: 2 searches = $0.020. About 25k tokens of search and page content read by Sonnet 5 = $0.050. About 2k tokens out, thinking included = $0.020. The watch rules, about 3k tokens, are cached across the run and cost close to nothing. That comes to **≈ $0.09 per item, ≈ $3.60 for 40 items**. This is not measured. The token volumes come from the two runs above, where the search content was 23k and 35k tokens.
48
+
49
+ **The planning figure is $4–5 per weekly 40-item run, about $17–22 a month.** The measured and estimated paths land within 20% of each other. **Cost does not decide between them; deployability does.**
50
+
51
+ ## Decision
52
+
53
+ ### 1. Web access: the Anthropic Messages API with server-side web tools, on the project's own key
54
+
55
+ R04 calls `@anthropic-ai/sdk` (already a dependency) **from the market-watch module's own code**, one request per tracked item. The request uses model `claude-sonnet-5`, the tools `web_search_20260209` (`max_uses: 3`) and `web_fetch_20260209` (`max_uses: 2`), a bounded `max_tokens`, and structured output for the entries. The watch rules come first, in the system prompt, under `cache_control`. A `refusal`, a `max_tokens` stop, or a tool error block (these arrive with HTTP 200 and do not raise) ends that item with a recorded error. It never becomes an entry.
56
+
57
+ **Not through the grade port.** That port spawns a CLI the server does not have, caches answers, and is tied to grading. This is a new LLM caller, the second server-side one after discord-support. Its model id stays a single constant in the module, so the ModelProvider port (ADR 0089 §3) can take it over later.
58
+
59
+ **The key is the project's, set in Settings.** The project owner pastes an Anthropic API key into Market Watch settings (R03, [task 1004938](https://cloudbongos.com/builders#/task/1004938)). It is stored with `api.secretBox`, shown only as its last four characters, never returned or logged, and decrypted only inside a run (the ai-studio rules). Because the key is the project's own account, the project pays the provider directly. Bongos holds no money (ADR 0385) and charges no markup (ADR 0392's multiplier applies to *our* key, and Market Watch has none). **No key means no run**: the run is recorded as skipped with reason `no_key`, and the page shows it.
60
+
61
+ ### 2. Scheduling: Market Watch's own clock-anchored poller, on the daily-checks pattern
62
+
63
+ The market-watch module declares `contributes.pollers: ["weekly-run"]`. That poller is a copy of the `autonomy/pollers/daily-checks.js` shape, not an import of it:
64
+ - one unref'd **hourly** tick (env-overridable), first tick a few minutes after boot plus jitter, kill-switched by `ROUTINE_TIMERS_DISABLED=1`, never throws into the server;
65
+ - **due by the ledger, not by the boot**: a run starts when Market Watch is on and the newest `market_watch_runs` row is at least 7 days old minus a 30-minute slack. A deploy restarts the timer and changes nothing about when the next run is due;
66
+ - **resumable**: the hub redeploys on every merge, so a 20–30-minute run will sometimes be cut off. Each item's result is written as it finishes. A run left `running` for longer than its timeout is picked up by the next tick, which does only the items it has not done, and never charges twice for an item;
67
+ - **one run at a time**: a row-level claim on the run (e.g. `UPDATE … WHERE status = 'queued' RETURNING`) so a second process on the same database cannot start a duplicate.
68
+
69
+ ### 3. Money: capped, recorded, fail closed
70
+
71
+ - Before a run, and again **before each item**, the poller reads this calendar month's Market Watch spend. If `spent + one item's ceiling > cap`, it stops. A run that cannot start is recorded as `skipped / cap_reached`. A run that stops partway is recorded as `partial / cap_reached`, with the items it did not reach. **If the spend cannot be read, nothing runs** (`spend_unreadable`), as in `agents/lib/spend-ceiling.js`.
72
+ - **An item's ceiling** is fixed by its bounds: 3 searches ($0.03), plus input bounded by `max_content_tokens` on the fetches, plus `max_tokens` out. R04 computes it from those numbers and states it in code, about $0.25 at the bounds above. **No item can cost more than its ceiling**, and no item starts unless its whole ceiling fits under the cap, so the cap is never overshot.
73
+ - **The real cost** of each item is priced from the response's `usage` (tokens through `llm-pricing.priceUsage`, plus `usage.server_tool_use.web_search_requests` × the search price). Each item is written to `cost_log` under source `market-watch:<run id>` with `category: 'api'` through the economy port, keyed so a retry cannot post twice. It is the same projection rule `agents/spawn.js mirrorCostToLedger` follows.
74
+
75
+ ## Rejected options
76
+
77
+ | Option | Why not |
78
+ |---|---|
79
+ | **Grade port with `tools: 'WebSearch,WebFetch'`** (the tool-enabled run) | Measured at a similar cost ($0.106/item), so there is nothing to gain. The server image has no `claude` CLI, so a deployed project would need a CLI install, `CLAUDE_BIN` and a signed-in CLI on every instance. The spawn runs under `bypassPermissions`. Opening the agents spawn path to tools means editing the protected `grading` module. The port's cache replays old answers. |
80
+ | **Revive the cron-router poller (task 1002498) and run Market Watch as a cron agent** | The owner set it aside ("not necessary now"). It would fire through the same grade port with no CLI on the server. The agents module is off on the hub. Its ledger would record the fire as `event` (`plannedRow`). Its ceilings are per payer and per definition, not the per-project monthly cap the owner asked for. Reviving it means building four things to use one. |
81
+ | **Autonomy's boot-anchored `routine-timers` pattern** | On an instance that redeploys on every merge it fires on each boot, so a paid run would be charged once per deploy. |
82
+ | **A separate search API (Brave, Tavily, Exa, or OpenRouter's web plugin) plus an LLM call** | Two vendors, two keys and two bills for one run, plus a fetch-and-clean step we would have to write. Search fees are about a fifth of the per-item cost, so a cheaper search API saves at most about $0.01 an item. Their prices were not checked for this ADR. The existing OpenRouter key is **per builder**, so using it would bill a person, not the project. |
83
+ | **The builder's own Claude Code session** (a local scheduled task) | Not unattended: a closed laptop is a missed week. It bills a builder, not the project, and its spend never reaches the project's cap or `cost_log`. |
84
+ | **Haiku as the research model** | Cheaper per token, but the dynamic-filtering web tools (`_20260209`) need Sonnet 4.6+ or Opus 4.6+. Also, `llm-pricing.js` prices the haiku family at Haiku 4.5's rate, so a newer Haiku would be mispriced until a task adds its row. |
85
+
86
+ ## What R04 ([task 1004937](https://cloudbongos.com/builders#/task/1004937)) must build
87
+
88
+ 1. `modules/market-watch/research.js`: one Messages API call per item, with the tools, bounds and structured output above. It is pure apart from an injected client, so tests run with no network. Entries without a `source_url` that appeared in a search or fetch result are dropped. That check runs in code, not in the prompt.
89
+ 2. `modules/market-watch/pollers/weekly-run.js`: the hourly, ledger-due, resumable, single-flight poller in Decision §2, with the cap checks in §3 before the run and before each item.
90
+ 3. Cost: price each item from `usage`. **Add the web-search price to `src/bongos/llm-pricing.js`** (it has no tool fees today) as the single place it is spelled. Write to `cost_log` as `market-watch:<run id>` through the economy port.
91
+ 4. Visible outcomes for every run: `done`, `partial`, or `skipped` with `no_key`, `cap_reached`, `spend_unreadable` or `off`. These are rows in the run table (R02, [task 1004936](https://cloudbongos.com/builders#/task/1004936)), never only log lines.
92
+ 5. The key: a project-scoped, owner-set, secret-boxed key read only inside a run. R03 builds the Settings surface, and R04 reads it through one accessor.
93
+ 6. Tests: a run on seed data produces sourced entries and score snapshots. A run over the cap is skipped and visible. A spend read that throws runs nothing. A cut-off run resumes without charging an item twice. The cost appears in the roll-up.
94
+
95
+ R04 must **not** touch `.github/workflows/`, the grading module, or the agents spawn path.
96
+
97
+ ## Consequences
98
+
99
+ - A project owner turns Market Watch on with their own Anthropic key and a cap. A 40-item watch costs about $4–5 a week on that key. Bongos never touches the money.
100
+ - The server gains a second direct LLM caller. If a third appears, the shared shape (key from Settings, bounded tools, usage priced into `cost_log`) should become a kernel helper, not a third copy.
101
+ - The cron-router poller stays abandoned. If agents ever need a weekly cadence, they should reuse this clock-anchored pattern and first fix `plannedRow`'s mapping of `cron` to `event`.
102
+ - **Unverified:** the direct-API figure is an estimate built from two CLI-measured items, and the web-search price is from a cached reference. R04's first real run on the hub (R09, [task 1004942](https://cloudbongos.com/builders#/task/1004942)) is the measurement that replaces both.
@@ -0,0 +1,36 @@
1
+ # 0395 — A task says what it needs, and each person's lists show only what they can do
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-10-29
5
+ - **Deciders:** The owner (direction of 2026-10-29: "what someone sees should be something they can actually do"; on this project everyone still sees everything).
6
+ - **Task:** [task 1004950](https://cloudbongos.com/builders#/task/1004950)
7
+ - **Related:** [ADR 0016](0016-trust-boundary-server-enforced-permissions.md) (authority read fresh, server-side), [ADR 0351](0351-a-criterion-closes-on-a-uat.md) (how the owner seat is read), the claim refusal ladder in `modules/lifecycle/CLAUDE.md`.
8
+
9
+ ## Context
10
+
11
+ On 2026-10-29 most of the 21 claimable maintenance tasks needed the live server, a signed-in browser or an owner decision. An agent on a builder machine picked one up, read it and stopped. The lists that recommend work offered tasks the reader could not finish.
12
+
13
+ The tasks table already had two session-fit fields: `automation_tag` (`overnight-safe`, `live-only`, `needs-input`) and `manual_degree`. Both answer *can this run unattended?* and feed the session-fit projection. Neither answers *who can do this at all?*
14
+
15
+ ## Decision
16
+
17
+ **1. `tasks.needs` is its own set, not a new `automation_tag` value.** A subset of `owner` (a decision, account or permission only the project owner holds) and `server` (hands-on work on the live host: DNS, secrets, the database by hand). Empty for most tasks. A task can need both. Overloading `automation_tag` would make one field answer two questions, and a task can be overnight-safe *and* need the server. A CHECK constraint holds the vocabulary; `src/bongos/task-needs.js` holds the same list and a test pins the two equal.
18
+
19
+ **2. Capabilities are resolved on the server, per request, uncached — like rank.** `owner` comes from the government owner seat (`positionsFor`), with the founding builder as the fallback, exactly as the criterion UAT sign-off reads it. `server` comes from `builders.capability_grants`, which only the person in the owner seat sets (`PATCH /builders/:id/capabilities`, also behind `builder.status.set`). Default off. Holding the owner seat does not imply server access: the owner's own agent runs on a builder machine too. Nothing in a request body can assert a capability.
20
+
21
+ **3. Only the lists that RECOMMEND work narrow.** The claimable feed (`/builder-start`, the work board's ready queue, Home's Next up, the autonomous runner) and the session optimizer leave out a task whose needs are not a subset of the viewer's capabilities. The autonomous and parallel runner never holds `owner`, whoever it runs as: an agent cannot make the owner's decision.
22
+
23
+ **4. The claim refuses only `server`.** A builder without the grant gets `NEEDS_SERVER_ACCESS` (403) with a plain sentence, on all three claim surfaces (`claimTask`, `claimTasksBatch`, `validateClaimBatch`, through the one extracted check `task-needs-gate.js`). Owner work stays claimable: the owner claims it, often through their own agent session, and refusing it would refuse them.
24
+
25
+ **5. Visibility is unchanged.** `GET /tasks`, the board, goal pages, search and the AI's context still show every task wherever project settings allow. The board gains a *Needs* filter, and `?needs=owner` on the claimable feed is the owner's "waiting on you" queue.
26
+
27
+ ## Consequences
28
+
29
+ - A builder without server access runs `/builder-start` and sees no server task. The owner sees owner tasks in their own list.
30
+ - Tagging is a person's call. The create routes (`POST /tasks`, `POST /goals/:id/tasks`) and `PATCH /tasks/:id` accept `needs`, and `/goal-create` tells the planner when to set it. Existing tasks are tagged by hand once this is live: [task 1005001](https://cloudbongos.com/builders#/task/1005001) carries the 2026-10-29 first-pass list; the migration tags nothing, because task ids differ between projects.
31
+ - `claimable_tasks` was rebuilt in the same migration so its `t.*` carries the column.
32
+
33
+ ## Not decided here
34
+
35
+ - A third need for a signed-in browser. The 2026-10-29 list had such tasks; they are tagged `owner` for now (the owner's browser), and a separate need can be added later by extending the list and the CHECK together.
36
+ - A settings screen for the grant. The route exists; a control in the hall can follow.
@@ -0,0 +1,41 @@
1
+ # 0396 — Google sign-in: a stand-in login, one chooser, and a CLI sign-in approved in the browser
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-10-29
5
+ - **Task:** [task 1004993](https://cloudbongos.com/builders#/task/1004993) (goal 1000095, work area 6)
6
+ - **Deciders:** the owner (Google is the only new way in: no email codes, no passwords; never join accounts on a matching email; a stand-in name the person chooses); Claude designed
7
+ - **Related:** **0393 (an account is a person; GitHub is one way in)**, 0016 (trust boundary), 0050 (one admission gate for web and CLI), 0141 (the hub is an identity provider), 0217 D3 (the hub sign-in records the terms), 0294 (CLI tokens do not expire)
8
+
9
+ ## Context
10
+
11
+ ADR 0393 made a platform account a person with ways in, and minted negative person keys for people who arrive without GitHub. Nothing yet let anyone arrive that way. The owner asked for "Continue with Google" beside "Continue with GitHub" on every sign-in and sign-up screen of the hub, with the command-line sign-in working for a Google account too.
12
+
13
+ Three things stood in the way:
14
+
15
+ 1. **Core's `builders.github_login` is `NOT NULL`.** Every hub session belongs to a builder row, and a Google-only person has no GitHub login. Making the column nullable is a core migration that every screen reading a login would have to follow.
16
+ 2. **About twenty sign-in links** on the hub's pages, plus the hall's, each go straight to GitHub.
17
+ 3. **The CLI's sign-in is GitHub's own device flow**: the terminal shows a code to type at github.com, which a person with no GitHub account cannot do.
18
+
19
+ ## Decision
20
+
21
+ 1. **A stand-in login for the builder row, never a GitHub-shaped one** (the owner's pick, 2026-10-29: a stand-in, and the person decides it). A person with no GitHub linked gets `~<handle>` as `builders.github_login`, where the handle is the one they choose on their account page; a first Google sign-in lands on `/account?welcome=google` to choose it. `~` never appears in a GitHub login, so no admission rule, first-admin seat, invite or GitHub API call keyed on a login can ever reach a real GitHub user through it. The stand-in is refreshed at each sign-in. Once GitHub is linked (task 1004995), the real login is used instead: the hub answers it with the person.
22
+ 2. **The stand-in never reaches the hub's own record.** `upsertAccount` ignores any login a caller passes for a minted (negative) key, on insert and on update, so `platform_identity_accounts.github_login` stays NULL until linking sets it (ADR 0393 §4). This also covers the hub routes that pass `req.builder.github_login` along.
23
+ 3. **The person is found by Google's own id, through the hub.** Core asks the `platform-identity.hub` port's `signInWithGoogle`, which finds the person by `(google, sub)` or creates them (`createAccountForLogin`). Emails are stored for display and never looked up. So Google sign-in exists only where the hub module runs and the owner's Google app is configured (`<PREFIX>_GOOGLE_CLIENT_ID` / `_SECRET`); every other instance is unchanged.
24
+ 4. **The ID token is checked, not trusted.** It is fetched from Google's token endpoint over TLS with our client secret, so its signature is not re-verified (OpenID Connect Core §REDACTED_IP). Its issuer, audience (and `azp` with several audiences), expiry and a per-sign-in `nonce` held in a host-only cookie beside the `state` are checked.
25
+ 5. **One chooser instead of twenty buttons.** When Google is offered, `/auth/web/start` shows a small page with both buttons rather than jumping to GitHub, so every existing link, the hall's included, gains Google at once. `via=github` is the GitHub button, and it is also used by doors that need GitHub itself: creating a project, and "New account", which names a GitHub account.
26
+ 6. **The CLI is approved in the browser.** When Google is offered, `/auth/device/start` answers in GitHub's device-flow shape, but the `verification_uri` is this site's own `/auth/device?code=…`. There the person signs in with either way, checks the code against their terminal, and presses Approve. That posts from their browser cookie only (a bearer cannot mint a bearer, as for `/auth/cli-token/issue`), same-site only, and mints a CLI session for whoever approved. The terminal's next poll receives it exactly once. The CLI needs no change. GitHub users on the hub use this page too. It is one click when they are already signed in.
27
+ 7. **Admission is the GitHub web flow's gate.** A first-time Google person is admitted only under open enrollment (they have no GitHub login an Archon could have invited), an existing one always passes, and a switched-off builder gets no session. The hub runs open enrollment.
28
+ 8. **Projects cannot take a Google-only person yet.** ADR 0393 says an older core must not be sent a minted key. `/sso/authorize` refuses one with a plain page, and "join a project" refuses before relaying. Task 1004994 opens both doors.
29
+
30
+ ## Consequences
31
+
32
+ - A Google-only person can sign up, sign in on the hub's web pages and its hall, use the CLI, and edit their account. They cannot join another project's hall until task 1004994.
33
+ - The stand-in shows wherever core shows a login (`~ana` in the hall). Task 1004994, which teaches halls about Google-only builders, should prefer the handle when showing one. A handle renamed on the account page reaches the builder row at the next sign-in.
34
+ - The hub's CLI sign-in now runs through the browser approval page for everyone, GitHub users included. It shares GitHub's device-code phishing risk, and the page says the same thing GitHub's does: approve only a sign-in you just started, and cancel a link someone sent you.
35
+ - Pending CLI approvals live in memory, like GitHub's device flows here. A restart drops one, and the person runs the sign-in again.
36
+ - The terms page still says people sign in with GitHub. Changing it is the owner's call because it changes the agreed text and its version.
37
+ - **Still owed before linking ships (task 1004995, the ADR 0393 carry-over):** every GitHub sign-in must find the person through `findPersonByLogin` first.
38
+
39
+ ## What this does not do
40
+
41
+ It adds no other provider, no email codes and no passwords. It does not link accounts, show a Link GitHub chip, or guide anyone to GitHub (tasks 1004995–1004997).
@@ -147,7 +147,7 @@ These 20 numbers are each shared by exactly two files. They are **accepted histo
147
147
  | 0051 | [Full session transcripts in the BFG corpus (with transcript-grade scrubbing)](0051-full-session-transcript-corpus.md) | GDS / BFG |
148
148
  | 0052 | [Sandbox for everyone: a game-only preview entrypoint + the game in the starter clone](0052-sandbox-for-everyone-game-only-preview.md) | GDS / dev boxes |
149
149
  | 0053 | [Scoped, short-lived dev-box session + apex-cookie strip at the tunnel (SR#2/#11)](0053-scoped-dev-box-session.md) | GDS / dev boxes + security |
150
- | 0054 | [Flat cost-plus session token reward (example as equity): reward = round(true_cost_usd × 1.20 × 100), server-priced from per-model tokens, idempotent per session_id](0054-session-token-reward-cost-plus.md) | GDS / economy |
150
+ | 0054 | [Flat cost-plus session token reward (example as equity): reward = round(true_cost_usd × 1.20 × 100), server-priced from per-model tokens, idempotent per session_id](0054-session-token-reward-cost-plus.md) _(credits-as-equity framing and "1 drachma = $1" superseded by ADR 0385 / ADR 0387)_ | GDS / economy |
151
151
  | 0055 | [Server-mediated branch publish (the dev box holds no GitHub credential): box uploads a thin bundle, the GDS pushes + opens the PR + auto-merges with a server-side PAT](0055-server-mediated-branch-publish.md) | GDS / dev boxes + ship pipeline |
152
152
  | 0056 | [Version-control the prod deploy script as a reviewable mirror (`scripts/deploy/deploy-prod.sh`), and retry the post-restart `/healthz` so a slow service bind no longer false-fails a successful deploy](0056-prod-deploy-script-mirror-and-healthcheck-retry.md) | GDS / deploy pipeline |
153
153
  | 0057-a | [Approve new members and first dev boxes from Discord: two Archon-only channels where a 👍/👎 fires the existing admit (`access_requests`) / first-box (`box_intents`) approve action, re-checked against the reactor's live rank](0057-discord-archon-approval-channels.md) _(box half superseded by ADR 0059)_ | GDS / Discord + onboarding |
@@ -503,3 +503,12 @@ These 20 numbers are each shared by exactly two files. They are **accepted histo
503
503
  | 0382 | [**A hardware card is a passkey step-up check, switched on per builder by an Archon** ([task 1004676](https://cloudbongos.com/builders#/task/1004676), proposed). No WebAuthn exists today; sign-in is GitHub OAuth or the hub IdP. **D1:** WebAuthn via a vetted library. **D2:** the card is a second factor, a server-recorded step-up, never the first. **D3:** register first; the requirement needs a registered card. **D4:** an Archon switches it on per builder, default off. **D5:** guards rank changes, deploy, session revoke, payouts. No PC/SC reader needed. Rejected: PC/SC middleware, card as sole login.](0382-a-hardware-card-is-a-passkey-step-up-an-archon-switches-on-per-builder.md) | auth / security |
504
504
  | 0383 | [**Sessions end after ten idle days, CLI included** ([task 1004830](https://cloudbongos.com/builders#/task/1004830)). Supersedes ADR 0294. Any session unused for 10 days stops authenticating and drops off the Active sessions list; measured on last_used_at, so it is an idle limit, not a ceiling. Rejected: hiding old sessions while they stay live; browser-only.](0383-sessions-end-after-ten-idle-days.md) | auth / security |
505
505
  | 0384 | [**The /raise investor page is a hub module that builders edit in place** ([task 1004847](https://cloudbongos.com/builders#/task/1004847)). A `raise` module, enabled only on the hub: every write is an audited `/api/bongos/raise/*` route, the page itself a GET-only `raise.web` port core mounts at /raise; team editing rides the ordinary builder session (any active builder, owner 2026-10-25); investors get a hashed-token session; /raise is exempt from the pre-launch gate. Everything, copy included, publishes with the core (owner's call); only the password hash stays in env.](0384-the-raise-page-is-a-hub-module-builders-edit-in-place.md) | modules / pages |
506
+ | 0385 | [**Money lives with a licensed processor, credits stay a project-scoped meter, and each project sets its own pay** ([task 1004905](https://cloudbongos.com/builders#/task/1004905) · goal 1000092; also records tasks 1003180, 1003181 and 1004909). Bongos holds no user money and never redeems credits for cash; projects choose pay form and funding rule; payouts run under a project-signed rule; module sales carry a buyer-paid platform fee (processing cost plus 1 percent, per ADR 0392); crypto stays out. Six legal items are held for counsel and block the money-moving tasks.](0385-money-lives-with-a-processor-credits-stay-a-project-meter.md) | economy / legal |
507
+ | 0386 | [**The funding wall is a per-project rule: hold at claim, refuse at completion, or carry on and record the shortfall** ([task 1003181](https://cloudbongos.com/builders#/task/1003181) · goal 1000092). Bongos sets no wall of its own; a project that sets nothing gets the claim-time hold, which refuses before any work is spent and holds the estimate so claims cannot over-commit the balance.](0386-the-funding-wall-is-a-per-project-rule-claim-time-hold-by-default.md) | economy |
508
+ | 0387 | [**Two ledgers: credits measure work, money stays with the processor** ([task 1003180](https://cloudbongos.com/builders#/task/1003180) · goal 1000092). Resolves ADR 0054's double denomination by splitting, not by re-denominating credits into cents: `credit_log` stays a work meter with no money column, money gets its own cents record tied to the processor, no dollar value is read off a credit, and historic rows are neither rewritten nor classified.](0387-two-ledgers-credits-measure-work-money-stays-with-the-processor.md) | economy |
509
+ | 0388 | [**A module sale is split by the project's own rule, and the buyer pays the platform fee on top** ([task 1004909](https://cloudbongos.com/builders#/task/1004909) · goal 1000092). The platform fee is the processor's cost plus 1 percent of the price, collected for Cloud Bongos LLC and never deducted from contributors; the project sets the seller share and contributor pool, the pool is divided by recorded contribution, and no paid module lists without a split rule.](0388-a-module-sale-is-split-by-the-project-the-buyer-pays-the-platform-fee.md) | economy / marketplace |
510
+ | 0392 | [**AI generation runs on a processor-held balance or the builder's own key** ([task 1004916](https://cloudbongos.com/builders#/task/1004916)). A new `ai-studio` module: price = provider cost x one named multiplier (1.08), the processor's fee added at top-up, the full price held on the processor-held balance before any run and released on failure (Bongos keeps no balance, ADR 0385), every movement in one append-only ledger in micro-dollars; the builder's own key is entered only in Settings, encrypted, never returned or logged, and runs at no charge.](0392-ai-generation-runs-on-a-processor-held-balance-and-the-builders-own-keys.md) |
511
+ | 0393 | [**A platform account is a person, and GitHub is one way in** ([task 1004992](https://cloudbongos.com/builders#/task/1004992)). An account's ways in (GitHub, Google) are rows the database keeps one owner for; `github_id` keeps its name and becomes the person key, positive for a GitHub-born person and minted negative for one who arrived without GitHub, so no wire format changes; `github_login` is NULL with no GitHub linked; an email is shown, never looked up.](0393-a-platform-account-is-a-person-and-github-is-one-way-in.md) |
512
+ | 0396 | [**Google sign-in: a stand-in login, one chooser, and a CLI sign-in approved in the browser** ([task 1004993](https://cloudbongos.com/builders#/task/1004993)). A Google-only person gets `~<handle>` as the core builder login, a value no GitHub login can be, and never on the hub record; they are found by Google's own id through the hub, never an email. Every sign-in link shows Continue with GitHub / Continue with Google; the CLI sign-in is approved on this site's own page from the browser cookie. Projects cannot take a Google-only person until task 1004994.](0396-google-sign-in-a-stand-in-login-and-a-browser-approved-cli-sign-in.md) | identity / sign-in |
513
+ | 0394 | [**Market Watch searches the web through the Messages API on the project's own key, and runs from its own clock-anchored poller** ([task 1004934](https://cloudbongos.com/builders#/task/1004934) · goal 1000137). Not the grade port: the server image has no `claude` CLI, and a measured tool-enabled run costs the same ($0.106 per item). One Sonnet 5 call per item with bounded web search and fetch, on an owner-set key in Settings (the project pays the provider; Bongos holds no money); an hourly, ledger-due, resumable poller on the daily-checks pattern, not the abandoned cron-router; capped per item before it starts and fail closed; about $4–5 per weekly 40-item run.](0394-market-watch-searches-through-the-messages-api-on-the-projects-key-and-runs-from-its-own-poller.md) | modules / economy |
514
+ | 0395 | [**A task says what it needs, and each person's lists show only what they can do** ([task 1004950](https://cloudbongos.com/builders#/task/1004950)). `tasks.needs` ⊆ {owner, server} is its own set, not an `automation_tag` value (that one says whether work runs unattended). Capabilities are read fresh like rank: `owner` from the owner seat (founding builder fallback, the UAT reading), `server` from `builders.capability_grants`, set only by the owner. The recommend lists (claimable feed, optimizer, autonomous runner) show only tasks whose needs the viewer holds; the runner never holds owner. The claim refuses only `server` (`NEEDS_SERVER_ACCESS`, all three surfaces). Visibility elsewhere is unchanged; the board filters by need.](0395-a-task-says-what-it-needs-and-lists-show-only-what-you-can-do.md) | lifecycle / work tracking |