@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
@@ -7,8 +7,9 @@
7
7
  // WHY THIS ROUTE EXISTS. Goal 1000072's thesis is that this project is legible to
8
8
  // Claude and illegible to a human: the system knows everything and exposes almost
9
9
  // none of it in a shape a person can hold. The five values here — the rot timer, the
10
- // category set, how credits are calculated, whether equity is real, and what share of
11
- // upmarket revenue is paid on credits — were each answerable only by reading source.
10
+ // category set, how credits are calculated, what credits are (and are not) next to
11
+ // equity, and whether a credit-weighted pool can pay out — were each answerable only
12
+ // by reading source.
12
13
  // This route is the TRANSLATION LAYER, not a new capability: it invents no value and
13
14
  // stores nothing of its own.
14
15
  //
@@ -20,10 +21,11 @@
20
21
  // and say why.
21
22
  //
22
23
  // AND THE ONE THAT KEEPS IT HONEST: a fundamental with no knob behind it is reported
23
- // as ABSENT, never as a plausible zero. `upmarket_percent` is that row today — the
24
- // task asked for it, and no such knob exists anywhere in this codebase. Rendering it
25
- // as "0%" would be a fabricated fact about the project's economics; saying "there is
26
- // no such setting yet, and here is where it would live" is the legible answer.
24
+ // as ABSENT or GATED, never as a plausible zero. `upmarket_percent` (shown as the
25
+ // credit-weighted pool) is that row today: no such knob exists, and ADR 0385 gates
26
+ // the pay type behind counsel and a signed rule. Rendering it as "0%" would be a
27
+ // fabricated fact about the project's economics; saying "not available, and here is
28
+ // what gates it" is the legible answer.
27
29
  //
28
30
  // MODULE BOUNDARY: reaches core only through the published doorway
29
31
  // `../../../src/module-api`; `../goal-category` is an intra-module sibling.
@@ -118,58 +120,64 @@ function creditRow() {
118
120
  // (ADR 0146). A page that showed the estimate without that sentence would be telling
119
121
  // a builder they will earn a number this instance has decided not to pay.
120
122
  const estimate = policy.perTaskCredit
121
- ? 'A task’s own credit estimate is also paid, scaled by its kind.'
122
- : 'A task’s credit estimate is an advertised guide only — this instance pays cost-plus and nothing else.';
123
+ ? 'A task’s own credit estimate is also awarded, scaled by its kind.'
124
+ : 'A task’s credit estimate is an advertised guide only — this instance awards cost-plus credits and nothing else.';
125
+ // Wording per ADR 0387: the formula's INPUT is USD, so the credits are "sized by"
126
+ // the session's cost — never "worth" dollars, and never money.
123
127
  return {
124
128
  key: 'credit_calculation',
125
129
  title: 'Credit calculation',
126
130
  editable: false,
127
131
  state: 'live',
128
132
  value: { marginPct: policy.marginPct, usdPeg: policy.usdPeg, mode: policy.mode, perTaskCredit: policy.perTaskCredit },
129
- sentence: `A shipped session pays round(true cost in USD × ${mult}${pegNote}). ${estimate}`,
133
+ sentence: `A shipped session earns round(true cost in USD × ${mult}${pegNote}) credits, sized by the session’s cost in USD. Credits measure contribution; they are not money. ${estimate}`,
130
134
  source: 'branding pack → `reward`',
131
135
  reads: { file: 'config/branding.json → reward', adr: 'ADR 0054, ADR 0146' },
132
136
  };
133
137
  }
134
138
 
135
- // Equity. THE OWNER DECIDED THIS (2026-08-25) and the decision is that it is NOT REAL:
136
- // credits ARE the ownership stream today, and no legal equity is issued. The page says
137
- // that plainly rather than implying a cap table exists — an owner reading their own
138
- // settings page must not come away believing shares are being issued. If issuance ever
139
- // becomes real it gets its own decision record; the Board Room spec already parks
140
- // Howey / Subchapter T explicitly out of scope.
139
+ // Equity. Bongos issues none, and credits are NOT equity. The owner's 2026-08-25
140
+ // decision (no legal equity issued) stands; the older ADR 0054 framing that a credit
141
+ // IS a unit of ownership was superseded on 2026-10-29 (ADR 0385 decisions 2-3, ADR
142
+ // 0387): credits are a project-scoped contribution meter, and equity, if a project
143
+ // offers any, is that project's own grant, one signature each. The page says that
144
+ // plainly — an owner reading their own settings page must not come away believing
145
+ // credits are shares or that a cap table exists. The key stays `equity_calculation`
146
+ // so existing readers keep working.
141
147
  function equityRow() {
142
148
  return {
143
149
  key: 'equity_calculation',
144
- title: 'Equity calculation',
150
+ title: 'Equity',
145
151
  editable: false,
146
152
  state: 'not-yet-real',
147
- sentence: 'No legal equity is issued. Credits are the ownership stream this project actually runs — '
148
- + 'the owner’s framing in ADR 0054 is that a credit IS a unit of ownership. There is no cap table, '
149
- + 'no share class and no issuance behind this row.',
150
- source: 'owner decision, 2026-08-25',
151
- reads: { file: 'docs/adr/0054-drachmae-token-reward.md', adr: 'ADR 0054' },
153
+ sentence: 'Credits are a contribution meter, not ownership: they measure work on this project '
154
+ + 'and are never money, never redeemable and never equity. No legal equity is issued here. '
155
+ + 'Equity, if any, is granted by the project separately, one signed grant at a time — '
156
+ + 'there is no cap table and no share class behind this row.',
157
+ source: 'owner decisions, 2026-08-25 and 2026-10-29',
158
+ reads: { file: 'docs/adr/0054-session-token-reward-cost-plus.md', adr: 'ADR 0054 (superseded in part), ADR 0385, ADR 0387' },
152
159
  };
153
160
  }
154
161
 
155
- // The share of upmarket revenue paid out on credits. NO SUCH KNOB EXISTS — searched
156
- // across code, config, migrations and docs on this task and found only prose in an
157
- // unrelated competitive-analysis file. So this row reports ABSENCE and names where the
158
- // setting would live if it became real, which is the actionable fact. A rendered "0%"
159
- // would be a fabricated economic claim; an omitted row would leave the owner to
160
- // rediscover the gap by grep.
162
+ // The credit-weighted pool: a project dividing a pool of its OWN money by credit
163
+ // share (ADR 0387 decision 3). The row key stays `upmarket_percent` (its first name,
164
+ // "share of upmarket revenue paid on credits") so existing readers keep working.
165
+ // NO SUCH KNOB EXISTS, and ADR 0385's pay-type gates hold this one shut until the
166
+ // wording rule is in force, the model's inputs are visible and a signed payout rule
167
+ // exists — with counsel's answer on held item 2 first. So the row reports GATED,
168
+ // never a rendered "0%", which would be a fabricated economic claim.
161
169
  function upmarketRow() {
162
170
  return {
163
171
  key: 'upmarket_percent',
164
- title: 'Percent of upmarket revenue paid on credits',
172
+ title: 'Credit-weighted pool',
165
173
  editable: false,
166
- state: 'absent',
167
- sentence: 'This project has no such setting yet — nothing in the code, config or database '
168
- + 'defines it, so there is no number to show. It would belong beside the other reward '
169
- + 'terms in the branding pack’s `reward` block, and paying against it would need its own '
170
- + 'decision record first.',
171
- source: 'not configured anywhere',
172
- reads: { file: 'config/branding.json → reward (where it would live)', adr: null },
174
+ state: 'gated',
175
+ sentence: 'Not available (gated). A project could one day divide a pool of its own money by '
176
+ + 'credit share, but that pay type stays switched off until counsel clears it and the '
177
+ + 'project signs a payout rule. Bongos sets no share and no rate, and no dollar value is '
178
+ + 'ever read off a credit.',
179
+ source: 'gated by ADR 0385 (pay-type gates)',
180
+ reads: { file: 'docs/adr/0385-money-lives-with-a-processor-credits-stay-a-project-meter.md', adr: 'ADR 0385, ADR 0387' },
173
181
  };
174
182
  }
175
183
 
@@ -251,10 +251,31 @@ function badKind(res) {
251
251
  }
252
252
 
253
253
 
254
+ // task 1004950 / ADR 0395: a task's needs are a subset of TASK_NEEDS. Shared by
255
+ // POST /tasks, PATCH /tasks/:id and POST /goals/:id/tasks so all three refuse alike.
256
+ function badNeeds(res, bad) {
257
+ const valid = api.taskNeeds.TASK_NEEDS;
258
+ return res.fail('bad_needs', { status: 400, message: `needs must list only: ${valid.join(', ')} (unknown: ${bad.join(', ')}). Most tasks need neither.`, details: { valid } });
259
+ }
260
+
261
+ // ?needs= on GET /tasks and GET /tasks/claimable (task 1004950): one need
262
+ // ('owner' | 'server') or 'none'. Shared so the two lists agree on the vocabulary.
263
+ const NEEDS_FILTERS = new Set([...api.taskNeeds.TASK_NEEDS, 'none']);
264
+ function parseNeedsFilter(raw) {
265
+ if (raw === undefined || raw === '') return undefined;
266
+ return NEEDS_FILTERS.has(raw) ? raw : { bad: 'bad_needs' };
267
+ }
268
+ function badNeedsFilter(res) {
269
+ return res.fail('bad_needs', { status: 400, message: `needs must be one of ${[...NEEDS_FILTERS].join(' | ')}.` });
270
+ }
271
+
254
272
  module.exports = {
273
+ badNeedsFilter,
274
+ parseNeedsFilter,
255
275
  MIGRATION_KEYWORD_RE,
256
276
  VALID_FITS,
257
277
  badKind,
278
+ badNeeds,
258
279
  deHashRefs,
259
280
  detectsMigration,
260
281
  gateTaskOwnership,
@@ -26,7 +26,7 @@ const { buildDependencyAdvisory } = require('../dependency-advisory');
26
26
  const { classifyTaskKind, VALID_KINDS, WRITABLE_DISCIPLINES, resolveWriteDiscipline, VOTABLE_TASK_STATUSES, TASK_FIELD_BOUNDS } = require('../task-classifier');
27
27
  const { isModuleKey, moduleKeys } = api.moduleScopeMap;
28
28
  const { validateOrRespond, LIMITS, parseId } = api;
29
- const { badKind, detectsMigration } = require('./task-route-helpers.js');
29
+ const { badKind, badNeeds, detectsMigration } = require('./task-route-helpers.js');
30
30
  const { describeCheckViolation } = require('../pg-constraint-fail.js');
31
31
  const { goalNotOpenRefusal } = require('./goal-route-authz.js');
32
32
  const { resolveUpkeepHome, isUpkeepFiling, UPKEEP_HINT } = require('../upkeep-home.js'); // task 1004597
@@ -62,6 +62,7 @@ module.exports = function registerTaskWriteRoutes(router) {
62
62
  newcomer_friendly: { type: 'boolean' }, // V3.R15 #239 — Xenos-claimable flag
63
63
  security_sensitive: { type: 'boolean' }, // task 990 — Archon-only ship broadcast
64
64
  requires_rank: { type: 'string', maxLength: 16 }, // task 1498 / ADR 0084 — per-task rank floor (auto-derived; explicit value can only raise it)
65
+ needs: {}, // task 1004950 / ADR 0395 — ['owner'|'server'] or a comma list; checked inline
65
66
  auto_detect_touches: { type: 'boolean' },
66
67
  // task 438: link the new task to done-when criteria at creation time. Each
67
68
  // element is a criterion_id slug (required on a multi-goal version — task
@@ -169,6 +170,9 @@ module.exports = function registerTaskWriteRoutes(router) {
169
170
  return res.fail('bad_requires_rank', { status: 400, message: `requires_rank must be one of ${db.LIVE_RANK_LADDER.join(' | ')}. The floor auto-derives from touches + security_sensitive; an explicit value can only raise it.`, details: { valid: db.LIVE_RANK_LADDER } });
170
171
  }
171
172
  }
173
+ // task 1004950 / ADR 0395: what the task needs beyond the build loop.
174
+ const createNeeds = api.taskNeeds.normalizeNeeds(body.needs);
175
+ if (!createNeeds.ok) return badNeeds(res, createNeeds.bad);
172
176
  // BV1.R53 (task 1422): validate module_key against MODULE_SCOPE_MAP.
173
177
  if (body.module_key != null && !isModuleKey(body.module_key)) {
174
178
  return res.fail('bad_module_key', { status: 400, message: `module_key must be one of the known module keys, or omitted. Valid keys: ${moduleKeys().join(', ')}.`, details: { valid: moduleKeys() } });
@@ -245,6 +249,7 @@ module.exports = function registerTaskWriteRoutes(router) {
245
249
  requiresRank: body.requires_rank ?? null,
246
250
  moduleKey: body.module_key ?? null,
247
251
  goalId: body.goal_id ?? null,
252
+ needs: createNeeds.needs,
248
253
  // task 1003278 — attribution, from the AUTHENTICATED request, never the
249
254
  // body: who created a task is a fact about the caller, not an input.
250
255
  createdBy: req.builder.id,
@@ -348,6 +353,7 @@ module.exports = function registerTaskWriteRoutes(router) {
348
353
  xenos_claimable: { type: 'boolean' }, // pre-rename alias for newcomer_friendly
349
354
  security_sensitive: { type: 'boolean' }, // task 990 — Archon-only ship broadcast
350
355
  automation_tag: { type: 'string', maxLength: 64 },
356
+ needs: {}, // task 1004950 — ['owner'|'server'], a comma list, or null/[] to clear; checked inline
351
357
  needs_migration: { type: 'boolean' }, // task 1002648 — correct the create-time heuristic's flag (the claim-time allocator keys off it)
352
358
  // task 1002647 (map s4.3): the task body is a load-bearing builder prompt
353
359
  // and was settable only at create — no route edited it at any rank. Now
@@ -397,6 +403,7 @@ module.exports = function registerTaskWriteRoutes(router) {
397
403
  const hasParallelSafe = Object.prototype.hasOwnProperty.call(body, 'parallel_safe');
398
404
  const hasSecuritySensitive = Object.prototype.hasOwnProperty.call(body, 'security_sensitive');
399
405
  const hasAutomationTag = Object.prototype.hasOwnProperty.call(body, 'automation_tag');
406
+ const hasNeeds = Object.prototype.hasOwnProperty.call(body, 'needs');
400
407
  const hasNeedsMigration = Object.prototype.hasOwnProperty.call(body, 'needs_migration');
401
408
  const hasRequiresRank = Object.prototype.hasOwnProperty.call(body, 'requires_rank');
402
409
  const hasModuleKey = Object.prototype.hasOwnProperty.call(body, 'module_key');
@@ -416,8 +423,8 @@ module.exports = function registerTaskWriteRoutes(router) {
416
423
  if (Object.prototype.hasOwnProperty.call(body, 'status')) {
417
424
  return res.fail('status_not_patchable', { status: 400, message: STATUS_HINT });
418
425
  }
419
- if (!hasParent && !hasKind && !hasDiscipline && !hasNewcomerOrXenos && !hasParallelSafe && !hasSecuritySensitive && !hasAutomationTag && !hasNeedsMigration && !hasRequiresRank && !hasModuleKey && !hasGoalId && !hasDescription && !hasTitle && !hasPriority && !hasValueSummary) {
420
- return res.fail('no_supported_fields', { status: 400, message: 'PATCH /tasks/:id supports parent_task_id, kind, discipline, title, description, value_summary, priority, parallel_safe, security_sensitive, automation_tag, needs_migration, requires_rank, module_key, goal_id, and newcomer_friendly (alias: xenos_claimable). ' + STATUS_HINT });
426
+ if (!hasParent && !hasKind && !hasDiscipline && !hasNewcomerOrXenos && !hasParallelSafe && !hasSecuritySensitive && !hasAutomationTag && !hasNeeds && !hasNeedsMigration && !hasRequiresRank && !hasModuleKey && !hasGoalId && !hasDescription && !hasTitle && !hasPriority && !hasValueSummary) {
427
+ return res.fail('no_supported_fields', { status: 400, message: 'PATCH /tasks/:id supports parent_task_id, kind, discipline, title, description, value_summary, priority, parallel_safe, security_sensitive, automation_tag, needs, needs_migration, requires_rank, module_key, goal_id, and newcomer_friendly (alias: xenos_claimable). ' + STATUS_HINT });
421
428
  }
422
429
  // ---- task 1002609 (G-22): settle EVERY gate and validation before the ----
423
430
  // ---- FIRST write. The previous shape validated-and-wrote per field in ----
@@ -476,6 +483,8 @@ module.exports = function registerTaskWriteRoutes(router) {
476
483
  return res.fail('bad_automation_tag', { status: 400, message: 'automation_tag must be one of overnight-safe | live-only | needs-input, or null to clear', details: { valid: Array.from(VALID_AUTOMATION_TAGS) } });
477
484
  }
478
485
  }
486
+ const patchNeeds = hasNeeds ? api.taskNeeds.normalizeNeeds(body.needs) : null;
487
+ if (patchNeeds && !patchNeeds.ok) return badNeeds(res, patchNeeds.bad);
479
488
  if (hasModuleKey) {
480
489
  // BV1.R53 (task 1422): assign or clear the module affinity label. null clears.
481
490
  if (body.module_key !== null && !isModuleKey(body.module_key)) {
@@ -577,6 +586,15 @@ module.exports = function registerTaskWriteRoutes(router) {
577
586
  return res.fail('patch_failed', { status: 500, message: 'internal error' });
578
587
  }
579
588
  }
589
+ if (hasNeeds) {
590
+ try {
591
+ updated = await db.updateTaskNeeds(id, patchNeeds.needs);
592
+ if (!updated) return res.fail('task_not_found', 404);
593
+ } catch (err) {
594
+ log.error('[gds] PATCH /tasks/:id needs', err);
595
+ return res.fail('patch_failed', { status: 500, message: 'internal error' });
596
+ }
597
+ }
580
598
  if (hasNeedsMigration) {
581
599
  try {
582
600
  updated = await db.updateTaskNeedsMigration(id, body.needs_migration);
@@ -91,7 +91,7 @@ const PUBLISH_BUNDLE_MAX_BYTES = 20 * 1024 * 1024; // 20 MB decoded bundle
91
91
  // autolinks to GitHub's own issue space (dead links for GDS task ids — task 916).
92
92
  // Mirrors ship.js sanitizeGitHubRefs (which lives in scripts/ and isn't requirable
93
93
  // here).
94
- const { MIGRATION_KEYWORD_RE, VALID_FITS, badKind, deHashRefs, detectsMigration, gateTaskOwnership, graderBypassEnabled, isVerifyTask, peerVoteNet, postGateAuthorTrust, rankAttestsGateTrust } = require('./task-route-helpers.js');
94
+ const { MIGRATION_KEYWORD_RE, VALID_FITS, badKind, badNeedsFilter, parseNeedsFilter, deHashRefs, detectsMigration, gateTaskOwnership, graderBypassEnabled, isVerifyTask, peerVoteNet, postGateAuthorTrust, rankAttestsGateTrust } = require('./task-route-helpers.js');
95
95
  // task 1003208: structured logging (pino via the doorway) — was console.*.
96
96
  const log = api.logger('lifecycle');
97
97
  const registerTaskWriteRoutes = require('./task-write-routes.js'); // task 1003206 — POST /tasks + PATCH /tasks/:id
@@ -176,10 +176,11 @@ module.exports = function buildTasksRouter() {
176
176
  if (limit && limit.bad) return res.fail('bad_limit', { status: 400, message: 'limit must be a positive integer up to 1000.' });
177
177
  const offset = posInt(offsetRaw, 'bad_offset');
178
178
  if (offset && offset.bad) return res.fail('bad_offset', { status: 400, message: 'offset must be a positive integer.' });
179
+ const needs = parseNeedsFilter(req.query.needs); if (needs && needs.bad) return badNeedsFilter(res); // task 1004950
179
180
 
180
181
  const tasks = await db.listTasks({
181
182
  versionId: version, status, kind, discipline, goalId,
182
- createdBy, staleDays, limit, offset,
183
+ createdBy, staleDays, limit, offset, needs,
183
184
  });
184
185
  // ?include=deps (hall redesign task 1002439) — enrich each task with its
185
186
  // dependency summary (`blocked_by[]` with per-target satisfied state, and a
@@ -216,16 +217,15 @@ module.exports = function buildTasksRouter() {
216
217
  if (claimStaleDays && claimStaleDays.bad) return res.fail('bad_stale_days', { status: 400, message: 'stale_days must be a positive integer number of days.' });
217
218
  // ?limit=N — opt-in, for a caller that renders N rows (goal 1000072). It slices the
218
219
  // RETURNED feed, never the query — lifecycle/CLAUDE.md has the why.
220
+ const claimNeeds = parseNeedsFilter(req.query.needs); if (claimNeeds && claimNeeds.bad) return badNeedsFilter(res); // task 1004950
219
221
  const claimLimit = posInt(req.query.limit, 'bad_limit', 1000);
220
222
  if (claimLimit && claimLimit.bad) return res.fail('bad_limit', { status: 400, message: 'limit must be a positive integer (max 1000).' });
221
223
  const [feed, active] = await Promise.all([
222
224
  db.listClaimableTasks({
223
- versionId: version || null,
224
- builderId: req.builder.id,
225
- discipline: discipline || null,
226
- fit: fit || null,
225
+ versionId: version || null, builderId: req.builder.id,
226
+ discipline: discipline || null, fit: fit || null,
227
227
  createdBy: claimCreatedBy ?? null,
228
- staleDays: claimStaleDays ?? null,
228
+ staleDays: claimStaleDays ?? null, needs: claimNeeds ?? null,
229
229
  }),
230
230
  req.query.group === 'goal' ? [] : db.listActiveClaims(),
231
231
  ]);
@@ -507,11 +507,11 @@ module.exports = function buildTasksRouter() {
507
507
  // the numeric done_when_criteria.id, mirroring DELETE deps/:depId).
508
508
  // rank: metic+archon — removes a criterion↔task edge; task-shaping, Metic+ (ADR 0090).
509
509
  router.delete('/tasks/:id/criteria/:criterionId', auth.requireBuilder, auth.requirePermission('task.criterion.manage'), async (req, res) => {
510
- const id = Number(req.params.id);
511
- const criterionId = Number(req.params.criterionId);
512
- if (!Number.isFinite(id) || !Number.isFinite(criterionId)) {
513
- return res.fail('bad_task_id', 400);
514
- }
510
+ // task 1004984: whole positive ids only (Number.isFinite let 7.5 through).
511
+ const id = parseId(req, res, { code: 'bad_task_id', positiveInt: true });
512
+ if (id === null) return;
513
+ const criterionId = parseId(req, res, { param: 'criterionId', code: 'bad_criterion_id', positiveInt: true });
514
+ if (criterionId === null) return;
515
515
  const removed = await db.removeTaskCriterion(id, criterionId);
516
516
  res.json({ ok: true, removed });
517
517
  });
@@ -194,12 +194,15 @@ function githubAccessCause(errors) {
194
194
  // best-effort by design: insertAuditLog uses the module pool rather than the
195
195
  // ship's transaction client, and an audit miss must never roll back a landed ship
196
196
  // (the posture src/bongos/middleware/audit.js already takes).
197
+ // method is 'POST' — the ship route's own verb. audit_log's CHECK admits only
198
+ // POST/PATCH/PUT/DELETE (migration 033), so the old 'SHIP' failed every write
199
+ // and the record never existed (task 1004879).
197
200
  async function recordShipAssurance({ insertAuditLog, taskId, builderId, assurance, log }) {
198
201
  try {
199
202
  await insertAuditLog({
200
203
  builderId,
201
204
  route: `/tasks/${taskId}/ship`,
202
- method: 'SHIP',
205
+ method: 'POST',
203
206
  requestBodyRedacted: { ship_assurance: assurance },
204
207
  responseStatus: 200,
205
208
  surface: 'lifecycle',
@@ -447,10 +447,15 @@ function buildCardModel(input = {}) {
447
447
  let deployText;
448
448
  let deployCommit = null;
449
449
  let hostedText = null;
450
+ // task 1004741: "live" only when a reading SAYS the live build carries this
451
+ // merge. Shipped means merged; a merge reaches no instance until a published
452
+ // release carries it and the pin moves (merge-reach.js).
453
+ let liveConfirmed = false;
450
454
  if (stage === 'shipped') {
451
455
  deployCommit = input.deploy && input.deploy.commit ? String(input.deploy.commit) : null;
452
456
  const reach = boundReading(input.deploy && input.deploy.reach);
453
457
  if (reach) {
458
+ liveConfirmed = reach.includes === true;
454
459
  // task 1004682: who sees this merge, in the one shared wording
455
460
  // (merge-reach.js) — the hub's live build as read, then the hosted rule.
456
461
  // A bare "live · <sha>" read as "every user has it", which is only true
@@ -458,7 +463,8 @@ function buildCardModel(input = {}) {
458
463
  // boundReading re-bounds it (host-shaped, hex, boolean) before printing.
459
464
  [deployText, hostedText] = whoSeesAMerge(reach);
460
465
  } else {
461
- deployText = deployCommit ? `live · ${deployCommit}` : 'live';
466
+ // No reading of the live build: say what is known (it merged), never "live".
467
+ deployText = `merged${deployCommit ? ` · ${deployCommit}` : ''} — not live until a published release carries it and the pin moves`;
462
468
  }
463
469
  } else if (stage === 'confirmed') {
464
470
  // task 1002541: this line used to be unconditional, driven by the STATUS
@@ -491,7 +497,7 @@ function buildCardModel(input = {}) {
491
497
 
492
498
  addFact('', changed);
493
499
  if (gradeText) addFact('grade', gradeText, { cls: gradeCls });
494
- if (stage === 'shipped') addFact('live', deployCommit, { mono: true });
500
+ if (stage === 'shipped') addFact(liveConfirmed ? 'live' : 'merged', deployCommit, { mono: true });
495
501
  // The reason can be a full sentence — too long for a fact chip, so the chip
496
502
  // carries the alarm and the reason rides in its tooltip (the Deploy row above
497
503
  // always shows it in full).
@@ -0,0 +1,70 @@
1
+ // taskNeedsFailures — the claim-time half of "a task says what it needs" (task
2
+ // 1004950, ADR 0395). The goalMembershipFailures / relevanceFrictionFailures
3
+ // shape: one DB-read check called from all THREE claim surfaces in db-claims.js
4
+ // (claimTask, claimTasksBatch, validateClaimBatch), so no surface can advertise
5
+ // or take a claim another refuses.
6
+ //
7
+ // Only `server` refuses. `owner` work is claimable — by the owner, the one person
8
+ // the feed shows it to; refusing it here would also refuse the owner's own agent
9
+ // session, which is how the owner does that work. The feed is where `owner`
10
+ // narrows (db-tasks.listClaimableTasks).
11
+ //
12
+ // `client` is the caller's open txn client (claimTask) or the pool itself (the
13
+ // read-only dry run). Capabilities are resolved fresh, once per call, through the
14
+ // doorway — never from the request body (ADR 0016). Returns [{ task_id, refusal }].
15
+
16
+ 'use strict';
17
+
18
+ const api = require('../../src/module-api');
19
+
20
+ async function taskNeedsFailures({ client, tasks, builderId, deps = {} }) {
21
+ const ids = (tasks || []).map((t) => Number(t.id)).filter((n) => Number.isFinite(n));
22
+ if (ids.length === 0) return [];
23
+ const { rows } = await client.query(
24
+ `SELECT id, needs FROM tasks WHERE id = ANY($1::bigint[]) AND cardinality(needs) > 0`,
25
+ [ids]
26
+ );
27
+ if (rows.length === 0) return [];
28
+ const needsApi = api.taskNeeds;
29
+ const capabilities = await (deps.resolveCapabilities || needsApi.resolveCapabilities)(builderId);
30
+ const failures = [];
31
+ for (const r of rows) {
32
+ const refused = needsApi.claimRefusalNeeds(r.needs, capabilities);
33
+ if (refused.length === 0) continue;
34
+ failures.push({
35
+ task_id: Number(r.id),
36
+ refusal: {
37
+ code: 'NEEDS_SERVER_ACCESS',
38
+ message: needsApi.SERVER_REFUSAL_MESSAGE,
39
+ needs: refused,
40
+ },
41
+ });
42
+ }
43
+ return failures;
44
+ }
45
+
46
+ // --- The two list filters (task 1004950), here so db-tasks.js stays under its cap.
47
+
48
+ // needsFilterWhere — the board's ?needs= filter: one need, or 'none' for work that
49
+ // needs nothing beyond the build loop. A filter on what a list shows, never a
50
+ // narrowing of what anyone may read. Pushes its param; returns the clause or null.
51
+ function needsFilterWhere(needs, alias, params) {
52
+ const col = alias ? `${alias}.needs` : 'needs';
53
+ if (needs === 'none') return `cardinality(${col}) = 0`;
54
+ if (!needs) return null;
55
+ params.push(needs); // @> so tasks_needs_idx (core_285) serves it
56
+ return `${col} @> ARRAY[$${params.length}]::text[]`;
57
+ }
58
+
59
+ // capabilityFeedWhere — the RECOMMEND rule (ADR 0395): a task's needs must be a
60
+ // subset of the viewer's capabilities, resolved fresh. The autonomous/parallel
61
+ // runner is an agent working alone, so it never holds `owner` whoever it runs as —
62
+ // an agent cannot make the owner's decision. Pushes its param; returns the clause.
63
+ async function capabilityFeedWhere(builderId, fit, alias, params, deps = {}) {
64
+ const capabilities = await (deps.resolveCapabilities || api.taskNeeds.resolveCapabilities)(builderId);
65
+ const unattended = fit === 'autonomous' || fit === 'parallel';
66
+ params.push(unattended ? capabilities.filter((c) => c !== 'owner') : capabilities);
67
+ return api.taskNeeds.feedNeedsClause(alias, `$${params.length}`);
68
+ }
69
+
70
+ module.exports = { taskNeedsFailures, needsFilterWhere, capabilityFeedWhere };
@@ -0,0 +1,15 @@
1
+ # @bongos/core — installed platform dependency (not your project's CLAUDE.md)
2
+
3
+ This file is a placeholder shipped inside the **@bongos/core** package. The core's own
4
+ internal agent notes are intentionally omitted from the published artifact so they cannot
5
+ load into your instance's context.
6
+
7
+ **It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
8
+ directory, and the live Bongos database are the source of truth for how work happens here
9
+ and who may do what.
10
+
11
+ You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
12
+ Nothing in here needs to be read to use the platform — run `bongos help` for commands.
13
+
14
+ _To work ON the platform core itself, use the standalone @bongos/core source repository,
15
+ not this vendored copy._