@bongos/core 1.23.22 → 1.24.2

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 (435) hide show
  1. package/.bongos-core.json +933 -413
  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 +2 -2
  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 +96 -0
  27. package/clients/bongos-client/index.cjs +96 -0
  28. package/clients/bongos-client/index.d.ts +155 -4
  29. package/clients/bongos-client/index.mjs +96 -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 +12 -1
  33. package/docs/adr/0393-a-platform-account-is-a-person-and-github-is-one-way-in.md +37 -0
  34. package/docs/adr/0394-market-watch-searches-through-the-messages-api-on-the-projects-key-and-runs-from-its-own-poller.md +102 -0
  35. package/docs/adr/0395-a-task-says-what-it-needs-and-lists-show-only-what-you-can-do.md +36 -0
  36. package/docs/adr/0396-google-sign-in-a-stand-in-login-and-a-browser-approved-cli-sign-in.md +41 -0
  37. package/docs/adr/README.md +6 -2
  38. package/docs/api/openapi.json +8651 -5833
  39. package/docs/api-reference.md +66 -10
  40. package/docs/architecture.md +37 -1
  41. package/docs/canonical-permissions.md +1 -1
  42. package/docs/copy-inventory.md +427 -339
  43. package/docs/copy-registry.json +1376 -500
  44. package/docs/file-map.md +1 -0
  45. package/docs/module-api-changelog.md +7 -0
  46. package/docs/onboarding/diagrams/03-drachmae-karma.mmd +3 -3
  47. package/docs/onboarding/diagrams/README.md +1 -1
  48. package/docs/onboarding/drachmae-and-karma.md +15 -15
  49. package/docs/onboarding/primer.md +2 -2
  50. package/docs/page-inventory.json +57 -7
  51. package/docs/page-readings.json +1210 -1027
  52. package/docs/recipes/google-sign-in.md +60 -0
  53. package/docs/recipes/ops-gotchas.md +1 -1
  54. package/migrations/core_282_builders_lower_login_idx.sql +33 -0
  55. package/migrations/core_283_claimable_ignores_page_markers.sql +53 -0
  56. package/migrations/core_283_payout_approvals.sql +183 -0
  57. package/migrations/core_284_payout_gates.sql +179 -0
  58. package/migrations/core_285_task_needs.sql +74 -0
  59. package/modules/ai-studio/billing.js +67 -0
  60. package/modules/ai-studio/context.js +29 -0
  61. package/modules/ai-studio/db.js +172 -0
  62. package/modules/ai-studio/gemini.js +75 -0
  63. package/modules/ai-studio/generate.js +119 -0
  64. package/modules/ai-studio/keys.js +73 -0
  65. package/modules/ai-studio/migrations/ai_studio_001_keys.sql +22 -0
  66. package/modules/ai-studio/migrations/ai_studio_002_ledger.sql +48 -0
  67. package/modules/ai-studio/migrations/ai_studio_003_requests.sql +55 -0
  68. package/modules/ai-studio/openrouter.js +90 -0
  69. package/modules/ai-studio/payments.js +105 -0
  70. package/modules/ai-studio/pricing.js +92 -0
  71. package/modules/ai-studio/routes/ai-studio.js +235 -5
  72. package/modules/ai-studio/studio.js +121 -0
  73. package/modules/autonomy/fence.js +64 -1
  74. package/modules/builder-settings/builder-needs.js +27 -1
  75. package/modules/copy-desk/routes/copy-desk.js +5 -0
  76. package/modules/economy/achievements.js +22 -10
  77. package/modules/economy/cost-classify.js +9 -0
  78. package/modules/economy/cost-write-guard.js +52 -0
  79. package/modules/economy/cost.js +2 -1
  80. package/modules/economy/credits.js +18 -15
  81. package/modules/economy/goal-reward.js +161 -0
  82. package/modules/economy/reward.js +4 -0
  83. package/modules/economy/routes/cost.js +33 -7
  84. package/modules/government/access-routes.js +70 -0
  85. package/modules/government/access.js +88 -0
  86. package/modules/government/asks.js +5 -2
  87. package/modules/government/catalog.js +6 -0
  88. package/modules/government/db.js +72 -0
  89. package/modules/government/docket.js +48 -2
  90. package/modules/government/migrations/government_035_payout_approve.sql +29 -0
  91. package/modules/government/migrations/government_036_access_holders.sql +33 -0
  92. package/modules/government/pollers/board-expiry.js +108 -20
  93. package/modules/government/protected-surfaces.json +1 -0
  94. package/modules/government/routes/government.js +31 -0
  95. package/modules/government/routing.js +49 -7
  96. package/modules/government/waiting.js +78 -0
  97. package/modules/hall-ui/public/ai-studio-lib.js +268 -0
  98. package/modules/hall-ui/public/ai-studio.css +193 -0
  99. package/modules/hall-ui/public/ai-studio.html +98 -0
  100. package/modules/hall-ui/public/ai-studio.js +556 -0
  101. package/modules/hall-ui/public/ai-studio.states.json +94 -0
  102. package/modules/hall-ui/public/blockers-by-person.js +158 -0
  103. package/modules/hall-ui/public/blockers-lib.js +15 -1
  104. package/modules/hall-ui/public/blockers.html +8 -0
  105. package/modules/hall-ui/public/blockers.js +1 -1
  106. package/modules/hall-ui/public/claim-action.js +3 -0
  107. package/modules/hall-ui/public/collab.html +12 -0
  108. package/modules/hall-ui/public/craft-mode.js +4 -1
  109. package/modules/hall-ui/public/criterion-uat-ui.js +4 -1
  110. package/modules/hall-ui/public/drachmae.html +1 -1
  111. package/modules/hall-ui/public/genesis/r5-life-1-primordial-sea.jpg +0 -0
  112. package/modules/hall-ui/public/genesis/r5-life-2-first-cells-cut.webp +0 -0
  113. package/modules/hall-ui/public/genesis/r5-life-2-first-cells.jpg +0 -0
  114. package/modules/hall-ui/public/genesis/r5-life-3-genome-cut.webp +0 -0
  115. package/modules/hall-ui/public/genesis/r5-life-3-genome.jpg +0 -0
  116. package/modules/hall-ui/public/genesis/r5-life-4-membrane-cut.webp +0 -0
  117. package/modules/hall-ui/public/genesis/r5-life-4-membrane.jpg +0 -0
  118. package/modules/hall-ui/public/genesis/r5-life-5-nervous-system-cut.webp +0 -0
  119. package/modules/hall-ui/public/genesis/r5-life-5-nervous-system.jpg +0 -0
  120. package/modules/hall-ui/public/goal-inbox.js +1 -1
  121. package/modules/hall-ui/public/goals-render.js +4 -0
  122. package/modules/hall-ui/public/goals.html +8 -0
  123. package/modules/hall-ui/public/gov-living.js +6 -2
  124. package/modules/hall-ui/public/government-access.js +108 -0
  125. package/modules/hall-ui/public/government.html +21 -0
  126. package/modules/hall-ui/public/hall-ask.css +5 -0
  127. package/modules/hall-ui/public/hall-ask.js +39 -3
  128. package/modules/hall-ui/public/notes-panel.css +20 -0
  129. package/modules/hall-ui/public/notes-panel.js +146 -0
  130. package/modules/hall-ui/public/project-settings.html +1 -1
  131. package/modules/hall-ui/public/project-settings.js +7 -5
  132. package/modules/hall-ui/public/settings-ai-keys.js +124 -0
  133. package/modules/hall-ui/public/settings.html +15 -0
  134. package/modules/hall-ui/public/street-deck.js +12 -3
  135. package/modules/hall-ui/public/studio.css +90 -8
  136. package/modules/hall-ui/public/studio.html +39 -10
  137. package/modules/hall-ui/public/studio.js +148 -18
  138. package/modules/hall-ui/public/task.html +2 -0
  139. package/modules/hall-ui/public/task.js +4 -0
  140. package/modules/hall-ui/public/tweak-editor.js +12 -3
  141. package/modules/hall-ui/public/welcome-boot.css +42 -6
  142. package/modules/hall-ui/public/welcome-boot.js +20 -6
  143. package/modules/hall-ui/public/work.js +20 -0
  144. package/modules/hall-ui/records/ai-studio.md +36 -0
  145. package/modules/hall-ui/records/blockers-people.md +38 -0
  146. package/modules/lifecycle/db-claims.js +32 -4
  147. package/modules/lifecycle/db-goals.js +18 -5
  148. package/modules/lifecycle/db-tasks.js +15 -6
  149. package/modules/lifecycle/done-when.js +23 -1
  150. package/modules/lifecycle/lifecycle.js +5 -0
  151. package/modules/lifecycle/migrations/lifecycle_017_notes.sql +46 -0
  152. package/modules/lifecycle/module.json +2 -1
  153. package/modules/lifecycle/notes.js +219 -0
  154. package/modules/lifecycle/page-notice.js +124 -0
  155. package/modules/lifecycle/page-tweak-artist.js +18 -1
  156. package/modules/lifecycle/publish-reconciler.js +67 -20
  157. package/modules/lifecycle/routes/claims.js +5 -2
  158. package/modules/lifecycle/routes/goals.js +3 -1
  159. package/modules/lifecycle/routes/notes.js +91 -0
  160. package/modules/lifecycle/routes/project-settings.js +43 -35
  161. package/modules/lifecycle/routes/task-route-helpers.js +21 -0
  162. package/modules/lifecycle/routes/task-write-routes.js +21 -3
  163. package/modules/lifecycle/routes/tasks.js +12 -12
  164. package/modules/lifecycle/task-needs-gate.js +70 -0
  165. package/modules/market-watch/CLAUDE.md +15 -0
  166. package/modules/market-watch/db.js +340 -0
  167. package/modules/market-watch/key.js +58 -0
  168. package/modules/market-watch/migrations/market_watch_001_tables.sql +249 -0
  169. package/modules/market-watch/migrations/market_watch_002_run.sql +46 -0
  170. package/modules/market-watch/module.json +26 -0
  171. package/modules/market-watch/pollers/hub-seed.js +54 -0
  172. package/modules/market-watch/pollers/weekly-run.js +100 -0
  173. package/modules/market-watch/research.js +350 -0
  174. package/modules/market-watch/run.js +245 -0
  175. package/modules/market-watch/seed/market-data.json +38 -0
  176. package/modules/market-watch/seed/partners-data.json +497 -0
  177. package/modules/market-watch/seed.js +252 -0
  178. package/modules/market-watch/store.js +230 -0
  179. package/modules/memory/routes/memory.js +14 -2
  180. package/modules/memory/routes/search.js +8 -2
  181. package/modules/platform-identity/account-logins.js +192 -0
  182. package/modules/platform-identity/migrations/platform_identity_034_account_logins.sql +96 -0
  183. package/modules/platform-identity/platform-identity.js +5 -4
  184. package/modules/platform-identity/routes/my-projects.js +6 -0
  185. package/modules/platform-identity/routes/profile.js +18 -0
  186. package/modules/platform-identity/routes/public-profile.js +10 -1
  187. package/modules/platform-identity/routes/sso.js +26 -1
  188. package/modules/platform-identity/tests/platform-identity.mjs +2 -1
  189. package/modules/provisioning/credential-preflight.js +13 -3
  190. package/modules/provisioning/pollers/app-liveness.js +25 -5
  191. package/modules/provisioning/routes/provisioning.js +1 -1
  192. package/modules/provisioning/starter-bundles.js +6 -2
  193. package/modules/public-landing/public/account.html +10 -2
  194. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg +0 -0
  195. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
  196. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
  197. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +1 -1
  198. package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp +0 -0
  199. package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp.json +1 -1
  200. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg +0 -0
  201. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
  202. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
  203. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +1 -1
  204. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg +0 -0
  205. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
  206. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
  207. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +1 -1
  208. package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp +0 -0
  209. package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp.json +1 -1
  210. package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp +0 -0
  211. package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp.json +1 -1
  212. package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp +0 -0
  213. package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp.json +1 -1
  214. package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp +0 -0
  215. package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp.json +1 -1
  216. package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp +0 -0
  217. package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp.json +1 -1
  218. package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp +0 -0
  219. package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp.json +1 -1
  220. package/modules/public-landing/public/assets/world/r3-space-nebula.jpg +0 -0
  221. package/modules/public-landing/public/assets/world/r3-space-nebula.jpg.json +1 -1
  222. package/modules/public-landing/public/assets/world/r4-card-chrome.jpg +0 -0
  223. package/modules/public-landing/public/assets/world/r4-card-chrome.jpg.json +1 -1
  224. package/modules/public-landing/public/assets/world/r4-card-nebula.jpg +0 -0
  225. package/modules/public-landing/public/assets/world/r4-card-nebula.jpg.json +1 -1
  226. package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg +0 -0
  227. package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg.json +1 -1
  228. package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp +0 -0
  229. package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp.json +1 -1
  230. package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg +0 -0
  231. package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg.json +1 -1
  232. package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp +0 -0
  233. package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp.json +1 -1
  234. package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg +0 -0
  235. package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg.json +1 -1
  236. package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp +0 -0
  237. package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp.json +1 -1
  238. package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg +0 -0
  239. package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg.json +1 -1
  240. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp +0 -0
  241. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp.json +1 -1
  242. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg +0 -0
  243. package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg.json +1 -1
  244. package/modules/public-landing/public/community.html +1 -1
  245. package/modules/public-landing/public/connections.html +2 -2
  246. package/modules/public-landing/public/guild.html +1 -1
  247. package/modules/public-landing/public/invitations.html +2 -2
  248. package/modules/public-landing/public/projects.html +5 -5
  249. package/modules/raise/views/nda.html +2 -0
  250. package/modules/specialities/db.js +32 -1
  251. package/modules/specialities/routes/specialities.js +10 -6
  252. package/modules/status-ui/public/index.html +1 -1
  253. package/package-lock.json +2 -2
  254. package/package.json +1 -1
  255. package/release-notes.json +230 -0
  256. package/scripts/bongos/autobongos-tweak-lane.js +46 -1
  257. package/scripts/bongos/backfill-subagent-rewards.js +2 -2
  258. package/scripts/bongos/claim.js +22 -1
  259. package/scripts/bongos/db-backup-nightly.sh +29 -0
  260. package/scripts/bongos/deploy-divergence.js +7 -0
  261. package/scripts/bongos/doc-cli-guard.js +6 -0
  262. package/scripts/bongos/fitness.js +3 -2
  263. package/scripts/bongos/gen-diagrams.js +3 -3
  264. package/scripts/bongos/government-walk.js +3 -0
  265. package/scripts/bongos/lib.sh +4 -5
  266. package/scripts/bongos/migration-namespace.js +13 -1
  267. package/scripts/bongos/plain-cards.js +4 -0
  268. package/scripts/bongos/provision-github-setup.js +3 -1
  269. package/scripts/bongos/provision-units.js +6 -1
  270. package/scripts/bongos/publish-manifest.js +3 -0
  271. package/scripts/bongos/release.js +2 -0
  272. package/scripts/bongos/run-unit-tests.js +268 -82
  273. package/scripts/bongos/seed-tasks.example.json +56 -0
  274. package/scripts/bongos/seed-tasks.js +222 -0
  275. package/scripts/bongos/seed-verify-criteria.js +1 -2
  276. package/scripts/bongos/ship-finish.js +2 -0
  277. package/scripts/bongos/ship-resume.js +2 -0
  278. package/scripts/bongos/strand-lease.js +250 -0
  279. package/scripts/bongos/strand-watch.js +26 -5
  280. package/scripts/bongos/version-literal-guard.js +2 -2
  281. package/scripts/discord/post.js +4 -3
  282. package/scripts/gds/seed-tasks.js +7 -0
  283. package/scripts/gds/{build-bongos.js → strand-lease.js} +3 -3
  284. package/scripts/hall-preview/server.js +21 -0
  285. package/src/bongos/auth-config.js +29 -1
  286. package/src/bongos/auth-discord.js +3 -3
  287. package/src/bongos/auth-github.js +3 -6
  288. package/src/bongos/auth-google.js +217 -0
  289. package/src/bongos/device-approval.js +140 -0
  290. package/src/bongos/llm-pricing.js +17 -1
  291. package/src/bongos/metrics.js +48 -2
  292. package/src/bongos/middleware/rate-limit.js +30 -11
  293. package/src/bongos/module-scope-map.js +12 -1
  294. package/src/bongos/optimizer.js +15 -1
  295. package/src/bongos/payout-approvals.js +342 -0
  296. package/src/bongos/payout-gates.js +364 -0
  297. package/src/bongos/prelaunch-gate.js +7 -9
  298. package/src/bongos/prelaunch-switch.js +20 -0
  299. package/src/bongos/route-rank-check.js +7 -0
  300. package/src/bongos/routes/auth-google.js +246 -0
  301. package/src/bongos/routes/auth.js +38 -1
  302. package/src/bongos/routes/backup.js +33 -9
  303. package/src/bongos/routes/builders.js +57 -30
  304. package/src/bongos/routes/github.js +9 -0
  305. package/src/bongos/routes/me.js +18 -0
  306. package/src/bongos/routes/my-payouts.js +133 -0
  307. package/src/bongos/routes/payout-approvals.js +312 -0
  308. package/src/bongos/routes/payout-gates.js +179 -0
  309. package/src/bongos/routes.js +15 -1
  310. package/src/bongos/secret-box.js +18 -3
  311. package/src/bongos/serve-internal.js +47 -19
  312. package/src/bongos/sign-in-pages.js +138 -0
  313. package/src/bongos/task-needs.js +146 -0
  314. package/src/bongos-downloads.js +18 -1
  315. package/src/instance-config.js +28 -0
  316. package/src/module-api.js +14 -1
  317. package/tests/access_holders.mjs +214 -0
  318. package/tests/account_logins.mjs +326 -0
  319. package/tests/account_logins_db.mjs +161 -0
  320. package/tests/ai_studio_billing.mjs +253 -0
  321. package/tests/ai_studio_generate.mjs +282 -0
  322. package/tests/ai_studio_keys.mjs +159 -0
  323. package/tests/ai_studio_requests.mjs +319 -0
  324. package/tests/auth_backchannel_logout_route.mjs +201 -0
  325. package/tests/auto_satisfy_criteria.mjs +10 -1
  326. package/tests/autobongos_tweak_lane.mjs +144 -0
  327. package/tests/autobongos_tweak_pin_identity.mjs +137 -0
  328. package/tests/backfill_subagent_rewards.mjs +17 -1
  329. package/tests/backup_trigger_lock.mjs +119 -0
  330. package/tests/blockers_people.mjs +198 -0
  331. package/tests/bounded_rate_maps.mjs +184 -0
  332. package/tests/claimable_blocker_status.mjs +1 -1
  333. package/tests/claims_batch_routes.mjs +421 -0
  334. package/tests/config_read_cache.mjs +171 -0
  335. package/tests/copy_desk_page_editor.mjs +12 -0
  336. package/tests/cost_write_guard.mjs +200 -0
  337. package/tests/craft_achievements.mjs +33 -10
  338. package/tests/criterion_goal_attach.mjs +1 -1
  339. package/tests/db_backup_nightly.mjs +73 -1
  340. package/tests/deploy_divergence_line.mjs +7 -0
  341. package/tests/effective_visibility_predicate.mjs +12 -0
  342. package/tests/fitness.mjs +19 -0
  343. package/tests/fixtures/seed-tasks/bongos-v1-idea-routing.json +256 -0
  344. package/tests/fixtures/seed-tasks/bv1-profiles.json +592 -0
  345. package/tests/fixtures/seed-tasks/bv2-ideator-surface.json +373 -0
  346. package/tests/fixtures/seed-tasks/fake-api.mjs +80 -0
  347. package/tests/github_one_trip.mjs +34 -0
  348. package/tests/github_repo_visibility.mjs +20 -0
  349. package/tests/goal_achievement.mjs +30 -1
  350. package/tests/goal_achievement_reward.mjs +205 -0
  351. package/tests/goal_sort_staleness.mjs +2 -1
  352. package/tests/goal_tier.mjs +11 -1
  353. package/tests/google_fonts_nonblocking.mjs +84 -0
  354. package/tests/google_sign_in.mjs +401 -0
  355. package/tests/government_board_expiry.mjs +94 -0
  356. package/tests/government_board_membership.mjs +4 -1
  357. package/tests/government_protected_surfaces.mjs +1 -1
  358. package/tests/government_routes.mjs +1 -1
  359. package/tests/government_seed.mjs +1 -0
  360. package/tests/hall_ai_studio.mjs +347 -0
  361. package/tests/hall_page_gate_map.mjs +3 -0
  362. package/tests/hall_studio_home.mjs +61 -2
  363. package/tests/home_attention_cards.mjs +1 -1
  364. package/tests/human_signal.mjs +141 -0
  365. package/tests/install_script_reader.mjs +51 -0
  366. package/tests/integration_fixture_ids.mjs +82 -0
  367. package/tests/invite_expiry_db.mjs +13 -1
  368. package/tests/lib_sh_resolution.mjs +0 -4
  369. package/tests/market_watch_data.mjs +244 -0
  370. package/tests/market_watch_data_db.mjs +308 -0
  371. package/tests/market_watch_module.mjs +114 -0
  372. package/tests/market_watch_run.mjs +503 -0
  373. package/tests/metrics.mjs +124 -0
  374. package/tests/migration_namespace.mjs +14 -0
  375. package/tests/module_api.mjs +2 -0
  376. package/tests/module_loader.mjs +1 -1
  377. package/tests/notes.mjs +182 -0
  378. package/tests/own_scope_guard.mjs +5 -0
  379. package/tests/page_tweak_marker_sql.mjs +76 -0
  380. package/tests/page_tweak_no_block.mjs +135 -0
  381. package/tests/page_tweak_notice.mjs +103 -0
  382. package/tests/payout_approvals.mjs +253 -0
  383. package/tests/payout_approvals_db.mjs +106 -0
  384. package/tests/payout_gates.mjs +322 -0
  385. package/tests/payout_gates_db.mjs +113 -0
  386. package/tests/planning_session_skill.mjs +13 -3
  387. package/tests/project_settings_page.mjs +29 -5
  388. package/tests/project_settings_spend_cap_route.mjs +235 -0
  389. package/tests/promotion_sweep.mjs +29 -1
  390. package/tests/provision.mjs +3 -2
  391. package/tests/provisioning_app_status.mjs +18 -0
  392. package/tests/provisioning_credential_preflight.mjs +53 -0
  393. package/tests/provisioning_env_manifest_edit_delete.mjs +223 -0
  394. package/tests/publish_reconciler_overlap.mjs +60 -0
  395. package/tests/rank_change_broadcast_port.mjs +151 -0
  396. package/tests/seed_tasks.mjs +176 -0
  397. package/tests/seed_verify_criteria.mjs +2 -6
  398. package/tests/site_classified_once.mjs +142 -0
  399. package/tests/speciality_db_shape.mjs +23 -0
  400. package/tests/speciality_session_skills.mjs +48 -2
  401. package/tests/strand_lease.mjs +249 -0
  402. package/tests/strand_watch.mjs +10 -0
  403. package/tests/task_criteria_routes.mjs +275 -0
  404. package/tests/task_criteria_unlink_db.mjs +123 -0
  405. package/tests/task_needs.mjs +233 -0
  406. package/tests/task_needs_db.mjs +110 -0
  407. package/tests/unit_runner_serial.mjs +266 -43
  408. package/tests/version_literals.mjs +5 -7
  409. package/tests/welcome_boot.mjs +16 -5
  410. package/tests/welcome_boot_browser.mjs +36 -4
  411. package/docs/recipes/bongos-cli-release.md +0 -66
  412. package/scripts/bongos/backfill-disciplines.js +0 -144
  413. package/scripts/bongos/backfill-session-earnings.js +0 -123
  414. package/scripts/bongos/backfill-session-types.js +0 -87
  415. package/scripts/bongos/build-bongos.js +0 -94
  416. package/scripts/bongos/cleanup-orphans.js +0 -74
  417. package/scripts/bongos/seed-bongos-v1-idea-routing-tasks.js +0 -152
  418. package/scripts/bongos/seed-bv1-profiles-tasks.js +0 -135
  419. package/scripts/bongos/seed-bv2-ideator-surface-tasks.js +0 -390
  420. package/scripts/bongos/smoke-batch-claim.sh +0 -238
  421. package/scripts/bongos/smoke-batch-release.sh +0 -316
  422. package/scripts/bongos/smoke-claim-consistency.sh +0 -222
  423. package/scripts/bongos/smoke-dependencies.sh +0 -143
  424. package/scripts/gds/backfill-disciplines.js +0 -7
  425. package/scripts/gds/backfill-session-earnings.js +0 -7
  426. package/scripts/gds/backfill-session-types.js +0 -7
  427. package/scripts/gds/cleanup-orphans.js +0 -7
  428. package/scripts/gds/seed-bongos-v1-idea-routing-tasks.js +0 -6
  429. package/scripts/gds/seed-bv1-profiles-tasks.js +0 -6
  430. package/scripts/gds/seed-bv2-ideator-surface-tasks.js +0 -6
  431. package/scripts/gds/smoke-batch-claim.sh +0 -8
  432. package/scripts/gds/smoke-batch-release.sh +0 -8
  433. package/scripts/gds/smoke-claim-consistency.sh +0 -8
  434. package/scripts/gds/smoke-dependencies.sh +0 -8
  435. package/tests/smoke_dependencies_witness.mjs +0 -46
@@ -0,0 +1,158 @@
1
+ // blockers-by-person.js — open blockers grouped UNDER THE PEOPLE they wait on (task 1004999).
2
+ //
3
+ // WHY. The Blockers page listed every open blocker under one heading, "Requires project
4
+ // owner fix", whoever it actually waited on. A blocker has no assignee; who it waits on
5
+ // is the seat its ask names (owner, a building's Lead, a board), and who sits in that
6
+ // seat changes. The server resolves it at read time with the City's own routing
7
+ // (GET /government/blockers/people), so this page and the City can never disagree about
8
+ // whose a blocker is. This file only draws the answer.
9
+ //
10
+ // The viewer's own group comes first, marked "You". A blocker that resolves to nobody is
11
+ // shown under "Nobody yet" — never dropped. A signed-out viewer, or one the read refuses,
12
+ // sees no section at all: the queue below stays the page.
13
+ //
14
+ // UMD like blockers-lib.js: the pure `peopleHtml` is required by
15
+ // tests/blockers_people.mjs; the mount runs only in a browser.
16
+ (function (root, factory) {
17
+ var mod = factory();
18
+ if (typeof module !== 'undefined' && module.exports) module.exports = mod;
19
+ else root.OTBBlockersPeople = mod;
20
+ }(typeof self !== 'undefined' ? self : this, function () {
21
+ 'use strict';
22
+
23
+ function nameOf(p) {
24
+ return (p && (p.name || p.login)) || (p && p.id != null ? 'Builder ' + p.id : 'Someone');
25
+ }
26
+
27
+ // "as the owner" · "as the Planning Lead" · "as the owner, since Planning has no Lead".
28
+ function asWords(item) {
29
+ // task 1005000: only whoever holds an outside access can clear it — say so first.
30
+ if (item.needs) return item.fell_to_owner ? item.needs + ', and nobody is recorded holding it' : item.needs;
31
+ var as = item.as || 'their seat';
32
+ if (item.fell_to_owner && /^lead:([a-z]+)$/.test(String(item.asked || ''))) {
33
+ var b = String(item.asked).slice(5);
34
+ return 'as ' + as + ', since ' + b.charAt(0).toUpperCase() + b.slice(1) + ' has no Lead';
35
+ }
36
+ return 'as ' + as;
37
+ }
38
+
39
+ function rowHtml(item, o) {
40
+ var esc = o.esc;
41
+ var title = esc(item.title || ('Blocker ' + item.id));
42
+ var href = o.blockerHref ? o.blockerHref(item.id) : null;
43
+ var titleHtml = href
44
+ ? '<a class="ov-row__title" href="' + esc(href) + '">' + title + '</a>'
45
+ : '<span class="ov-row__title">' + title + '</span>';
46
+ var facts = [esc(asWords(item))];
47
+ if (item.opened_at) facts.push('filed ' + esc(o.fmtDate(item.opened_at)));
48
+ var q = item.question ? '<span class="ov-row__sub">' + esc(item.question) + '</span>' : '';
49
+ return '<li class="ov-row ov-row--dense"><div class="ov-row__main">' + titleHtml + q
50
+ + '<div class="ov-row__facts"><span class="ov-row__when">' + facts.join(' · ') + '</span></div></div></li>';
51
+ }
52
+
53
+ function groupHtml(name, n, note, items, o) {
54
+ return '<div class="ov-group">'
55
+ + '<div class="ov-group__head"><span class="ov-group__name">' + o.esc(name) + '</span>'
56
+ + '<span class="ov-group__n">' + n + '</span>'
57
+ + (note ? '<span class="ov-group__note">' + o.esc(note) + '</span>' : '')
58
+ + '</div><ul class="ov-rows">' + items.map(function (it) { return rowHtml(it, o); }).join('') + '</ul></div>';
59
+ }
60
+
61
+ // peopleHtml(data, { esc, fmtDate, viewerId, blockerHref }) → markup, or '' when there
62
+ // is nothing to show. `data` is GET /government/blockers/people.
63
+ function peopleHtml(data, o) {
64
+ if (!o || typeof o.esc !== 'function' || typeof o.fmtDate !== 'function') {
65
+ throw new TypeError('peopleHtml: opts.esc and opts.fmtDate are required');
66
+ }
67
+ if (!data || !data.available) return '';
68
+ var people = Array.isArray(data.people) ? data.people.slice() : [];
69
+ var nobody = Array.isArray(data.nobody) ? data.nobody : [];
70
+ if (!people.length && !nobody.length) return '';
71
+ var me = o.viewerId == null ? null : String(o.viewerId);
72
+ people.sort(function (a, b) { return (a.person.id === me ? 0 : 1) - (b.person.id === me ? 0 : 1); });
73
+ var html = people.map(function (g) {
74
+ var mine = g.person.id === me;
75
+ return groupHtml(mine ? 'You' : nameOf(g.person), g.count, mine ? 'waiting on you' : '', g.items, o);
76
+ }).join('');
77
+ if (nobody.length) html += groupHtml('Nobody yet', nobody.length, 'no one sits in the seat it waits on', nobody, o);
78
+ return html;
79
+ }
80
+
81
+ // mineHtml(data, opts) → the viewer's own rows, for Collab's "waiting on you" block.
82
+ // data is GET /government/blockers/for-me ({ available, count, items }).
83
+ function mineHtml(data, o) {
84
+ if (!o || typeof o.esc !== 'function' || typeof o.fmtDate !== 'function') {
85
+ throw new TypeError('mineHtml: opts.esc and opts.fmtDate are required');
86
+ }
87
+ if (!data || !data.available || !Array.isArray(data.items) || !data.items.length) return '';
88
+ return data.items.map(function (it) { return rowHtml(it, o); }).join('');
89
+ }
90
+
91
+ var blockerHref = function (id) { return '#/blocker/' + encodeURIComponent(id); };
92
+
93
+ // Collab's block: only what waits on the viewer. Hidden when nothing does.
94
+ function mountMine(doc, kit) {
95
+ var section = doc.getElementById('cb-blockers-scroll');
96
+ var list = doc.getElementById('cb-blockers');
97
+ if (!section || !list) return;
98
+ fetch('/api/bongos/government/blockers/for-me', { credentials: 'same-origin' })
99
+ .then(function (r) { return r.ok ? r.json() : null; })
100
+ .catch(function () { return null; })
101
+ .then(function (data) {
102
+ var html = mineHtml(data, { esc: kit.esc, fmtDate: kit.fmtAbsDate, blockerHref: blockerHref });
103
+ if (!html) return;
104
+ list.innerHTML = html;
105
+ var n = doc.getElementById('cb-blockers-n');
106
+ if (n) n.textContent = String(data.count);
107
+ section.hidden = false;
108
+ });
109
+ }
110
+
111
+ // The browser half: fetch, draw, reveal. Any failure leaves the section hidden.
112
+ // Collab loads dom-utils but not the kit, so the two helpers fall back to the shell's.
113
+ function helpers() {
114
+ var kit = window.OTBKit || {};
115
+ var esc = kit.esc || (window.OTB && window.OTB.escapeHtml);
116
+ var fmtDate = kit.fmtAbsDate || function (iso) {
117
+ var d = new Date(iso);
118
+ return isNaN(d.getTime()) ? '' : d.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' });
119
+ };
120
+ return typeof esc === 'function' ? { esc: esc, fmtAbsDate: fmtDate } : null;
121
+ }
122
+
123
+ function mount() {
124
+ var doc = typeof document !== 'undefined' ? document : null;
125
+ var h = doc ? helpers() : null;
126
+ if (!h) return;
127
+ mountMine(doc, h);
128
+ var section = doc.getElementById('blockers-people-scroll');
129
+ var body = doc.getElementById('blockers-people-body');
130
+ var kit = h;
131
+ if (!section || !body) return;
132
+ var get = function (path) {
133
+ return fetch('/api/bongos' + path, { credentials: 'same-origin' })
134
+ .then(function (r) { return r.ok ? r.json() : null; })
135
+ .catch(function () { return null; });
136
+ };
137
+ Promise.all([get('/government/blockers/people'), get('/me')]).then(function (res) {
138
+ var data = res[0];
139
+ var me = res[1] && (res[1].builder || res[1]);
140
+ var html = peopleHtml(data, {
141
+ esc: kit.esc,
142
+ fmtDate: kit.fmtAbsDate,
143
+ viewerId: me && me.id != null ? me.id : null,
144
+ blockerHref: blockerHref,
145
+ });
146
+ if (!html) return;
147
+ body.innerHTML = html;
148
+ section.hidden = false;
149
+ });
150
+ }
151
+
152
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
153
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mount);
154
+ else mount();
155
+ }
156
+
157
+ return { peopleHtml: peopleHtml, mineHtml: mineHtml, asWords: asWords };
158
+ }));
@@ -101,5 +101,19 @@
101
101
  };
102
102
  }
103
103
 
104
- return { blockingLine: blockingLine, blockerFacts: blockerFacts };
104
+ // Which human signal a blocker wears (task 1004946, owner 2026-10-28): every
105
+ // blocker waits on a person, but some ask them to DO a thing and some to
106
+ // CHOOSE. The stored ask (government_asks, on GET /blockers as `ask`) says
107
+ // which: two or more REAL options is a choice, a HUMAN DECISION. An option
108
+ // that only defers ("Not yet", "later") is no choice at all, so "Do it / Not
109
+ // yet" is a HUMAN TASK, as is a blocker with no ask. One rule for the Blockers
110
+ // page and Home's slice, so the two never disagree.
111
+ var DEFER_KEYS = /^(later|not[-_ ]?yet|notnow|not[-_ ]now|wait|defer|skip|no)$/i;
112
+ function humanKind(b) {
113
+ var opts = b && b.ask && Array.isArray(b.ask.options) ? b.ask.options : [];
114
+ var real = opts.filter(function (o) { return o && !DEFER_KEYS.test(String(o.key || '')); });
115
+ return real.length >= 2 ? 'decision' : 'task';
116
+ }
117
+
118
+ return { blockingLine: blockingLine, blockerFacts: blockerFacts, humanKind: humanKind };
105
119
  }));
@@ -33,6 +33,13 @@
33
33
  bar. task 1003456 added the write half: filing (any signed-in
34
34
  builder) below the heading, and per-row Resolve/Link (blocker.review
35
35
  — metic+archon) rendered by blockers.js when the viewer holds it. -->
36
+ <!-- task 1004999: the same open blockers, grouped under the people they wait
37
+ on (GET /government/blockers/people — the City's routing). Hidden until
38
+ that read answers; the queue below stays the page without it. -->
39
+ <section class="scroll" id="blockers-people-scroll" aria-labelledby="blockers-people-h" hidden>
40
+ <h2 id="blockers-people-h" class="scroll__h">Waiting on people</h2>
41
+ <div id="blockers-people-body"></div>
42
+ </section>
36
43
  <section class="scroll" id="blockers-scroll" aria-labelledby="blockers-h">
37
44
  <h2 id="blockers-h" class="scroll__h">Requires project owner fix<span class="count-pill" id="blockers-count" hidden></span></h2>
38
45
  <div id="blockers-file-area"></div>
@@ -67,5 +74,6 @@
67
74
  <script src="/builders/blockers-lib.js"></script>
68
75
  <script src="/builders/hall-ask.js"></script>
69
76
  <script src="/builders/blockers.js"></script>
77
+ <script src="/builders/blockers-by-person.js"></script>
70
78
  </body>
71
79
  </html>
@@ -147,7 +147,7 @@
147
147
  // bookkeeping, so it stays a plain control beside the facts (owner
148
148
  // 2026-10-23: a card only where the page asks the reader to decide).
149
149
  return window.OTBAsk.html({
150
- attrs: { 'data-id': id },
150
+ attrs: { 'data-id': id }, human: lib.humanKind(b),
151
151
  q: b.title || ('Blocker ' + id), qHref: blockerHref(id),
152
152
  saidHtml: facts.noteHtml + '<div class="ov-row__facts blockers-facts">' + facts.factsHtml
153
153
  + '<button type="button" class="btn-ghost blockers-facts__link" data-link-open="' + kit.esc(id) + '">Link a task</button></div>',
@@ -89,6 +89,9 @@
89
89
  // a private goal, so this must not invent one either.
90
90
  case 'GOAL_MEMBERSHIP_REQUIRED':
91
91
  return errMessage(data) || 'That task belongs to a goal you are not a member of.';
92
+ // task 1004950: the task needs hands on the live server.
93
+ case 'NEEDS_SERVER_ACCESS':
94
+ return errMessage(data) || 'This task needs server access, which you do not have yet, so pick another task or ask the project owner for it.';
92
95
  case 'DEPS_NOT_SHIPPED':
93
96
  return 'That task waits on unshipped dependencies. Pick another for now.';
94
97
  // 10. the module-level sibling of TOUCHES_CONFLICT, and a REFUSAL where
@@ -63,6 +63,17 @@
63
63
  <button class="cb-ask__copy cb-copy-all" id="cb-for-me-copy" type="button" hidden></button>
64
64
  </section>
65
65
 
66
+ <!-- task 1004999: open blockers whose seat resolves to this viewer (as the
67
+ owner, a building's Lead, or a board member) — GET
68
+ /government/blockers/for-me, drawn by blockers-by-person.js. Hidden
69
+ until something waits on them. -->
70
+ <section class="scroll cb-block" id="cb-blockers-scroll" aria-labelledby="cb-blockers-h" hidden>
71
+ <div class="cb-block__head">
72
+ <h2 class="scroll__h" id="cb-blockers-h">Blockers waiting on you <span class="cb-block__n" id="cb-blockers-n"></span></h2>
73
+ </div>
74
+ <ul class="ov-rows" id="cb-blockers"></ul>
75
+ </section>
76
+
66
77
  <section class="scroll cb-block" aria-labelledby="cb-mine-h">
67
78
  <div class="cb-block__head">
68
79
  <h2 class="scroll__h" id="cb-mine-h">Your asks <span class="cb-block__n" id="cb-mine-n"></span></h2>
@@ -155,5 +166,6 @@
155
166
  <script src="/builders/palette.js" defer></script>
156
167
  <script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
157
168
  <script src="/builders/collab.js?v=2026-09-24-archive"></script>
169
+ <script src="/builders/blockers-by-person.js"></script>
158
170
  </body>
159
171
  </html>
@@ -750,7 +750,10 @@
750
750
  // one) opens it on arrival, in any mode.
751
751
  var note = /[?&]note=([1-9]\d{0,17})(?:&|$)/.exec((window.location && window.location.search) || '');
752
752
  var notes = toolFor('palette');
753
- if (note && notes) loadTool(notes).then(function (api) { api.openNote(note[1]); }, function () { /* the page still works */ });
753
+ // The Studio reads the doc as a full page of its own (studio.js), so the
754
+ // side sheet stays shut there.
755
+ var onStudio = document.body && document.body.getAttribute('data-page') === 'studio';
756
+ if (note && notes && !onStudio) loadTool(notes).then(function (api) { api.openNote(note[1]); }, function () { /* the page still works */ });
754
757
  }
755
758
  // Once a signed-in builder is known, ask — unless this session already has a
756
759
  // mode, already saw the picker, or the page opts out.
@@ -62,7 +62,10 @@
62
62
  const pill = (s === 'open' || s === 'legacy_satisfied') ? ''
63
63
  : `<span class="crit-state crit-state--${esc(s)}">${esc(STATE_LABELS[s])}</span>`;
64
64
  const tag = c && c.backend_only ? '<span class="crit-tag" title="No screen to record: this is signed off without a video">Backend-only</span>' : '';
65
- return pill + tag;
65
+ // task 1004922: a criterion waiting on a person to try it is a HUMAN TASK.
66
+ const ask = typeof window !== 'undefined' && window.OTBAsk;
67
+ const human = s === 'awaiting_uat' && ask ? ask.human('task') : '';
68
+ return human + pill + tag;
66
69
  }
67
70
 
68
71
  // The trailing action. canReview = the viewer may sign off (Metic+; the server
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Drachmae &amp; karma — {{worldName}}</title>
7
- <meta name="description" content="How you get paid and how you earn trust: the cost-plus {{currency}} reward, the kind multipliers, the monthly budget cap, and how karma differs from {{currency}}.">
7
+ <meta name="description" content="How your work is counted and how you earn trust: the cost-plus {{currency}} reward, the kind multipliers, the monthly budget cap, and how karma differs from {{currency}}.">
8
8
  <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>◈</text></svg>">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -522,7 +522,7 @@
522
522
  });
523
523
  // Was a link only; now it answers in place: the overlay is where it resolves.
524
524
  return window.OTBAsk.html({
525
- cls: 'ginbox__item',
525
+ cls: 'ginbox__item', human: lib.humanKind(b),
526
526
  where: 'Waiting on the project owner',
527
527
  q: b.title || `Blocker ${b.id}`, qHref: `#/blocker/${encodeURIComponent(b.id)}`,
528
528
  saidHtml: facts.factsHtml + facts.noteHtml,
@@ -336,6 +336,8 @@
336
336
  const kindTag = window.OTB.kindIsUnset(t.kind)
337
337
  ? ''
338
338
  : `<span class="task__tag">${escapeHtml(t.kind)}</span>`;
339
+ // task 1004950 — what the task needs beyond the build loop, said in words.
340
+ const needsTags = (Array.isArray(t.needs) ? t.needs : []).map((n) => `<span class="task__tag task__tag--needs">${n === 'owner' ? 'needs the project owner' : n === 'server' ? 'needs server access' : escapeHtml(n)}</span>`).join('');
339
341
  const newcomerTag = t.newcomer_friendly ? '<span class="task__tag task__tag--newcomer">newcomer</span>' : '';
340
342
  const rankTag = rankTagHtml(t.requires_rank);
341
343
  const touches = Array.isArray(t.touches) ? t.touches : [];
@@ -387,6 +389,7 @@
387
389
  ${kindTag}
388
390
  ${disciplineTag}
389
391
  ${newcomerTag}
392
+ ${needsTags}
390
393
  ${rankTag}
391
394
  ${t.priority ? `<span class="task__tag">${escapeHtml(t.priority)}</span>` : ''}
392
395
  <span>${minutesText}</span>
@@ -924,6 +927,7 @@
924
927
  // task 1953 — my own pending request (any viewer) + the goal's pending queue
925
928
  // (managers only); async, re-renders Actions + the pending panel on arrival.
926
929
  loadGoalMembership(g.id, canManage);
930
+ if (window.OTBNotes) window.OTBNotes.mount($('#goal-notes'), { goal_id: g.id }); // task 1004745
927
931
  void sat; void crit; // now read inside renderCompletion()
928
932
  }
929
933
  function renderActions(g, { joined, canManage }) {
@@ -17,6 +17,7 @@
17
17
  styles now live in style.css's hall-kit section. -->
18
18
  <link rel="stylesheet" href="/builders/hall-ask.css">
19
19
  <link rel="stylesheet" href="/builders/goals.css">
20
+ <link rel="stylesheet" href="/builders/notes-panel.css">
20
21
  </head>
21
22
  <body data-page="goals" data-page-title="Goals">
22
23
 
@@ -130,6 +131,12 @@
130
131
  </div>
131
132
  </div>
132
133
 
134
+ <!-- task 1004745: notes on the goal, grouped by purpose (notes-panel.js). -->
135
+ <div class="scroll goal-notes">
136
+ <h2 class="scroll__h">Notes</h2>
137
+ <div id="goal-notes"></div>
138
+ </div>
139
+
133
140
  <div class="scroll" style="margin-top:20px">
134
141
  <div class="goal-work__head">
135
142
  <h2 class="scroll__h">Tasks</h2>
@@ -271,6 +278,7 @@
271
278
  <script src="/builders/goal-author-lib.js"></script>
272
279
  <script src="/builders/goal-author.js"></script>
273
280
  <script src="/builders/hall-ask.js"></script>
281
+ <script src="/builders/notes-panel.js"></script>
274
282
  <script src="/builders/goals-render.js?v=2026-09-30-uat2"></script>
275
283
  <script src="/builders/goals-page.js?v=2026-09-30-uat2"></script>
276
284
  </body>
@@ -344,8 +344,12 @@
344
344
  const format = askFormat(human);
345
345
  // the server's route (task 1004705), when the docket was routed
346
346
  const served = ctx.routed ? obj(base.route) : null;
347
+ // task 1004999: the server names who sits in the seat now, so a card can say
348
+ // "waits on Lars, the owner" and not only "waits on the owner".
349
+ const servedPeople = served ? arr(served.people).filter((p) => p && p.id != null)
350
+ .map((p) => ({ id: str(p.id), login: str(p.login), name: str(p.name || p.login) })) : [];
347
351
  const route = served
348
- ? { seat: str(served.seat), label: str(served.label), person: null, fell_to_owner: !!served.fell_to_owner, members: [] }
352
+ ? { seat: str(served.seat), label: str(served.label), person: servedPeople[0] || null, people: servedPeople, fell_to_owner: !!served.fell_to_owner, members: servedPeople.map((p) => p.id) }
349
353
  : ctx.resolveSeat(human && human.waits_on, base.building);
350
354
  const nobody = served && obj(served.nobody) ? { why: str(served.nobody.why), outcome: served.nobody.outcome == null ? null : str(served.nobody.outcome) } : null;
351
355
  // an ask nobody answers has done what its "if nobody answers" line says
@@ -375,7 +379,7 @@
375
379
  human: human || null,
376
380
  format,
377
381
  shown: format.ok ? human : null,
378
- waits_on: { seat: route.seat, label: route.label, person: route.person || null, fell_to_owner: !!route.fell_to_owner, real: !!served },
382
+ waits_on: { seat: route.seat, label: route.label, person: route.person || null, people: route.people || (route.person ? [route.person] : []), fell_to_owner: !!route.fell_to_owner, real: !!served },
379
383
  needs_you: needsYou,
380
384
  nobody,
381
385
  // the card's words are the agent's own stored ask (task 1004703)
@@ -0,0 +1,108 @@
1
+ // government-access.js — Outside access, on the Government page's Permissions tab
2
+ // (task 1005000).
3
+ //
4
+ // WHY. Some blockers can only be cleared by whoever holds access OUTSIDE Bongos: the
5
+ // GitHub repository's admin, the server, DNS, hosting, package publishing, payments.
6
+ // The owner records who holds each here (GET/POST/DELETE /government/access), and an
7
+ // ask can then wait on `access:<key>` — it reaches that person, and reads on the
8
+ // Blockers page as "needs repo-owner access". An access nobody is recorded holding
9
+ // falls to the owner, like every unheld seat.
10
+ //
11
+ // A RECORD OF WHO TO ASK, NEVER A PERMISSION: holding one here unlocks nothing in
12
+ // Bongos, and no credential is stored — the page says so. The server gates the
13
+ // writes on government.manage; the Permissions tab is already sealed for everyone
14
+ // else (government.js sealRanksPanel), so this file draws no gate of its own.
15
+ //
16
+ // UMD so tests/access_holders.mjs can run `accessHtml` without a browser.
17
+ (function (root, factory) {
18
+ var mod = factory();
19
+ if (typeof module !== 'undefined' && module.exports) module.exports = mod;
20
+ else root.OTBGovAccess = mod;
21
+ }(typeof self !== 'undefined' ? self : this, function () {
22
+ 'use strict';
23
+
24
+ // accessHtml(list, esc) → one row per access: its name, what it is, who holds it
25
+ // (each with a Remove control), or that it falls to the owner.
26
+ function accessHtml(list, esc) {
27
+ if (!Array.isArray(list) || !list.length) return '';
28
+ return '<ul class="ov-rows">' + list.map(function (a) {
29
+ var holders = Array.isArray(a.holders) ? a.holders : [];
30
+ var who = holders.length
31
+ ? holders.map(function (h) {
32
+ return '<span class="ov-fact">' + esc(h.name || h.login || ('builder ' + h.id))
33
+ + ' <button type="button" class="btn-ghost" data-act="revoke" data-key="' + esc(a.key) + '" data-id="' + esc(h.id) + '">Remove</button></span>';
34
+ }).join('')
35
+ : '<span class="ov-fact">Nobody recorded — it falls to the owner</span>';
36
+ return '<li class="ov-row"><div class="ov-row__main">'
37
+ + '<span class="ov-row__title">' + esc(a.title || a.name) + '</span>'
38
+ + '<span class="ov-row__sub">' + esc(a.what) + '</span>'
39
+ + '<div class="ov-row__facts">' + who + '<span class="ov-mono">' + esc(a.seat) + '</span></div>'
40
+ + '</div></li>';
41
+ }).join('') + '</ul>';
42
+ }
43
+
44
+ // The Record form's picker: the same display name the list shows.
45
+ function optionsHtml(list, esc) {
46
+ return (Array.isArray(list) ? list : []).map(function (a) { return '<option value="' + esc(a.key) + '">' + esc(a.title || a.name) + '</option>'; }).join('');
47
+ }
48
+
49
+ function mount() {
50
+ var doc = typeof document !== 'undefined' ? document : null;
51
+ var box = doc && doc.getElementById('gov-access');
52
+ var form = doc && doc.getElementById('gov-access-form');
53
+ var select = doc && doc.getElementById('gov-access-key');
54
+ var esc = window.OTB && window.OTB.escapeHtml;
55
+ if (!box || !form || !select || typeof esc !== 'function') return;
56
+ var toast = function (m) { if (window.OTB && window.OTB.toast) window.OTB.toast(m, { duration: 3200 }); };
57
+ var call = function (method, path, body) {
58
+ return fetch('/api/bongos' + path, {
59
+ method: method,
60
+ credentials: 'same-origin',
61
+ headers: body ? { 'Content-Type': 'application/json' } : {},
62
+ body: body ? JSON.stringify(body) : undefined,
63
+ }).then(function (r) {
64
+ return r.json().catch(function () { return {}; }).then(function (d) { return { ok: r.ok, status: r.status, data: d }; });
65
+ });
66
+ };
67
+ var said = function (r) { var e = r.data && r.data.error; return (e && e.message) || ('It did not save (' + r.status + ').'); };
68
+
69
+ function load() {
70
+ return call('GET', '/government/access').then(function (r) {
71
+ if (!r.ok || !r.data || !Array.isArray(r.data.access)) {
72
+ box.innerHTML = '<p class="ov-row__sub">Outside access could not be read just now.</p>';
73
+ return;
74
+ }
75
+ if (!select.options.length) {
76
+ select.innerHTML = optionsHtml(r.data.access, esc);
77
+ }
78
+ box.innerHTML = accessHtml(r.data.access, esc);
79
+ });
80
+ }
81
+
82
+ box.addEventListener('click', function (ev) {
83
+ var btn = ev.target.closest && ev.target.closest('[data-act=revoke]');
84
+ if (!btn) return;
85
+ btn.disabled = true;
86
+ call('DELETE', '/government/access/' + encodeURIComponent(btn.dataset.key) + '/holders/' + encodeURIComponent(btn.dataset.id))
87
+ .then(function (r) { if (!r.ok) { toast(said(r)); btn.disabled = false; return null; } return load(); });
88
+ });
89
+ form.addEventListener('submit', function (ev) {
90
+ ev.preventDefault();
91
+ var id = Number(doc.getElementById('gov-access-builder').value);
92
+ call('POST', '/government/access/' + encodeURIComponent(select.value) + '/holders', { builder_id: id })
93
+ .then(function (r) {
94
+ if (!r.ok) { toast(said(r)); return null; }
95
+ form.reset();
96
+ return load();
97
+ });
98
+ });
99
+ load();
100
+ }
101
+
102
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
103
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mount);
104
+ else mount();
105
+ }
106
+
107
+ return { accessHtml: accessHtml, optionsHtml: optionsHtml };
108
+ }));
@@ -116,6 +116,26 @@
116
116
  <div id="gov-assignments"><span class="kit-skel"></span></div>
117
117
  </section>
118
118
 
119
+ <!-- task 1005000: who holds the keys OUTSIDE Bongos (GitHub admin, server, DNS,
120
+ hosting, publishing, payments), so an ask can wait on access:<key>.
121
+ Drawn by government-access.js. -->
122
+ <section class="scroll" aria-labelledby="gov-access-h">
123
+ <h2 class="scroll__h" id="gov-access-h">Outside access</h2>
124
+ <p class="scroll__lede">Who holds the keys outside the project, so a blocker that needs one reaches them. This is a record of who to ask: it grants nothing here and stores no password or token.</p>
125
+ <form class="ov-form" id="gov-access-form" autocomplete="off">
126
+ <div class="ov-field ov-field--sm">
127
+ <label class="ov-field__l" for="gov-access-builder">Builder id</label>
128
+ <input id="gov-access-builder" type="number" min="1" placeholder="e.g. 95" required>
129
+ </div>
130
+ <div class="ov-field">
131
+ <label class="ov-field__l" for="gov-access-key">Access</label>
132
+ <select id="gov-access-key" required></select>
133
+ </div>
134
+ <button class="btn-accent" type="submit">Record</button>
135
+ </form>
136
+ <div id="gov-access"><span class="kit-skel"></span></div>
137
+ </section>
138
+
119
139
  <section class="scroll" aria-labelledby="gov-principals-h">
120
140
  <h2 class="scroll__h" id="gov-principals-h">Machine principals</h2>
121
141
  <p class="scroll__lede">Capabilities held by a machine identity, never by a person or a rank. Shown for completeness; nothing here can be granted.</p>
@@ -143,5 +163,6 @@
143
163
  BEFORE government.js, which destructures window.OTBBoardLib at IIFE entry. -->
144
164
  <script src="/builders/board-lib.js?v=2026-09-10-boardlib1"></script>
145
165
  <script src="/builders/government.js?v=2026-09-02-gov-rows"></script>
166
+ <script src="/builders/government-access.js"></script>
146
167
  </body>
147
168
  </html>
@@ -33,6 +33,11 @@ html[data-theme="dark"] body {
33
33
  .hask:last-child { margin-bottom: 0; }
34
34
  .hask__where { display: flex; align-items: center; gap: 8px; min-height: 24px; font-size: 13px; color: var(--ink-soft); min-width: 0; }
35
35
  .hask__dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
36
+ /* the human signal (task 1004922): a person on the hall's "needs you" tint,
37
+ HUMAN TASK or HUMAN DECISION; also drawn bare beside an Awaiting UAT row */
38
+ .hhuman { flex: none; display: inline-flex; align-items: center; gap: 7px; min-height: 28px; padding: 3px 12px 3px 8px; box-sizing: border-box; border-radius: 999px; background: var(--accent-tint); color: var(--accent-ink); font-family: var(--font-mono); font-size: 12.5px; font-weight: 700; letter-spacing: 0.07em; line-height: 1; white-space: nowrap; vertical-align: middle; }
39
+ .hhuman__i { flex: none; width: 20px; height: 20px; }
40
+ .hask__human { display: flex; align-items: center; min-height: 24px; }
36
41
  .hask__where-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
37
42
  .hask__q { margin: 0; font-size: 21px; font-weight: 300; letter-spacing: -0.02em; line-height: 1.2; overflow-wrap: anywhere; }
38
43
  .hask__q a { color: inherit; text-decoration: none; }
@@ -10,11 +10,20 @@
10
10
  // path: switching a row to the card changes how it looks, never what it calls.
11
11
  // An option with `href` is a link (an act that needs its own panel).
12
12
  //
13
- // OTBAsk.html({ where, q, qHref, said, saidHtml, tag, cls, attrs,
13
+ // OTBAsk.html({ where, q, qHref, said, saidHtml, tag, cls, attrs, human,
14
14
  // row: bool, opts: [{ label, result, rec, href, attrs }] })
15
15
  //
16
16
  // Every string is escaped here; `saidHtml` is the one pre-built slot, for a
17
17
  // caller whose facts are already escaped markup (blockerFacts).
18
+ //
19
+ // THE HUMAN SIGNAL (task 1004922, owner 2026-10-28). Everything that waits on
20
+ // a person says so with a person: HUMAN DECISION (a person at a fork) when
21
+ // they must choose, HUMAN TASK (a person with a hand up) when they must do
22
+ // something only a person can do. Every card is a decision unless the caller
23
+ // passes `human: 'task'` (a blocker's owner fix); `human: false` drops it.
24
+ // `OTBAsk.human(kind)` is the same badge for a surface that is not a card
25
+ // (the Awaiting UAT row). The chat half of the rule — `🙋 **HUMAN TASK:**` /
26
+ // `🔀 **HUMAN DECISION:**` — lives in docs/voice.md and the Conductor.
18
27
  (function () {
19
28
  // The shared escaper: window.OTB in the page; under Node (this file is also require()d as a
20
29
  // CommonJS lib — see its module.exports and its tests) dom-utils.js's own export (task 1004876).
@@ -32,19 +41,46 @@
32
41
  : `<button class="${cls}" type="button"${attrsOf(o.attrs)}>${inner}</button>`;
33
42
  }
34
43
 
44
+ // 18px grid, 1.5 stroke, currentColor — the rail's glyph recipe.
45
+ const HUMAN = {
46
+ task: {
47
+ label: 'HUMAN TASK',
48
+ title: 'A person needs to do this',
49
+ svg: '<circle cx="8" cy="5" r="2.2"/><path d="M3.5 16v-2.5a4.5 4.5 0 0 1 9 0V16"/><path d="M12.5 9.5 14.5 3"/>',
50
+ },
51
+ decision: {
52
+ label: 'HUMAN DECISION',
53
+ title: 'A person needs to decide this',
54
+ svg: '<circle cx="7" cy="6" r="2.2"/><path d="M2.5 16v-2a4.5 4.5 0 0 1 9 0v2"/><path d="M13 2.5v3M13 5.5l-2 2.2M13 5.5l2 2.2"/>',
55
+ },
56
+ };
57
+
58
+ function human(kind) {
59
+ const h = HUMAN[kind];
60
+ if (!h) return '';
61
+ return `<span class="hhuman hhuman--${kind}" title="${h.title}">`
62
+ + `<svg class="hhuman__i" viewBox="0 0 18 18" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${h.svg}</svg>`
63
+ + `<span class="hhuman__t">${h.label}</span></span>`;
64
+ }
65
+
35
66
  function html(c) {
36
67
  const tag = c.tag || 'li';
37
68
  const opts = (c.opts || []).filter(Boolean);
38
69
  const q = c.qHref ? `<a href="${esc(c.qHref)}">${esc(c.q)}</a>` : esc(c.q);
70
+ const badge = human(c.human === undefined ? 'decision' : c.human);
71
+ const where = c.where ? `<span class="hask__where-t">${esc(c.where)}</span>` : '';
39
72
  return `<${tag} class="hask${c.cls ? ' ' + esc(c.cls) : ''}"${attrsOf(c.attrs)}>`
40
- + (c.where ? `<div class="hask__where"><span class="hask__dot" aria-hidden="true"></span><span class="hask__where-t">${esc(c.where)}</span></div>` : '')
73
+ // With a where line the badge leads it; without one (the Blockers page
74
+ // dropped its repeated eyebrow) the badge stands on a line of its own.
75
+ + (where ? `<div class="hask__where">${badge || '<span class="hask__dot" aria-hidden="true"></span>'}${where}</div>`
76
+ : (badge ? `<div class="hask__human">${badge}</div>` : ''))
41
77
  + `<h4 class="hask__q">${q}</h4>`
42
78
  + (c.saidHtml ? `<div class="hask__said">${c.saidHtml}</div>` : (c.said ? `<p class="hask__said">${esc(c.said)}</p>` : ''))
43
79
  + (opts.length ? `<div class="hask__opts${c.row ? ' hask__opts--row' : ''}">${opts.map(optHtml).join('')}</div>` : '')
44
80
  + `</${tag}>`;
45
81
  }
46
82
 
47
- const api = { html, esc };
83
+ const api = { html, esc, human };
48
84
  if (typeof window !== 'undefined') window.OTBAsk = api;
49
85
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
50
86
  })();