@bongos/core 1.23.22 → 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.
- package/.bongos-core.json +902 -407
- package/.claude/skills/blocker-review/SKILL.md +9 -1
- package/.claude/skills/blocker-solve/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +1 -1
- package/.claude/skills/builder-reauth/SKILL.md +1 -1
- package/.claude/skills/builder-redteam/SKILL.md +1 -1
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/builder-setup/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -1
- package/.claude/skills/collab-review/SKILL.md +5 -2
- package/.claude/skills/design/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +2 -2
- package/.claude/skills/grade-recover/SKILL.md +1 -1
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/.claude/skills/owner-review/SKILL.md +1 -1
- package/.claude/skills/planning-session/SKILL.md +11 -11
- package/.claude/skills/read-session-export/SKILL.md +1 -1
- package/.claude/skills/recall/SKILL.md +1 -1
- package/.claude/skills/scan-before-install/SKILL.md +1 -1
- package/.claude/skills/session-handoff/SKILL.md +1 -1
- package/.claude/skills/strand-fix/SKILL.md +13 -0
- package/.claude/skills/worktree-clean/SKILL.md +1 -1
- package/AGENTS.md +15 -0
- package/GEMINI.md +5 -0
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +90 -0
- package/clients/bongos-client/index.cjs +90 -0
- package/clients/bongos-client/index.d.ts +149 -4
- package/clients/bongos-client/index.mjs +90 -0
- package/docs/adr/0054-session-token-reward-cost-plus.md +3 -1
- package/docs/adr/0055-server-mediated-branch-publish.md +1 -1
- package/docs/adr/0393-a-platform-account-is-a-person-and-github-is-one-way-in.md +37 -0
- package/docs/adr/0394-market-watch-searches-through-the-messages-api-on-the-projects-key-and-runs-from-its-own-poller.md +102 -0
- package/docs/adr/0395-a-task-says-what-it-needs-and-lists-show-only-what-you-can-do.md +36 -0
- package/docs/adr/0396-google-sign-in-a-stand-in-login-and-a-browser-approved-cli-sign-in.md +41 -0
- package/docs/adr/README.md +5 -1
- package/docs/api/openapi.json +8555 -5876
- package/docs/api-reference.md +63 -10
- package/docs/architecture.md +37 -1
- package/docs/canonical-permissions.md +1 -1
- package/docs/copy-inventory.md +420 -337
- package/docs/copy-registry.json +1321 -500
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +5 -0
- package/docs/onboarding/diagrams/03-drachmae-karma.mmd +3 -3
- package/docs/onboarding/diagrams/README.md +1 -1
- package/docs/onboarding/drachmae-and-karma.md +15 -15
- package/docs/onboarding/primer.md +2 -2
- package/docs/page-inventory.json +55 -6
- package/docs/page-readings.json +1185 -1011
- package/docs/recipes/google-sign-in.md +60 -0
- package/docs/recipes/ops-gotchas.md +1 -1
- package/migrations/core_282_builders_lower_login_idx.sql +33 -0
- package/migrations/core_283_claimable_ignores_page_markers.sql +53 -0
- package/migrations/core_283_payout_approvals.sql +183 -0
- package/migrations/core_284_payout_gates.sql +179 -0
- package/migrations/core_285_task_needs.sql +74 -0
- package/modules/ai-studio/billing.js +67 -0
- package/modules/ai-studio/context.js +29 -0
- package/modules/ai-studio/db.js +172 -0
- package/modules/ai-studio/gemini.js +75 -0
- package/modules/ai-studio/generate.js +119 -0
- package/modules/ai-studio/keys.js +73 -0
- package/modules/ai-studio/migrations/ai_studio_001_keys.sql +22 -0
- package/modules/ai-studio/migrations/ai_studio_002_ledger.sql +48 -0
- package/modules/ai-studio/migrations/ai_studio_003_requests.sql +55 -0
- package/modules/ai-studio/openrouter.js +90 -0
- package/modules/ai-studio/payments.js +105 -0
- package/modules/ai-studio/pricing.js +92 -0
- package/modules/ai-studio/routes/ai-studio.js +235 -5
- package/modules/ai-studio/studio.js +121 -0
- package/modules/autonomy/fence.js +64 -1
- package/modules/builder-settings/builder-needs.js +27 -1
- package/modules/copy-desk/routes/copy-desk.js +5 -0
- package/modules/economy/achievements.js +22 -10
- package/modules/economy/cost-classify.js +9 -0
- package/modules/economy/cost-write-guard.js +52 -0
- package/modules/economy/cost.js +2 -1
- package/modules/economy/credits.js +18 -15
- package/modules/economy/goal-reward.js +161 -0
- package/modules/economy/reward.js +4 -0
- package/modules/economy/routes/cost.js +33 -7
- package/modules/government/catalog.js +6 -0
- package/modules/government/db.js +34 -0
- package/modules/government/docket.js +36 -0
- package/modules/government/migrations/government_035_payout_approve.sql +29 -0
- package/modules/government/pollers/board-expiry.js +108 -20
- package/modules/government/protected-surfaces.json +1 -0
- package/modules/government/routes/government.js +22 -0
- package/modules/government/routing.js +39 -5
- package/modules/government/waiting.js +74 -0
- package/modules/hall-ui/public/ai-studio-lib.js +268 -0
- package/modules/hall-ui/public/ai-studio.css +193 -0
- package/modules/hall-ui/public/ai-studio.html +98 -0
- package/modules/hall-ui/public/ai-studio.js +556 -0
- package/modules/hall-ui/public/ai-studio.states.json +94 -0
- package/modules/hall-ui/public/blockers-by-person.js +156 -0
- package/modules/hall-ui/public/blockers-lib.js +15 -1
- package/modules/hall-ui/public/blockers.html +8 -0
- package/modules/hall-ui/public/blockers.js +1 -1
- package/modules/hall-ui/public/claim-action.js +3 -0
- package/modules/hall-ui/public/collab.html +12 -0
- package/modules/hall-ui/public/craft-mode.js +4 -1
- package/modules/hall-ui/public/criterion-uat-ui.js +4 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/genesis/r5-life-1-primordial-sea.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-2-first-cells-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-2-first-cells.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-3-genome-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-3-genome.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-4-membrane-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-4-membrane.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-5-nervous-system-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-5-nervous-system.jpg +0 -0
- package/modules/hall-ui/public/goal-inbox.js +1 -1
- package/modules/hall-ui/public/goals-render.js +4 -0
- package/modules/hall-ui/public/goals.html +8 -0
- package/modules/hall-ui/public/gov-living.js +6 -2
- package/modules/hall-ui/public/hall-ask.css +5 -0
- package/modules/hall-ui/public/hall-ask.js +39 -3
- package/modules/hall-ui/public/notes-panel.css +20 -0
- package/modules/hall-ui/public/notes-panel.js +146 -0
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/project-settings.js +7 -5
- package/modules/hall-ui/public/settings-ai-keys.js +124 -0
- package/modules/hall-ui/public/settings.html +15 -0
- package/modules/hall-ui/public/street-deck.js +12 -3
- package/modules/hall-ui/public/studio.css +90 -8
- package/modules/hall-ui/public/studio.html +39 -10
- package/modules/hall-ui/public/studio.js +148 -18
- package/modules/hall-ui/public/task.html +2 -0
- package/modules/hall-ui/public/task.js +4 -0
- package/modules/hall-ui/public/tweak-editor.js +12 -3
- package/modules/hall-ui/public/welcome-boot.css +42 -6
- package/modules/hall-ui/public/welcome-boot.js +20 -6
- package/modules/hall-ui/public/work.js +20 -0
- package/modules/hall-ui/records/ai-studio.md +36 -0
- package/modules/hall-ui/records/blockers-people.md +24 -0
- package/modules/lifecycle/db-claims.js +32 -4
- package/modules/lifecycle/db-goals.js +18 -5
- package/modules/lifecycle/db-tasks.js +15 -6
- package/modules/lifecycle/done-when.js +23 -1
- package/modules/lifecycle/lifecycle.js +5 -0
- package/modules/lifecycle/migrations/lifecycle_017_notes.sql +46 -0
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/notes.js +219 -0
- package/modules/lifecycle/page-notice.js +124 -0
- package/modules/lifecycle/page-tweak-artist.js +18 -1
- package/modules/lifecycle/publish-reconciler.js +67 -20
- package/modules/lifecycle/routes/claims.js +5 -2
- package/modules/lifecycle/routes/goals.js +3 -1
- package/modules/lifecycle/routes/notes.js +91 -0
- package/modules/lifecycle/routes/project-settings.js +43 -35
- package/modules/lifecycle/routes/task-route-helpers.js +21 -0
- package/modules/lifecycle/routes/task-write-routes.js +21 -3
- package/modules/lifecycle/routes/tasks.js +12 -12
- package/modules/lifecycle/task-needs-gate.js +70 -0
- package/modules/market-watch/CLAUDE.md +15 -0
- package/modules/market-watch/db.js +340 -0
- package/modules/market-watch/key.js +58 -0
- package/modules/market-watch/migrations/market_watch_001_tables.sql +249 -0
- package/modules/market-watch/migrations/market_watch_002_run.sql +46 -0
- package/modules/market-watch/module.json +26 -0
- package/modules/market-watch/pollers/hub-seed.js +54 -0
- package/modules/market-watch/pollers/weekly-run.js +100 -0
- package/modules/market-watch/research.js +350 -0
- package/modules/market-watch/run.js +245 -0
- package/modules/market-watch/seed/market-data.json +38 -0
- package/modules/market-watch/seed/partners-data.json +497 -0
- package/modules/market-watch/seed.js +252 -0
- package/modules/market-watch/store.js +230 -0
- package/modules/memory/routes/memory.js +14 -2
- package/modules/memory/routes/search.js +8 -2
- package/modules/platform-identity/account-logins.js +192 -0
- package/modules/platform-identity/migrations/platform_identity_034_account_logins.sql +96 -0
- package/modules/platform-identity/platform-identity.js +5 -4
- package/modules/platform-identity/routes/my-projects.js +6 -0
- package/modules/platform-identity/routes/profile.js +18 -0
- package/modules/platform-identity/routes/public-profile.js +10 -1
- package/modules/platform-identity/routes/sso.js +26 -1
- package/modules/platform-identity/tests/platform-identity.mjs +2 -1
- package/modules/provisioning/credential-preflight.js +13 -3
- package/modules/provisioning/pollers/app-liveness.js +25 -5
- package/modules/provisioning/routes/provisioning.js +1 -1
- package/modules/provisioning/starter-bundles.js +6 -2
- package/modules/public-landing/public/account.html +10 -2
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-space-nebula.jpg +0 -0
- package/modules/public-landing/public/assets/world/r3-space-nebula.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg.json +1 -1
- package/modules/public-landing/public/community.html +1 -1
- package/modules/public-landing/public/connections.html +2 -2
- package/modules/public-landing/public/guild.html +1 -1
- package/modules/public-landing/public/invitations.html +2 -2
- package/modules/public-landing/public/projects.html +5 -5
- package/modules/raise/views/nda.html +2 -0
- package/modules/specialities/db.js +32 -1
- package/modules/specialities/routes/specialities.js +10 -6
- package/modules/status-ui/public/index.html +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +220 -0
- package/scripts/bongos/autobongos-tweak-lane.js +46 -1
- package/scripts/bongos/backfill-subagent-rewards.js +2 -2
- package/scripts/bongos/claim.js +22 -1
- package/scripts/bongos/db-backup-nightly.sh +29 -0
- package/scripts/bongos/deploy-divergence.js +7 -0
- package/scripts/bongos/doc-cli-guard.js +6 -0
- package/scripts/bongos/fitness.js +3 -2
- package/scripts/bongos/gen-diagrams.js +3 -3
- package/scripts/bongos/lib.sh +4 -5
- package/scripts/bongos/migration-namespace.js +13 -1
- package/scripts/bongos/plain-cards.js +4 -0
- package/scripts/bongos/provision-github-setup.js +3 -1
- package/scripts/bongos/provision-units.js +6 -1
- package/scripts/bongos/publish-manifest.js +3 -0
- package/scripts/bongos/release.js +2 -0
- package/scripts/bongos/run-unit-tests.js +268 -82
- package/scripts/bongos/seed-tasks.example.json +56 -0
- package/scripts/bongos/seed-tasks.js +222 -0
- package/scripts/bongos/seed-verify-criteria.js +1 -2
- package/scripts/bongos/ship-finish.js +2 -0
- package/scripts/bongos/ship-resume.js +2 -0
- package/scripts/bongos/strand-lease.js +250 -0
- package/scripts/bongos/strand-watch.js +26 -5
- package/scripts/bongos/version-literal-guard.js +2 -2
- package/scripts/discord/post.js +4 -3
- package/scripts/gds/seed-tasks.js +7 -0
- package/scripts/gds/{build-bongos.js → strand-lease.js} +3 -3
- package/scripts/hall-preview/server.js +21 -0
- package/src/bongos/auth-config.js +29 -1
- package/src/bongos/auth-discord.js +3 -3
- package/src/bongos/auth-github.js +3 -6
- package/src/bongos/auth-google.js +217 -0
- package/src/bongos/device-approval.js +140 -0
- package/src/bongos/llm-pricing.js +17 -1
- package/src/bongos/metrics.js +48 -2
- package/src/bongos/middleware/rate-limit.js +30 -11
- package/src/bongos/module-scope-map.js +12 -1
- package/src/bongos/optimizer.js +15 -1
- package/src/bongos/payout-approvals.js +342 -0
- package/src/bongos/payout-gates.js +364 -0
- package/src/bongos/prelaunch-gate.js +7 -9
- package/src/bongos/prelaunch-switch.js +20 -0
- package/src/bongos/route-rank-check.js +3 -0
- package/src/bongos/routes/auth-google.js +246 -0
- package/src/bongos/routes/auth.js +38 -1
- package/src/bongos/routes/backup.js +33 -9
- package/src/bongos/routes/builders.js +57 -30
- package/src/bongos/routes/github.js +9 -0
- package/src/bongos/routes/me.js +18 -0
- package/src/bongos/routes/my-payouts.js +133 -0
- package/src/bongos/routes/payout-approvals.js +312 -0
- package/src/bongos/routes/payout-gates.js +179 -0
- package/src/bongos/routes.js +15 -1
- package/src/bongos/secret-box.js +18 -3
- package/src/bongos/serve-internal.js +47 -19
- package/src/bongos/sign-in-pages.js +138 -0
- package/src/bongos/task-needs.js +146 -0
- package/src/bongos-downloads.js +18 -1
- package/src/instance-config.js +28 -0
- package/src/module-api.js +14 -1
- package/tests/account_logins.mjs +326 -0
- package/tests/account_logins_db.mjs +161 -0
- package/tests/ai_studio_billing.mjs +253 -0
- package/tests/ai_studio_generate.mjs +282 -0
- package/tests/ai_studio_keys.mjs +159 -0
- package/tests/ai_studio_requests.mjs +319 -0
- package/tests/auth_backchannel_logout_route.mjs +201 -0
- package/tests/auto_satisfy_criteria.mjs +10 -1
- package/tests/autobongos_tweak_lane.mjs +144 -0
- package/tests/autobongos_tweak_pin_identity.mjs +137 -0
- package/tests/backfill_subagent_rewards.mjs +17 -1
- package/tests/backup_trigger_lock.mjs +119 -0
- package/tests/blockers_people.mjs +198 -0
- package/tests/bounded_rate_maps.mjs +184 -0
- package/tests/claimable_blocker_status.mjs +1 -1
- package/tests/claims_batch_routes.mjs +421 -0
- package/tests/config_read_cache.mjs +171 -0
- package/tests/copy_desk_page_editor.mjs +12 -0
- package/tests/cost_write_guard.mjs +200 -0
- package/tests/craft_achievements.mjs +33 -10
- package/tests/criterion_goal_attach.mjs +1 -1
- package/tests/db_backup_nightly.mjs +73 -1
- package/tests/deploy_divergence_line.mjs +7 -0
- package/tests/effective_visibility_predicate.mjs +12 -0
- package/tests/fitness.mjs +19 -0
- package/tests/fixtures/seed-tasks/bongos-v1-idea-routing.json +256 -0
- package/tests/fixtures/seed-tasks/bv1-profiles.json +592 -0
- package/tests/fixtures/seed-tasks/bv2-ideator-surface.json +373 -0
- package/tests/fixtures/seed-tasks/fake-api.mjs +80 -0
- package/tests/github_one_trip.mjs +34 -0
- package/tests/github_repo_visibility.mjs +20 -0
- package/tests/goal_achievement.mjs +30 -1
- package/tests/goal_achievement_reward.mjs +205 -0
- package/tests/goal_sort_staleness.mjs +2 -1
- package/tests/goal_tier.mjs +11 -1
- package/tests/google_fonts_nonblocking.mjs +84 -0
- package/tests/google_sign_in.mjs +401 -0
- package/tests/government_board_expiry.mjs +94 -0
- package/tests/government_protected_surfaces.mjs +1 -1
- package/tests/government_routes.mjs +1 -1
- package/tests/hall_ai_studio.mjs +347 -0
- package/tests/hall_page_gate_map.mjs +3 -0
- package/tests/hall_studio_home.mjs +61 -2
- package/tests/home_attention_cards.mjs +1 -1
- package/tests/human_signal.mjs +141 -0
- package/tests/install_script_reader.mjs +51 -0
- package/tests/integration_fixture_ids.mjs +82 -0
- package/tests/invite_expiry_db.mjs +13 -1
- package/tests/lib_sh_resolution.mjs +0 -4
- package/tests/market_watch_data.mjs +244 -0
- package/tests/market_watch_data_db.mjs +308 -0
- package/tests/market_watch_module.mjs +114 -0
- package/tests/market_watch_run.mjs +503 -0
- package/tests/metrics.mjs +124 -0
- package/tests/migration_namespace.mjs +14 -0
- package/tests/module_api.mjs +2 -0
- package/tests/module_loader.mjs +1 -1
- package/tests/notes.mjs +182 -0
- package/tests/own_scope_guard.mjs +5 -0
- package/tests/page_tweak_marker_sql.mjs +76 -0
- package/tests/page_tweak_no_block.mjs +135 -0
- package/tests/page_tweak_notice.mjs +103 -0
- package/tests/payout_approvals.mjs +253 -0
- package/tests/payout_approvals_db.mjs +106 -0
- package/tests/payout_gates.mjs +322 -0
- package/tests/payout_gates_db.mjs +113 -0
- package/tests/planning_session_skill.mjs +13 -3
- package/tests/project_settings_page.mjs +29 -5
- package/tests/project_settings_spend_cap_route.mjs +235 -0
- package/tests/promotion_sweep.mjs +29 -1
- package/tests/provision.mjs +3 -2
- package/tests/provisioning_app_status.mjs +18 -0
- package/tests/provisioning_credential_preflight.mjs +53 -0
- package/tests/provisioning_env_manifest_edit_delete.mjs +223 -0
- package/tests/publish_reconciler_overlap.mjs +60 -0
- package/tests/rank_change_broadcast_port.mjs +151 -0
- package/tests/seed_tasks.mjs +176 -0
- package/tests/seed_verify_criteria.mjs +2 -6
- package/tests/site_classified_once.mjs +142 -0
- package/tests/speciality_db_shape.mjs +23 -0
- package/tests/speciality_session_skills.mjs +48 -2
- package/tests/strand_lease.mjs +249 -0
- package/tests/strand_watch.mjs +10 -0
- package/tests/task_criteria_routes.mjs +275 -0
- package/tests/task_criteria_unlink_db.mjs +123 -0
- package/tests/task_needs.mjs +233 -0
- package/tests/task_needs_db.mjs +110 -0
- package/tests/unit_runner_serial.mjs +266 -43
- package/tests/version_literals.mjs +5 -7
- package/tests/welcome_boot.mjs +16 -5
- package/tests/welcome_boot_browser.mjs +36 -4
- package/docs/recipes/bongos-cli-release.md +0 -66
- package/scripts/bongos/backfill-disciplines.js +0 -144
- package/scripts/bongos/backfill-session-earnings.js +0 -123
- package/scripts/bongos/backfill-session-types.js +0 -87
- package/scripts/bongos/build-bongos.js +0 -94
- package/scripts/bongos/cleanup-orphans.js +0 -74
- package/scripts/bongos/seed-bongos-v1-idea-routing-tasks.js +0 -152
- package/scripts/bongos/seed-bv1-profiles-tasks.js +0 -135
- package/scripts/bongos/seed-bv2-ideator-surface-tasks.js +0 -390
- package/scripts/bongos/smoke-batch-claim.sh +0 -238
- package/scripts/bongos/smoke-batch-release.sh +0 -316
- package/scripts/bongos/smoke-claim-consistency.sh +0 -222
- package/scripts/bongos/smoke-dependencies.sh +0 -143
- package/scripts/gds/backfill-disciplines.js +0 -7
- package/scripts/gds/backfill-session-earnings.js +0 -7
- package/scripts/gds/backfill-session-types.js +0 -7
- package/scripts/gds/cleanup-orphans.js +0 -7
- package/scripts/gds/seed-bongos-v1-idea-routing-tasks.js +0 -6
- package/scripts/gds/seed-bv1-profiles-tasks.js +0 -6
- package/scripts/gds/seed-bv2-ideator-surface-tasks.js +0 -6
- package/scripts/gds/smoke-batch-claim.sh +0 -8
- package/scripts/gds/smoke-batch-release.sh +0 -8
- package/scripts/gds/smoke-claim-consistency.sh +0 -8
- package/scripts/gds/smoke-dependencies.sh +0 -8
- package/tests/smoke_dependencies_witness.mjs +0 -46
|
@@ -121,8 +121,30 @@ export function createClient(opts = {}) {
|
|
|
121
121
|
postAgentsNameInvoke: (args) => request("POST", "/agents/{name}/invoke", { hasBody: true }, args),
|
|
122
122
|
},
|
|
123
123
|
"aiStudio": {
|
|
124
|
+
// GET /ai-studio/balance — rank: any-builder — GET /ai-studio/balance
|
|
125
|
+
getAiStudioBalance: (args) => request("GET", "/ai-studio/balance", { hasBody: false }, args),
|
|
126
|
+
// GET /ai-studio/files/{id} — rank: any-builder — GET /ai-studio/files/:id
|
|
127
|
+
getAiStudioFilesId: (args) => request("GET", "/ai-studio/files/{id}", { hasBody: false }, args),
|
|
128
|
+
// GET /ai-studio/keys — rank: any-builder — GET /ai-studio/keys
|
|
129
|
+
getAiStudioKeys: (args) => request("GET", "/ai-studio/keys", { hasBody: false }, args),
|
|
130
|
+
// DELETE /ai-studio/keys/{provider} — rank: any-builder — DELETE /ai-studio/keys/:provider
|
|
131
|
+
deleteAiStudioKeysProvider: (args) => request("DELETE", "/ai-studio/keys/{provider}", { hasBody: false }, args),
|
|
132
|
+
// PUT /ai-studio/keys/{provider} — rank: any-builder — PUT /ai-studio/keys/:provider
|
|
133
|
+
putAiStudioKeysProvider: (args) => request("PUT", "/ai-studio/keys/{provider}", { hasBody: true }, args),
|
|
134
|
+
// GET /ai-studio/requests — rank: any-builder — GET /ai-studio/requests
|
|
135
|
+
getAiStudioRequests: (args) => request("GET", "/ai-studio/requests", { hasBody: false }, args),
|
|
136
|
+
// POST /ai-studio/requests — rank: any-builder — POST /ai-studio/requests
|
|
137
|
+
postAiStudioRequests: (args) => request("POST", "/ai-studio/requests", { hasBody: true }, args),
|
|
138
|
+
// GET /ai-studio/requests/{id} — rank: any-builder — GET /ai-studio/requests/:id
|
|
139
|
+
getAiStudioRequestsId: (args) => request("GET", "/ai-studio/requests/{id}", { hasBody: false }, args),
|
|
124
140
|
// GET /ai-studio/status — rank: any-builder — GET /ai-studio/status
|
|
125
141
|
getAiStudioStatus: (args) => request("GET", "/ai-studio/status", { hasBody: false }, args),
|
|
142
|
+
// POST /ai-studio/topup — rank: any-builder — POST /ai-studio/topup
|
|
143
|
+
postAiStudioTopup: (args) => request("POST", "/ai-studio/topup", { hasBody: true }, args),
|
|
144
|
+
// GET /ai-studio/topup/quote — rank: any-builder — GET /ai-studio/topup/quote
|
|
145
|
+
getAiStudioTopupQuote: (args) => request("GET", "/ai-studio/topup/quote", { hasBody: false }, args),
|
|
146
|
+
// POST /ai-studio/uploads — rank: any-builder — POST /ai-studio/uploads
|
|
147
|
+
postAiStudioUploads: (args) => request("POST", "/ai-studio/uploads", { hasBody: true }, args),
|
|
126
148
|
},
|
|
127
149
|
"analytics": {
|
|
128
150
|
// GET /analytics/builder/{id} — rank: any-builder — GET /analytics/builder/:id
|
|
@@ -141,6 +163,10 @@ export function createClient(opts = {}) {
|
|
|
141
163
|
postAuthBackchannelLogout: (args) => request("POST", "/auth/backchannel-logout", { hasBody: true }, args),
|
|
142
164
|
// POST /auth/cli-token/issue — rank: any-builder — POST /auth/cli-token/issue
|
|
143
165
|
postAuthCliTokenIssue: (args) => request("POST", "/auth/cli-token/issue", { hasBody: true }, args),
|
|
166
|
+
// GET /auth/device — rank: public — GET /auth/device
|
|
167
|
+
getAuthDevice: (args) => request("GET", "/auth/device", { hasBody: false }, args),
|
|
168
|
+
// POST /auth/device/approve — rank: any-builder — POST /auth/device/approve
|
|
169
|
+
postAuthDeviceApprove: (args) => request("POST", "/auth/device/approve", { hasBody: true }, args),
|
|
144
170
|
// POST /auth/device/exchange — rank: public — POST /auth/device/exchange
|
|
145
171
|
postAuthDeviceExchange: (args) => request("POST", "/auth/device/exchange", { hasBody: true }, args),
|
|
146
172
|
// POST /auth/device/poll — rank: public — POST /auth/device/poll
|
|
@@ -153,6 +179,10 @@ export function createClient(opts = {}) {
|
|
|
153
179
|
getAuthDiscordStart: (args) => request("GET", "/auth/discord/start", { hasBody: false }, args),
|
|
154
180
|
// POST /auth/discord/unlink — rank: any-builder — POST /auth/discord/unlink
|
|
155
181
|
postAuthDiscordUnlink: (args) => request("POST", "/auth/discord/unlink", { hasBody: true }, args),
|
|
182
|
+
// GET /auth/google/callback — rank: public — GET /auth/google/callback
|
|
183
|
+
getAuthGoogleCallback: (args) => request("GET", "/auth/google/callback", { hasBody: false }, args),
|
|
184
|
+
// GET /auth/google/start — rank: public — GET /auth/google/start
|
|
185
|
+
getAuthGoogleStart: (args) => request("GET", "/auth/google/start", { hasBody: false }, args),
|
|
156
186
|
// POST /auth/logout — rank: any-builder — POST /auth/logout
|
|
157
187
|
postAuthLogout: (args) => request("POST", "/auth/logout", { hasBody: true }, args),
|
|
158
188
|
// POST /auth/revoke — rank: archon — POST /auth/revoke
|
|
@@ -229,6 +259,8 @@ export function createClient(opts = {}) {
|
|
|
229
259
|
getBuildersIdActivity: (args) => request("GET", "/builders/{id}/activity", { hasBody: false }, args),
|
|
230
260
|
// PATCH /builders/{id}/budget — rank: metic+archon — PATCH /builders/:id/budget
|
|
231
261
|
patchBuildersIdBudget: (args) => request("PATCH", "/builders/{id}/budget", { hasBody: true }, args),
|
|
262
|
+
// PATCH /builders/{id}/capabilities — rank: archon — PATCH /builders/:id/capabilities
|
|
263
|
+
patchBuildersIdCapabilities: (args) => request("PATCH", "/builders/{id}/capabilities", { hasBody: true }, args),
|
|
232
264
|
// GET /builders/{id}/onboarding — rank: public — GET /builders/:id/onboarding
|
|
233
265
|
getBuildersIdOnboarding: (args) => request("GET", "/builders/{id}/onboarding", { hasBody: false }, args),
|
|
234
266
|
// GET /builders/{id}/profile — rank: public — GET /builders/:id/profile
|
|
@@ -521,6 +553,10 @@ export function createClient(opts = {}) {
|
|
|
521
553
|
postGovernmentAssignments: (args) => request("POST", "/government/assignments", { hasBody: true }, args),
|
|
522
554
|
// DELETE /government/assignments/{builderId}/{rankKey} — rank: archon — DELETE /government/assignments/:builderId/:rankKey
|
|
523
555
|
deleteGovernmentAssignmentsBuilderIdRankKey: (args) => request("DELETE", "/government/assignments/{builderId}/{rankKey}", { hasBody: false }, args),
|
|
556
|
+
// GET /government/blockers/for-me — rank: any-builder — GET /government/blockers/for-me
|
|
557
|
+
getGovernmentBlockersForMe: (args) => request("GET", "/government/blockers/for-me", { hasBody: false }, args),
|
|
558
|
+
// GET /government/blockers/people — rank: any-builder — GET /government/blockers/people
|
|
559
|
+
getGovernmentBlockersPeople: (args) => request("GET", "/government/blockers/people", { hasBody: false }, args),
|
|
524
560
|
// POST /government/board/charter — rank: metic+archon — POST /government/board/charter
|
|
525
561
|
postGovernmentBoardCharter: (args) => request("POST", "/government/board/charter", { hasBody: true }, args),
|
|
526
562
|
// POST /government/board/decision-rules — rank: metic+archon — POST /government/board/decision-rules
|
|
@@ -741,6 +777,8 @@ export function createClient(opts = {}) {
|
|
|
741
777
|
deleteMeFilterViewsId: (args) => request("DELETE", "/me/filter-views/{id}", { hasBody: false }, args),
|
|
742
778
|
// PATCH /me/filter-views/{id} — rank: any-builder — PATCH /me/filter-views/:id
|
|
743
779
|
patchMeFilterViewsId: (args) => request("PATCH", "/me/filter-views/{id}", { hasBody: true }, args),
|
|
780
|
+
// POST /me/guardian-cosigns — rank: any-builder — POST /me/guardian-cosigns
|
|
781
|
+
postMeGuardianCosigns: (args) => request("POST", "/me/guardian-cosigns", { hasBody: true }, args),
|
|
744
782
|
// GET /me/guild-engagements — rank: any-builder — GET /me/guild-engagements
|
|
745
783
|
getMeGuildEngagements: (args) => request("GET", "/me/guild-engagements", { hasBody: false }, args),
|
|
746
784
|
// GET /me/guild-requests — rank: any-builder — GET /me/guild-requests
|
|
@@ -775,6 +813,12 @@ export function createClient(opts = {}) {
|
|
|
775
813
|
postMePasskeysRemove: (args) => request("POST", "/me/passkeys/remove", { hasBody: true }, args),
|
|
776
814
|
// POST /me/passkeys/rename — rank: any-builder — POST /me/passkeys/rename
|
|
777
815
|
postMePasskeysRename: (args) => request("POST", "/me/passkeys/rename", { hasBody: true }, args),
|
|
816
|
+
// POST /me/payout-age — rank: any-builder — POST /me/payout-age
|
|
817
|
+
postMePayoutAge: (args) => request("POST", "/me/payout-age", { hasBody: true }, args),
|
|
818
|
+
// POST /me/payout-freezes/{id}/appeal — rank: any-builder — POST /me/payout-freezes/:id/appeal
|
|
819
|
+
postMePayoutFreezesIdAppeal: (args) => request("POST", "/me/payout-freezes/{id}/appeal", { hasBody: true }, args),
|
|
820
|
+
// GET /me/payouts — rank: any-builder — GET /me/payouts
|
|
821
|
+
getMePayouts: (args) => request("GET", "/me/payouts", { hasBody: false }, args),
|
|
778
822
|
// PATCH /me/profile — rank: any-builder — PATCH /me/profile
|
|
779
823
|
patchMeProfile: (args) => request("PATCH", "/me/profile", { hasBody: true }, args),
|
|
780
824
|
// GET /me/profile-nudge — rank: any-builder — GET /me/profile-nudge
|
|
@@ -793,6 +837,8 @@ export function createClient(opts = {}) {
|
|
|
793
837
|
postMeSessionsRevoke: (args) => request("POST", "/me/sessions/revoke", { hasBody: true }, args),
|
|
794
838
|
// POST /me/sessions/revoke-all — rank: any-builder — POST /me/sessions/revoke-all
|
|
795
839
|
postMeSessionsRevokeAll: (args) => request("POST", "/me/sessions/revoke-all", { hasBody: true }, args),
|
|
840
|
+
// GET /me/sign-in-methods — rank: any-builder — GET /me/sign-in-methods
|
|
841
|
+
getMeSignInMethods: (args) => request("GET", "/me/sign-in-methods", { hasBody: false }, args),
|
|
796
842
|
// GET /me/skill-prefs — rank: any-builder — GET /me/skill-prefs
|
|
797
843
|
getMeSkillPrefs: (args) => request("GET", "/me/skill-prefs", { hasBody: false }, args),
|
|
798
844
|
// PATCH /me/skill-prefs — rank: any-builder — PATCH /me/skill-prefs
|
|
@@ -872,6 +918,16 @@ export function createClient(opts = {}) {
|
|
|
872
918
|
// POST /my-projects/join — rank: any-builder — POST /my-projects/join
|
|
873
919
|
postMyProjectsJoin: (args) => request("POST", "/my-projects/join", { hasBody: true }, args),
|
|
874
920
|
},
|
|
921
|
+
"notes": {
|
|
922
|
+
// GET /notes — rank: any-builder — GET /notes
|
|
923
|
+
getNotes: (args) => request("GET", "/notes", { hasBody: false }, args),
|
|
924
|
+
// POST /notes — rank: any-builder — POST /notes
|
|
925
|
+
postNotes: (args) => request("POST", "/notes", { hasBody: true }, args),
|
|
926
|
+
// DELETE /notes/{id} — rank: any-builder — DELETE /notes/:id
|
|
927
|
+
deleteNotesId: (args) => request("DELETE", "/notes/{id}", { hasBody: false }, args),
|
|
928
|
+
// PATCH /notes/{id} — rank: any-builder — PATCH /notes/:id
|
|
929
|
+
patchNotesId: (args) => request("PATCH", "/notes/{id}", { hasBody: true }, args),
|
|
930
|
+
},
|
|
875
931
|
"npmRelease": {
|
|
876
932
|
// DELETE /npm-release/preview — rank: any-builder — DELETE /npm-release/preview
|
|
877
933
|
deleteNpmReleasePreview: (args) => request("DELETE", "/npm-release/preview", { hasBody: false }, args),
|
|
@@ -918,6 +974,40 @@ export function createClient(opts = {}) {
|
|
|
918
974
|
// GET /page-notes/site — rank: any-builder — GET /page-notes/site
|
|
919
975
|
getPageNotesSite: (args) => request("GET", "/page-notes/site", { hasBody: false }, args),
|
|
920
976
|
},
|
|
977
|
+
"payouts": {
|
|
978
|
+
// POST /payouts/age-records — rank: archon — POST /payouts/age-records
|
|
979
|
+
postPayoutsAgeRecords: (args) => request("POST", "/payouts/age-records", { hasBody: true }, args),
|
|
980
|
+
// GET /payouts/approvals — rank: archon — GET /payouts/approvals
|
|
981
|
+
getPayoutsApprovals: (args) => request("GET", "/payouts/approvals", { hasBody: false }, args),
|
|
982
|
+
// POST /payouts/approve — rank: archon — POST /payouts/approve
|
|
983
|
+
postPayoutsApprove: (args) => request("POST", "/payouts/approve", { hasBody: true }, args),
|
|
984
|
+
// POST /payouts/contracts — rank: archon — POST /payouts/contracts
|
|
985
|
+
postPayoutsContracts: (args) => request("POST", "/payouts/contracts", { hasBody: true }, args),
|
|
986
|
+
// POST /payouts/freezes — rank: archon — POST /payouts/freezes
|
|
987
|
+
postPayoutsFreezes: (args) => request("POST", "/payouts/freezes", { hasBody: true }, args),
|
|
988
|
+
// POST /payouts/freezes/{id}/decide — rank: archon — POST /payouts/freezes/:id/decide
|
|
989
|
+
postPayoutsFreezesIdDecide: (args) => request("POST", "/payouts/freezes/{id}/decide", { hasBody: true }, args),
|
|
990
|
+
// GET /payouts/gates/{builderId} — rank: archon — GET /payouts/gates/:builderId
|
|
991
|
+
getPayoutsGatesBuilderId: (args) => request("GET", "/payouts/gates/{builderId}", { hasBody: false }, args),
|
|
992
|
+
// POST /payouts/grants — rank: archon — POST /payouts/grants
|
|
993
|
+
postPayoutsGrants: (args) => request("POST", "/payouts/grants", { hasBody: true }, args),
|
|
994
|
+
// POST /payouts/guardians — rank: archon — POST /payouts/guardians
|
|
995
|
+
postPayoutsGuardians: (args) => request("POST", "/payouts/guardians", { hasBody: true }, args),
|
|
996
|
+
// GET /payouts/rules — rank: archon — GET /payouts/rules
|
|
997
|
+
getPayoutsRules: (args) => request("GET", "/payouts/rules", { hasBody: false }, args),
|
|
998
|
+
// POST /payouts/rules — rank: archon — POST /payouts/rules
|
|
999
|
+
postPayoutsRules: (args) => request("POST", "/payouts/rules", { hasBody: true }, args),
|
|
1000
|
+
// POST /payouts/rules/{id}/narrow — rank: archon — POST /payouts/rules/:id/narrow
|
|
1001
|
+
postPayoutsRulesIdNarrow: (args) => request("POST", "/payouts/rules/{id}/narrow", { hasBody: true }, args),
|
|
1002
|
+
// POST /payouts/rules/{id}/revoke — rank: archon — POST /payouts/rules/:id/revoke
|
|
1003
|
+
postPayoutsRulesIdRevoke: (args) => request("POST", "/payouts/rules/{id}/revoke", { hasBody: true }, args),
|
|
1004
|
+
// POST /payouts/rules/{id}/runs — rank: archon — POST /payouts/rules/:id/runs
|
|
1005
|
+
postPayoutsRulesIdRuns: (args) => request("POST", "/payouts/rules/{id}/runs", { hasBody: true }, args),
|
|
1006
|
+
// POST /payouts/rules/{id}/runs/{runKey}/hold — rank: archon — POST /payouts/rules/:id/runs/:runKey/hold
|
|
1007
|
+
postPayoutsRulesIdRunsRunKeyHold: (args) => request("POST", "/payouts/rules/{id}/runs/{runKey}/hold", { hasBody: true }, args),
|
|
1008
|
+
// POST /payouts/screenings — rank: archon — POST /payouts/screenings
|
|
1009
|
+
postPayoutsScreenings: (args) => request("POST", "/payouts/screenings", { hasBody: true }, args),
|
|
1010
|
+
},
|
|
921
1011
|
"profiles": {
|
|
922
1012
|
// GET /profiles/{handle} — rank: public — GET /profiles/:handle
|
|
923
1013
|
getProfilesHandle: (args) => request("GET", "/profiles/{handle}", { hasBody: false }, args),
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
**Date:** 2026-06-13
|
|
4
4
|
**Context:** GDS-V4, task [#1005](https://example.com/builders#/task/1005). Builds on [ADR 0023](0023-kind-multipliers-and-idea-bonus.md) (per-task kind-multiplied credits), [ADR 0028](0028-honest-llm-spend-accounting.md) (the `llm-pricing.js` token→USD table + session-cost capture), and [ADR 0027](0027-bfg-session-inefficiency-evaluator.md) (the `session_records` upload-on-ship path this reward rides). Complements — does **not** replace — the per-task `credits_reward × kind_multiplier` path. Related future work: the full-transcript capture ADR (planned, task [#1004](https://example.com/builders#/task/1004)) will give this reward a more trustworthy raw-usage source, and the token-feed hardening scoped in task [#1005](https://example.com/builders#/task/1005) itself.
|
|
5
|
-
**Status:** Accepted.
|
|
5
|
+
**Status:** Accepted. Superseded in part by [ADR 0385](0385-money-lives-with-a-processor-credits-stay-a-project-meter.md) and [ADR 0387](0387-two-ledgers-credits-measure-work-money-stays-with-the-processor.md) (credits are a contribution meter, not equity or money).
|
|
6
6
|
**Track:** `internal` (development system / methodology).
|
|
7
7
|
|
|
8
|
+
> **Superseded in part (owner ruling, 2026-10-29; [ADR 0385](0385-money-lives-with-a-processor-credits-stay-a-project-meter.md) decision 2, [ADR 0387](0387-two-ledgers-credits-measure-work-money-stays-with-the-processor.md)).** The framing below that credits ("example") are **equity**, a stake or units of ownership, and the 2026-06-15 reading that **1 drachma = $1**, no longer hold. Credits are a project-scoped contribution meter: never money, never redeemable, never equity, and no dollar value is read off them. Equity, if a project offers any, is granted by that project separately. The formula itself is unchanged: the session reward is still *sized by* the session's cost in USD plus the margin. The waste mitigation in reason 2 below ("a builder who pads cost dilutes a pool they hold") rested on the equity framing and no longer applies. The rest of this ADR (server-side pricing, PASS gate, idempotency per session) stands.
|
|
9
|
+
|
|
8
10
|
## Problem
|
|
9
11
|
|
|
10
12
|
We are about to onboard the first **incentivized** builders. Today the only payout is per-task `credits_reward` — a unitless number an author sets at task-creation time. It rewards declaring a big number, not doing expensive, real work. The owner's decision (2026-06-13) is to pay builders proportionally to the **true economic cost** of the work they did — the API token spend of the session — plus a margin, with example standing in as **equity** in the project. It must go live today, be idempotent and reversible, and be impossible to inflate with a client-sent dollar figure.
|
|
@@ -62,4 +62,4 @@ A **fine-grained GitHub PAT** scoped to the single repo: `Contents: read/write`
|
|
|
62
62
|
confirmed->shipped flip.
|
|
63
63
|
|
|
64
64
|
- **Residual:** the server verifies the bundle tip == the claimed sha and that the caller holds the claim, but does not cheaply prove every commit in the bundle was authored under the claim — branch protection + required checks on `main` (which the credential cannot bypass) are the real gate, exactly as for any PR.
|
|
65
|
-
- **Operator action — DONE (blocker 31, resolved 2026-06-13):** the GitHub App was created and `GITHUB_APP_ID` + `GITHUB_APP_INSTALLATION_ID` + `GITHUB_APP_PRIVATE_KEY` set on the droplet (task 1043); `GITHUB_PUSH_TOKEN` then removed. To rotate the key: generate a new private key in the App's settings, replace `GITHUB_APP_PRIVATE_KEY` (single-line, literal `\n
|
|
65
|
+
- **Operator action — DONE (blocker 31, resolved 2026-06-13):** the GitHub App was created and `GITHUB_APP_ID` + `GITHUB_APP_INSTALLATION_ID` + `GITHUB_APP_PRIVATE_KEY` set on the droplet (task 1043); `GITHUB_PUSH_TOKEN` then removed. To rotate the key: generate a new private key in the App's settings, replace `GITHUB_APP_PRIVATE_KEY` (single-line, literal `\n`, wrapped in double quotes — unquoted, systemd drops the backslashes and the key no longer decodes; task 1005009) in `/etc/example/web-source.env`, restart `example`. To roll back to the PAT: restore `web-source.env.bak-task1043` and set a fresh `GITHUB_PUSH_TOKEN` (with the App vars absent, `resolveToken()` falls back to the PAT).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 0393 — A platform account is a person, and GitHub is one way in
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-10-29
|
|
5
|
+
- **Task:** [task 1004992](https://cloudbongos.com/builders#/task/1004992) (goal 1000095, work area 6)
|
|
6
|
+
- **Deciders:** the owner (Google as the only new way in, no email codes or passwords; GitHub and Google linkable both ways; never join accounts on a matching email); Claude designed
|
|
7
|
+
- **Related:** ADR 0016 (trust boundary), **0141 (the hub is an identity provider)**, 0171 (profile consent), 0205 (the hub's own sign-in witness), 0213 (terms acceptance)
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Every platform account on the hub was a GitHub account: `platform_identity_accounts` was keyed on GitHub's user id, and that column (`github_id`) is the join key of every other hub table and the identity every federated project receives at sign-in. A person without GitHub could not exist on the platform at all, so they could not ideate, do art or earn credits in any project, although none of that work touches GitHub.
|
|
12
|
+
|
|
13
|
+
The owner wants people to sign up with Google, and GitHub and Google to be linkable to one account either way. GitHub stays necessary for two things only: creating a project (its code lives on GitHub) and engineering work.
|
|
14
|
+
|
|
15
|
+
Renaming `github_id` everywhere was the obvious move and the wrong one: dozens of hub tables, every federated instance's `builders` table, and the wire format between them all carry it. A rename would be a breaking change across instances that upgrade on their own schedules.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
1. **An account is a person; its ways in are rows.** `platform_identity_account_logins` (migration 034) holds one row per provider identity: `(account, provider, subject)`, where `subject` is the provider's own stable user id (GitHub's numeric id, Google's `sub`). Never an email or a login: both can change hands.
|
|
20
|
+
2. **The database enforces ownership.** `UNIQUE (provider, subject)`: a provider identity belongs to exactly one person. `UNIQUE (account, provider)`: a person holds at most one way in per provider. A `CHECK` lists the providers (`github`, `google`); a new one is a decision, not a string.
|
|
21
|
+
3. **`github_id` keeps its name and becomes the person key.** Positive is the GitHub user id of a person who arrived through GitHub (every account before this change, unchanged). Negative is minted from `platform_identity_person_key_seq` for a person who arrived without GitHub. GitHub ids are positive, so a minted key can never collide with one, and a key never changes when a way in is linked or removed. No wire format changes.
|
|
22
|
+
4. **`github_login` is NULL for a person with no GitHub linked.** Inventing a login would let a real GitHub user of that name be taken for this person by the invite-by-login path.
|
|
23
|
+
5. **GitHub-born accounts need no writer change.** A trigger writes the `github` login row for every account inserted with a positive key, and the migration backfills every existing account. A non-GitHub account is born only through `createAccountForLogin`, which mints the key and writes the account and its way in in one transaction, and returns the existing person on a repeat or racing sign-up.
|
|
24
|
+
6. **An email is shown, never looked up.** It is stored as the provider vouched for it, for the person's own settings. No lookup keys on it, so owning a matching address never reaches an account. Linking two ways in requires the person to be signed in to both (the owner: suggest a link on a matching email, never join one).
|
|
25
|
+
7. **Identity, never authority.** Nothing here carries a rank, a credit or an admission (ADR 0016, ADR 0141).
|
|
26
|
+
|
|
27
|
+
## Consequences
|
|
28
|
+
|
|
29
|
+
- A person key no longer says which provider someone used. Code that needs to talk to GitHub must ask for the linked GitHub login, never assume the key is a GitHub id. The hub's numeric-key guard (`isNumericGithubId`) now admits a negative key and still refuses a `system:*` principal.
|
|
30
|
+
- **Every sign-in must resolve the person through the logins table first** once linking exists. Until then the mapping is trivially exact (a GitHub-born key IS its GitHub id), but the moment GitHub can be linked to a Google-born account, a GitHub sign-in that keys straight on the GitHub id would create a second person. The linking task (task 1004995) must route GitHub sign-ins through `findPersonByLogin` before it lets anyone link.
|
|
31
|
+
- Federated projects will start receiving negative keys when Google-born people sign in to them. An instance's `builders.github_id` is text, so it stores one, but any instance code that assumes a GitHub-shaped id must be found and fixed before those people are admitted (task 1004994), and an older core should not be sent one.
|
|
32
|
+
- A minted key's fallback handle reads `builder--N`; the handle normally grows from the name the provider gave.
|
|
33
|
+
- The own-scope read `GET /me/sign-in-methods` lists a person's ways in for account settings. The provider ids never cross.
|
|
34
|
+
|
|
35
|
+
## What this does not do
|
|
36
|
+
|
|
37
|
+
It adds no sign-in method by itself. Signing in with Google is task 1004993; halls admitting Google-born builders is task 1004994; linking both ways is task 1004995; the Link GitHub chip and the guided GitHub step are tasks 1004996 and 1004997.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# 0394 — Market Watch searches the web through the Messages API on the project's own key, and runs from its own clock-anchored poller
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-10-29
|
|
5
|
+
- **Task:** [task 1004934](https://cloudbongos.com/builders#/task/1004934) (goal 1000137, the R01 spike)
|
|
6
|
+
- **Deciders:** Claude investigated and chose; the owner set the constraints (on/off per project, a monthly AI cap, paid by the project, fail closed)
|
|
7
|
+
- **Related:** ADR 0016 (trust boundary), 0054 (credits), 0073 (keys in Settings, secret box), 0083 (modules), 0089 §3 (ModelProvider port), **0385 (Bongos holds no money)**, **0392 (AI generation and own keys)**
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Market Watch (goal 1000137) is an opt-in, weekly, AI-run watch on a project's competitors, partners and topics. Each tracked item gets a dated log of sourced findings and a score history. The owner's rules: it is switched on per project, paid for by the project, capped monthly, and it fails closed. Before R04 ([task 1004937](https://cloudbongos.com/builders#/task/1004937)) builds the weekly run, two questions need an answer: **how does a run reach the web**, and **what fires it each week**.
|
|
12
|
+
|
|
13
|
+
"Per project" means per instance. Every project is its own instance, with its own database and server process (`modules/provisioning`; the hub is one of them). A run that lives in a project's server process cannot write to another project, because it holds no connection to one.
|
|
14
|
+
|
|
15
|
+
### What exists today (read from the code, 2026-10-29)
|
|
16
|
+
|
|
17
|
+
**The grade port, `runSubagentCached`** (`modules/grading/grader-subagent.js`). It spawns the `claude` CLI: `claude -p --tools <opts.tools ?? 'Read'> --strict-mcp-config --permission-mode bypassPermissions`. The tool list is a passthrough string (task 1001691), so a caller *could* ask for `WebSearch,WebFetch`. Three things stop that from being the answer:
|
|
18
|
+
- **The server image has no `claude` CLI.** The `Dockerfile` installs `postgresql-client bash tini curl ca-certificates git` and `npm ci --omit=dev`. `package.json` carries `@anthropic-ai/sdk`, not the CLI. On a deployed instance the spawn fails with `SUBAGENT_SPAWN_FAILED` unless an operator adds the binary, sets `CLAUDE_BIN`, and signs it in. The port works where `ship.js` runs it: on a builder's machine.
|
|
19
|
+
- **The agents module never passes tools.** `modules/agents/spawn.js` sends `opts: { model }` only. Its header says v1 agents are "read-only by plumbing", and a write- or web-capable spawn needs a change in `grading`, which is protected.
|
|
20
|
+
- **The port's cache is wrong for news.** `runSubagentCached` replays a stored answer at $0 on a key hit. A watch that replays last week's search has found nothing.
|
|
21
|
+
|
|
22
|
+
**The agents module and the cron trigger.** `agents_005_cron_trigger.sql` made `'cron'` storable on `agents_definitions` and `agents_runs`, with a required `trigger_spec.cadence` (`weekly` among the allowed values). Nothing selects cron agents yet. Also, `spawn.js plannedRow` maps every trigger that is not `'on-demand'` to `'event'`. A cron fire through today's spawn path would be recorded in the ledger as an event fire. The agents module is `default: false`, and on the hub `GET /api/bongos/agents` answers **404**: it is not enabled there (checked 2026-10-29). Its spend guards (`lib/spend-ceiling.js`: per payer and per definition, both fail closed) bound agent fires. They do not express "this project's Market Watch budget".
|
|
23
|
+
|
|
24
|
+
**Why the cron-router poller was abandoned.** Task 1002498 (`modules/agents/pollers/cron-router.js`, one timer per cadence bucket) is `abandoned` with the note "WA6 review 2026-09-28: not necessary now (owner call)". There was no technical blocker. Its enabling bug (task 1004008, no `'cron'` type) shipped afterwards as agents_005, so the poller would select real rows today. But it would fire them through the grade port, which has no CLI on the server.
|
|
25
|
+
|
|
26
|
+
**The autonomy module's timers** (`modules/autonomy/pollers/`). Two patterns sit side by side:
|
|
27
|
+
- `routine-timers.js` uses a **boot-anchored** `setInterval` (peer-vote-tally, weekly). It first fires about 90 s after each boot. The hub redeploys on every merge, so in practice it runs once per deploy and almost never completes a 7-day interval. That is harmless for an idempotent tally and wrong for a paid run, which would be charged once per deploy.
|
|
28
|
+
- `daily-checks.js` uses a **clock-anchored** timer: an hourly unref'd tick asks the ledger whether the last run is due ("DAILY BY THE CLOCK, NOT BY THE BOOT"), with a 30-minute slack so runs do not drift. It is kill-switched by `ROUTINE_TIMERS_DISABLED=1` and never throws into the server.
|
|
29
|
+
|
|
30
|
+
Any module can contribute a poller of its own: `contributes.pollers` in `module.json`, started by the loader at boot (docs/modules-contract.md §Start pollers).
|
|
31
|
+
|
|
32
|
+
**How the server calls an LLM with tools today.** It doesn't. `modules/discord/discord-support.js` makes the one server-side Messages API call, through `@anthropic-ai/sdk`, with **no tools**, on an env-only, namespaced key (`DISCORD_SUPPORT_ANTHROPIC_API_KEY`). `modules/ai-studio` calls OpenRouter and Gemini over HTTPS.
|
|
33
|
+
|
|
34
|
+
**Keys in Settings.** Task 1004919 shipped `ai_studio_provider_keys`: **per-builder** keys for `openrouter` and `gemini` only (a CHECK constraint), encrypted with `api.secretBox`, never returned. There is no project-level key, no Anthropic key, and no search-API key in Settings.
|
|
35
|
+
|
|
36
|
+
### Cost measurements
|
|
37
|
+
|
|
38
|
+
**Measured (n=2, 2026-10-29), the tool-enabled CLI path.** I ran the grade port's exact spawn shape (`claude -p --model claude-sonnet-5 --output-format json --tools WebSearch,WebFetch --strict-mcp-config`) on one real watch item each: "find this week's news on X, at most 2 searches and 2 fetches, answer as JSON entries with a source URL and a confidence level". The CLI's own `total_cost_usd` envelope:
|
|
39
|
+
|
|
40
|
+
| Item | Cost | Wall time | Searches | Sourced entries |
|
|
41
|
+
|---|---|---|---|---|
|
|
42
|
+
| Linear | $0.1156 | 41 s | 2 | 2 |
|
|
43
|
+
| Replit | $0.0964 | 23 s | 2 | 3+ |
|
|
44
|
+
|
|
45
|
+
Mean **$0.106 per item**, so **≈ $4.24 for a 40-item run**. About half of each item's cost is the CLI's own overhead: 34–43k cache-read tokens of its system prompt, plus a Haiku 4.5 sub-call that reads the search results.
|
|
46
|
+
|
|
47
|
+
**Estimated, the direct Messages API path chosen below.** These are the repo's own rates (`src/bongos/llm-pricing.js`: sonnet = Claude Sonnet 5, $2 in / $10 out per million tokens) and Anthropic's published web-search price, **$10 per 1,000 searches**, with web fetch billed as tokens only (from the claude-api reference, cached 2026-10-06; re-verify before R04 prices a cap). Per item: 2 searches = $0.020. About 25k tokens of search and page content read by Sonnet 5 = $0.050. About 2k tokens out, thinking included = $0.020. The watch rules, about 3k tokens, are cached across the run and cost close to nothing. That comes to **≈ $0.09 per item, ≈ $3.60 for 40 items**. This is not measured. The token volumes come from the two runs above, where the search content was 23k and 35k tokens.
|
|
48
|
+
|
|
49
|
+
**The planning figure is $4–5 per weekly 40-item run, about $17–22 a month.** The measured and estimated paths land within 20% of each other. **Cost does not decide between them; deployability does.**
|
|
50
|
+
|
|
51
|
+
## Decision
|
|
52
|
+
|
|
53
|
+
### 1. Web access: the Anthropic Messages API with server-side web tools, on the project's own key
|
|
54
|
+
|
|
55
|
+
R04 calls `@anthropic-ai/sdk` (already a dependency) **from the market-watch module's own code**, one request per tracked item. The request uses model `claude-sonnet-5`, the tools `web_search_20260209` (`max_uses: 3`) and `web_fetch_20260209` (`max_uses: 2`), a bounded `max_tokens`, and structured output for the entries. The watch rules come first, in the system prompt, under `cache_control`. A `refusal`, a `max_tokens` stop, or a tool error block (these arrive with HTTP 200 and do not raise) ends that item with a recorded error. It never becomes an entry.
|
|
56
|
+
|
|
57
|
+
**Not through the grade port.** That port spawns a CLI the server does not have, caches answers, and is tied to grading. This is a new LLM caller, the second server-side one after discord-support. Its model id stays a single constant in the module, so the ModelProvider port (ADR 0089 §3) can take it over later.
|
|
58
|
+
|
|
59
|
+
**The key is the project's, set in Settings.** The project owner pastes an Anthropic API key into Market Watch settings (R03, [task 1004938](https://cloudbongos.com/builders#/task/1004938)). It is stored with `api.secretBox`, shown only as its last four characters, never returned or logged, and decrypted only inside a run (the ai-studio rules). Because the key is the project's own account, the project pays the provider directly. Bongos holds no money (ADR 0385) and charges no markup (ADR 0392's multiplier applies to *our* key, and Market Watch has none). **No key means no run**: the run is recorded as skipped with reason `no_key`, and the page shows it.
|
|
60
|
+
|
|
61
|
+
### 2. Scheduling: Market Watch's own clock-anchored poller, on the daily-checks pattern
|
|
62
|
+
|
|
63
|
+
The market-watch module declares `contributes.pollers: ["weekly-run"]`. That poller is a copy of the `autonomy/pollers/daily-checks.js` shape, not an import of it:
|
|
64
|
+
- one unref'd **hourly** tick (env-overridable), first tick a few minutes after boot plus jitter, kill-switched by `ROUTINE_TIMERS_DISABLED=1`, never throws into the server;
|
|
65
|
+
- **due by the ledger, not by the boot**: a run starts when Market Watch is on and the newest `market_watch_runs` row is at least 7 days old minus a 30-minute slack. A deploy restarts the timer and changes nothing about when the next run is due;
|
|
66
|
+
- **resumable**: the hub redeploys on every merge, so a 20–30-minute run will sometimes be cut off. Each item's result is written as it finishes. A run left `running` for longer than its timeout is picked up by the next tick, which does only the items it has not done, and never charges twice for an item;
|
|
67
|
+
- **one run at a time**: a row-level claim on the run (e.g. `UPDATE … WHERE status = 'queued' RETURNING`) so a second process on the same database cannot start a duplicate.
|
|
68
|
+
|
|
69
|
+
### 3. Money: capped, recorded, fail closed
|
|
70
|
+
|
|
71
|
+
- Before a run, and again **before each item**, the poller reads this calendar month's Market Watch spend. If `spent + one item's ceiling > cap`, it stops. A run that cannot start is recorded as `skipped / cap_reached`. A run that stops partway is recorded as `partial / cap_reached`, with the items it did not reach. **If the spend cannot be read, nothing runs** (`spend_unreadable`), as in `agents/lib/spend-ceiling.js`.
|
|
72
|
+
- **An item's ceiling** is fixed by its bounds: 3 searches ($0.03), plus input bounded by `max_content_tokens` on the fetches, plus `max_tokens` out. R04 computes it from those numbers and states it in code, about $0.25 at the bounds above. **No item can cost more than its ceiling**, and no item starts unless its whole ceiling fits under the cap, so the cap is never overshot.
|
|
73
|
+
- **The real cost** of each item is priced from the response's `usage` (tokens through `llm-pricing.priceUsage`, plus `usage.server_tool_use.web_search_requests` × the search price). Each item is written to `cost_log` under source `market-watch:<run id>` with `category: 'api'` through the economy port, keyed so a retry cannot post twice. It is the same projection rule `agents/spawn.js mirrorCostToLedger` follows.
|
|
74
|
+
|
|
75
|
+
## Rejected options
|
|
76
|
+
|
|
77
|
+
| Option | Why not |
|
|
78
|
+
|---|---|
|
|
79
|
+
| **Grade port with `tools: 'WebSearch,WebFetch'`** (the tool-enabled run) | Measured at a similar cost ($0.106/item), so there is nothing to gain. The server image has no `claude` CLI, so a deployed project would need a CLI install, `CLAUDE_BIN` and a signed-in CLI on every instance. The spawn runs under `bypassPermissions`. Opening the agents spawn path to tools means editing the protected `grading` module. The port's cache replays old answers. |
|
|
80
|
+
| **Revive the cron-router poller (task 1002498) and run Market Watch as a cron agent** | The owner set it aside ("not necessary now"). It would fire through the same grade port with no CLI on the server. The agents module is off on the hub. Its ledger would record the fire as `event` (`plannedRow`). Its ceilings are per payer and per definition, not the per-project monthly cap the owner asked for. Reviving it means building four things to use one. |
|
|
81
|
+
| **Autonomy's boot-anchored `routine-timers` pattern** | On an instance that redeploys on every merge it fires on each boot, so a paid run would be charged once per deploy. |
|
|
82
|
+
| **A separate search API (Brave, Tavily, Exa, or OpenRouter's web plugin) plus an LLM call** | Two vendors, two keys and two bills for one run, plus a fetch-and-clean step we would have to write. Search fees are about a fifth of the per-item cost, so a cheaper search API saves at most about $0.01 an item. Their prices were not checked for this ADR. The existing OpenRouter key is **per builder**, so using it would bill a person, not the project. |
|
|
83
|
+
| **The builder's own Claude Code session** (a local scheduled task) | Not unattended: a closed laptop is a missed week. It bills a builder, not the project, and its spend never reaches the project's cap or `cost_log`. |
|
|
84
|
+
| **Haiku as the research model** | Cheaper per token, but the dynamic-filtering web tools (`_20260209`) need Sonnet 4.6+ or Opus 4.6+. Also, `llm-pricing.js` prices the haiku family at Haiku 4.5's rate, so a newer Haiku would be mispriced until a task adds its row. |
|
|
85
|
+
|
|
86
|
+
## What R04 ([task 1004937](https://cloudbongos.com/builders#/task/1004937)) must build
|
|
87
|
+
|
|
88
|
+
1. `modules/market-watch/research.js`: one Messages API call per item, with the tools, bounds and structured output above. It is pure apart from an injected client, so tests run with no network. Entries without a `source_url` that appeared in a search or fetch result are dropped. That check runs in code, not in the prompt.
|
|
89
|
+
2. `modules/market-watch/pollers/weekly-run.js`: the hourly, ledger-due, resumable, single-flight poller in Decision §2, with the cap checks in §3 before the run and before each item.
|
|
90
|
+
3. Cost: price each item from `usage`. **Add the web-search price to `src/bongos/llm-pricing.js`** (it has no tool fees today) as the single place it is spelled. Write to `cost_log` as `market-watch:<run id>` through the economy port.
|
|
91
|
+
4. Visible outcomes for every run: `done`, `partial`, or `skipped` with `no_key`, `cap_reached`, `spend_unreadable` or `off`. These are rows in the run table (R02, [task 1004936](https://cloudbongos.com/builders#/task/1004936)), never only log lines.
|
|
92
|
+
5. The key: a project-scoped, owner-set, secret-boxed key read only inside a run. R03 builds the Settings surface, and R04 reads it through one accessor.
|
|
93
|
+
6. Tests: a run on seed data produces sourced entries and score snapshots. A run over the cap is skipped and visible. A spend read that throws runs nothing. A cut-off run resumes without charging an item twice. The cost appears in the roll-up.
|
|
94
|
+
|
|
95
|
+
R04 must **not** touch `.github/workflows/`, the grading module, or the agents spawn path.
|
|
96
|
+
|
|
97
|
+
## Consequences
|
|
98
|
+
|
|
99
|
+
- A project owner turns Market Watch on with their own Anthropic key and a cap. A 40-item watch costs about $4–5 a week on that key. Bongos never touches the money.
|
|
100
|
+
- The server gains a second direct LLM caller. If a third appears, the shared shape (key from Settings, bounded tools, usage priced into `cost_log`) should become a kernel helper, not a third copy.
|
|
101
|
+
- The cron-router poller stays abandoned. If agents ever need a weekly cadence, they should reuse this clock-anchored pattern and first fix `plannedRow`'s mapping of `cron` to `event`.
|
|
102
|
+
- **Unverified:** the direct-API figure is an estimate built from two CLI-measured items, and the web-search price is from a cached reference. R04's first real run on the hub (R09, [task 1004942](https://cloudbongos.com/builders#/task/1004942)) is the measurement that replaces both.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 0395 — A task says what it needs, and each person's lists show only what they can do
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-10-29
|
|
5
|
+
- **Deciders:** The owner (direction of 2026-10-29: "what someone sees should be something they can actually do"; on this project everyone still sees everything).
|
|
6
|
+
- **Task:** [task 1004950](https://cloudbongos.com/builders#/task/1004950)
|
|
7
|
+
- **Related:** [ADR 0016](0016-trust-boundary-server-enforced-permissions.md) (authority read fresh, server-side), [ADR 0351](0351-a-criterion-closes-on-a-uat.md) (how the owner seat is read), the claim refusal ladder in `modules/lifecycle/CLAUDE.md`.
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
On 2026-10-29 most of the 21 claimable maintenance tasks needed the live server, a signed-in browser or an owner decision. An agent on a builder machine picked one up, read it and stopped. The lists that recommend work offered tasks the reader could not finish.
|
|
12
|
+
|
|
13
|
+
The tasks table already had two session-fit fields: `automation_tag` (`overnight-safe`, `live-only`, `needs-input`) and `manual_degree`. Both answer *can this run unattended?* and feed the session-fit projection. Neither answers *who can do this at all?*
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
**1. `tasks.needs` is its own set, not a new `automation_tag` value.** A subset of `owner` (a decision, account or permission only the project owner holds) and `server` (hands-on work on the live host: DNS, secrets, the database by hand). Empty for most tasks. A task can need both. Overloading `automation_tag` would make one field answer two questions, and a task can be overnight-safe *and* need the server. A CHECK constraint holds the vocabulary; `src/bongos/task-needs.js` holds the same list and a test pins the two equal.
|
|
18
|
+
|
|
19
|
+
**2. Capabilities are resolved on the server, per request, uncached — like rank.** `owner` comes from the government owner seat (`positionsFor`), with the founding builder as the fallback, exactly as the criterion UAT sign-off reads it. `server` comes from `builders.capability_grants`, which only the person in the owner seat sets (`PATCH /builders/:id/capabilities`, also behind `builder.status.set`). Default off. Holding the owner seat does not imply server access: the owner's own agent runs on a builder machine too. Nothing in a request body can assert a capability.
|
|
20
|
+
|
|
21
|
+
**3. Only the lists that RECOMMEND work narrow.** The claimable feed (`/builder-start`, the work board's ready queue, Home's Next up, the autonomous runner) and the session optimizer leave out a task whose needs are not a subset of the viewer's capabilities. The autonomous and parallel runner never holds `owner`, whoever it runs as: an agent cannot make the owner's decision.
|
|
22
|
+
|
|
23
|
+
**4. The claim refuses only `server`.** A builder without the grant gets `NEEDS_SERVER_ACCESS` (403) with a plain sentence, on all three claim surfaces (`claimTask`, `claimTasksBatch`, `validateClaimBatch`, through the one extracted check `task-needs-gate.js`). Owner work stays claimable: the owner claims it, often through their own agent session, and refusing it would refuse them.
|
|
24
|
+
|
|
25
|
+
**5. Visibility is unchanged.** `GET /tasks`, the board, goal pages, search and the AI's context still show every task wherever project settings allow. The board gains a *Needs* filter, and `?needs=owner` on the claimable feed is the owner's "waiting on you" queue.
|
|
26
|
+
|
|
27
|
+
## Consequences
|
|
28
|
+
|
|
29
|
+
- A builder without server access runs `/builder-start` and sees no server task. The owner sees owner tasks in their own list.
|
|
30
|
+
- Tagging is a person's call. The create routes (`POST /tasks`, `POST /goals/:id/tasks`) and `PATCH /tasks/:id` accept `needs`, and `/goal-create` tells the planner when to set it. Existing tasks are tagged by hand once this is live: [task 1005001](https://cloudbongos.com/builders#/task/1005001) carries the 2026-10-29 first-pass list; the migration tags nothing, because task ids differ between projects.
|
|
31
|
+
- `claimable_tasks` was rebuilt in the same migration so its `t.*` carries the column.
|
|
32
|
+
|
|
33
|
+
## Not decided here
|
|
34
|
+
|
|
35
|
+
- A third need for a signed-in browser. The 2026-10-29 list had such tasks; they are tagged `owner` for now (the owner's browser), and a separate need can be added later by extending the list and the CHECK together.
|
|
36
|
+
- A settings screen for the grant. The route exists; a control in the hall can follow.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 0396 — Google sign-in: a stand-in login, one chooser, and a CLI sign-in approved in the browser
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-10-29
|
|
5
|
+
- **Task:** [task 1004993](https://cloudbongos.com/builders#/task/1004993) (goal 1000095, work area 6)
|
|
6
|
+
- **Deciders:** the owner (Google is the only new way in: no email codes, no passwords; never join accounts on a matching email; a stand-in name the person chooses); Claude designed
|
|
7
|
+
- **Related:** **0393 (an account is a person; GitHub is one way in)**, 0016 (trust boundary), 0050 (one admission gate for web and CLI), 0141 (the hub is an identity provider), 0217 D3 (the hub sign-in records the terms), 0294 (CLI tokens do not expire)
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
ADR 0393 made a platform account a person with ways in, and minted negative person keys for people who arrive without GitHub. Nothing yet let anyone arrive that way. The owner asked for "Continue with Google" beside "Continue with GitHub" on every sign-in and sign-up screen of the hub, with the command-line sign-in working for a Google account too.
|
|
12
|
+
|
|
13
|
+
Three things stood in the way:
|
|
14
|
+
|
|
15
|
+
1. **Core's `builders.github_login` is `NOT NULL`.** Every hub session belongs to a builder row, and a Google-only person has no GitHub login. Making the column nullable is a core migration that every screen reading a login would have to follow.
|
|
16
|
+
2. **About twenty sign-in links** on the hub's pages, plus the hall's, each go straight to GitHub.
|
|
17
|
+
3. **The CLI's sign-in is GitHub's own device flow**: the terminal shows a code to type at github.com, which a person with no GitHub account cannot do.
|
|
18
|
+
|
|
19
|
+
## Decision
|
|
20
|
+
|
|
21
|
+
1. **A stand-in login for the builder row, never a GitHub-shaped one** (the owner's pick, 2026-10-29: a stand-in, and the person decides it). A person with no GitHub linked gets `~<handle>` as `builders.github_login`, where the handle is the one they choose on their account page; a first Google sign-in lands on `/account?welcome=google` to choose it. `~` never appears in a GitHub login, so no admission rule, first-admin seat, invite or GitHub API call keyed on a login can ever reach a real GitHub user through it. The stand-in is refreshed at each sign-in. Once GitHub is linked (task 1004995), the real login is used instead: the hub answers it with the person.
|
|
22
|
+
2. **The stand-in never reaches the hub's own record.** `upsertAccount` ignores any login a caller passes for a minted (negative) key, on insert and on update, so `platform_identity_accounts.github_login` stays NULL until linking sets it (ADR 0393 §4). This also covers the hub routes that pass `req.builder.github_login` along.
|
|
23
|
+
3. **The person is found by Google's own id, through the hub.** Core asks the `platform-identity.hub` port's `signInWithGoogle`, which finds the person by `(google, sub)` or creates them (`createAccountForLogin`). Emails are stored for display and never looked up. So Google sign-in exists only where the hub module runs and the owner's Google app is configured (`<PREFIX>_GOOGLE_CLIENT_ID` / `_SECRET`); every other instance is unchanged.
|
|
24
|
+
4. **The ID token is checked, not trusted.** It is fetched from Google's token endpoint over TLS with our client secret, so its signature is not re-verified (OpenID Connect Core §REDACTED_IP). Its issuer, audience (and `azp` with several audiences), expiry and a per-sign-in `nonce` held in a host-only cookie beside the `state` are checked.
|
|
25
|
+
5. **One chooser instead of twenty buttons.** When Google is offered, `/auth/web/start` shows a small page with both buttons rather than jumping to GitHub, so every existing link, the hall's included, gains Google at once. `via=github` is the GitHub button, and it is also used by doors that need GitHub itself: creating a project, and "New account", which names a GitHub account.
|
|
26
|
+
6. **The CLI is approved in the browser.** When Google is offered, `/auth/device/start` answers in GitHub's device-flow shape, but the `verification_uri` is this site's own `/auth/device?code=…`. There the person signs in with either way, checks the code against their terminal, and presses Approve. That posts from their browser cookie only (a bearer cannot mint a bearer, as for `/auth/cli-token/issue`), same-site only, and mints a CLI session for whoever approved. The terminal's next poll receives it exactly once. The CLI needs no change. GitHub users on the hub use this page too. It is one click when they are already signed in.
|
|
27
|
+
7. **Admission is the GitHub web flow's gate.** A first-time Google person is admitted only under open enrollment (they have no GitHub login an Archon could have invited), an existing one always passes, and a switched-off builder gets no session. The hub runs open enrollment.
|
|
28
|
+
8. **Projects cannot take a Google-only person yet.** ADR 0393 says an older core must not be sent a minted key. `/sso/authorize` refuses one with a plain page, and "join a project" refuses before relaying. Task 1004994 opens both doors.
|
|
29
|
+
|
|
30
|
+
## Consequences
|
|
31
|
+
|
|
32
|
+
- A Google-only person can sign up, sign in on the hub's web pages and its hall, use the CLI, and edit their account. They cannot join another project's hall until task 1004994.
|
|
33
|
+
- The stand-in shows wherever core shows a login (`~ana` in the hall). Task 1004994, which teaches halls about Google-only builders, should prefer the handle when showing one. A handle renamed on the account page reaches the builder row at the next sign-in.
|
|
34
|
+
- The hub's CLI sign-in now runs through the browser approval page for everyone, GitHub users included. It shares GitHub's device-code phishing risk, and the page says the same thing GitHub's does: approve only a sign-in you just started, and cancel a link someone sent you.
|
|
35
|
+
- Pending CLI approvals live in memory, like GitHub's device flows here. A restart drops one, and the person runs the sign-in again.
|
|
36
|
+
- The terms page still says people sign in with GitHub. Changing it is the owner's call because it changes the agreed text and its version.
|
|
37
|
+
- **Still owed before linking ships (task 1004995, the ADR 0393 carry-over):** every GitHub sign-in must find the person through `findPersonByLogin` first.
|
|
38
|
+
|
|
39
|
+
## What this does not do
|
|
40
|
+
|
|
41
|
+
It adds no other provider, no email codes and no passwords. It does not link accounts, show a Link GitHub chip, or guide anyone to GitHub (tasks 1004995–1004997).
|
package/docs/adr/README.md
CHANGED
|
@@ -147,7 +147,7 @@ These 20 numbers are each shared by exactly two files. They are **accepted histo
|
|
|
147
147
|
| 0051 | [Full session transcripts in the BFG corpus (with transcript-grade scrubbing)](0051-full-session-transcript-corpus.md) | GDS / BFG |
|
|
148
148
|
| 0052 | [Sandbox for everyone: a game-only preview entrypoint + the game in the starter clone](0052-sandbox-for-everyone-game-only-preview.md) | GDS / dev boxes |
|
|
149
149
|
| 0053 | [Scoped, short-lived dev-box session + apex-cookie strip at the tunnel (SR#2/#11)](0053-scoped-dev-box-session.md) | GDS / dev boxes + security |
|
|
150
|
-
| 0054 | [Flat cost-plus session token reward (example as equity): reward = round(true_cost_usd × 1.20 × 100), server-priced from per-model tokens, idempotent per session_id](0054-session-token-reward-cost-plus.md) | GDS / economy |
|
|
150
|
+
| 0054 | [Flat cost-plus session token reward (example as equity): reward = round(true_cost_usd × 1.20 × 100), server-priced from per-model tokens, idempotent per session_id](0054-session-token-reward-cost-plus.md) _(credits-as-equity framing and "1 drachma = $1" superseded by ADR 0385 / ADR 0387)_ | GDS / economy |
|
|
151
151
|
| 0055 | [Server-mediated branch publish (the dev box holds no GitHub credential): box uploads a thin bundle, the GDS pushes + opens the PR + auto-merges with a server-side PAT](0055-server-mediated-branch-publish.md) | GDS / dev boxes + ship pipeline |
|
|
152
152
|
| 0056 | [Version-control the prod deploy script as a reviewable mirror (`scripts/deploy/deploy-prod.sh`), and retry the post-restart `/healthz` so a slow service bind no longer false-fails a successful deploy](0056-prod-deploy-script-mirror-and-healthcheck-retry.md) | GDS / deploy pipeline |
|
|
153
153
|
| 0057-a | [Approve new members and first dev boxes from Discord: two Archon-only channels where a 👍/👎 fires the existing admit (`access_requests`) / first-box (`box_intents`) approve action, re-checked against the reactor's live rank](0057-discord-archon-approval-channels.md) _(box half superseded by ADR 0059)_ | GDS / Discord + onboarding |
|
|
@@ -508,3 +508,7 @@ These 20 numbers are each shared by exactly two files. They are **accepted histo
|
|
|
508
508
|
| 0387 | [**Two ledgers: credits measure work, money stays with the processor** ([task 1003180](https://cloudbongos.com/builders#/task/1003180) · goal 1000092). Resolves ADR 0054's double denomination by splitting, not by re-denominating credits into cents: `credit_log` stays a work meter with no money column, money gets its own cents record tied to the processor, no dollar value is read off a credit, and historic rows are neither rewritten nor classified.](0387-two-ledgers-credits-measure-work-money-stays-with-the-processor.md) | economy |
|
|
509
509
|
| 0388 | [**A module sale is split by the project's own rule, and the buyer pays the platform fee on top** ([task 1004909](https://cloudbongos.com/builders#/task/1004909) · goal 1000092). The platform fee is the processor's cost plus 1 percent of the price, collected for Cloud Bongos LLC and never deducted from contributors; the project sets the seller share and contributor pool, the pool is divided by recorded contribution, and no paid module lists without a split rule.](0388-a-module-sale-is-split-by-the-project-the-buyer-pays-the-platform-fee.md) | economy / marketplace |
|
|
510
510
|
| 0392 | [**AI generation runs on a processor-held balance or the builder's own key** ([task 1004916](https://cloudbongos.com/builders#/task/1004916)). A new `ai-studio` module: price = provider cost x one named multiplier (1.08), the processor's fee added at top-up, the full price held on the processor-held balance before any run and released on failure (Bongos keeps no balance, ADR 0385), every movement in one append-only ledger in micro-dollars; the builder's own key is entered only in Settings, encrypted, never returned or logged, and runs at no charge.](0392-ai-generation-runs-on-a-processor-held-balance-and-the-builders-own-keys.md) |
|
|
511
|
+
| 0393 | [**A platform account is a person, and GitHub is one way in** ([task 1004992](https://cloudbongos.com/builders#/task/1004992)). An account's ways in (GitHub, Google) are rows the database keeps one owner for; `github_id` keeps its name and becomes the person key, positive for a GitHub-born person and minted negative for one who arrived without GitHub, so no wire format changes; `github_login` is NULL with no GitHub linked; an email is shown, never looked up.](0393-a-platform-account-is-a-person-and-github-is-one-way-in.md) |
|
|
512
|
+
| 0396 | [**Google sign-in: a stand-in login, one chooser, and a CLI sign-in approved in the browser** ([task 1004993](https://cloudbongos.com/builders#/task/1004993)). A Google-only person gets `~<handle>` as the core builder login, a value no GitHub login can be, and never on the hub record; they are found by Google's own id through the hub, never an email. Every sign-in link shows Continue with GitHub / Continue with Google; the CLI sign-in is approved on this site's own page from the browser cookie. Projects cannot take a Google-only person until task 1004994.](0396-google-sign-in-a-stand-in-login-and-a-browser-approved-cli-sign-in.md) | identity / sign-in |
|
|
513
|
+
| 0394 | [**Market Watch searches the web through the Messages API on the project's own key, and runs from its own clock-anchored poller** ([task 1004934](https://cloudbongos.com/builders#/task/1004934) · goal 1000137). Not the grade port: the server image has no `claude` CLI, and a measured tool-enabled run costs the same ($0.106 per item). One Sonnet 5 call per item with bounded web search and fetch, on an owner-set key in Settings (the project pays the provider; Bongos holds no money); an hourly, ledger-due, resumable poller on the daily-checks pattern, not the abandoned cron-router; capped per item before it starts and fail closed; about $4–5 per weekly 40-item run.](0394-market-watch-searches-through-the-messages-api-on-the-projects-key-and-runs-from-its-own-poller.md) | modules / economy |
|
|
514
|
+
| 0395 | [**A task says what it needs, and each person's lists show only what they can do** ([task 1004950](https://cloudbongos.com/builders#/task/1004950)). `tasks.needs` ⊆ {owner, server} is its own set, not an `automation_tag` value (that one says whether work runs unattended). Capabilities are read fresh like rank: `owner` from the owner seat (founding builder fallback, the UAT reading), `server` from `builders.capability_grants`, set only by the owner. The recommend lists (claimable feed, optimizer, autonomous runner) show only tasks whose needs the viewer holds; the runner never holds owner. The claim refuses only `server` (`NEEDS_SERVER_ACCESS`, all three surfaces). Visibility elsewhere is unchanged; the board filters by need.](0395-a-task-says-what-it-needs-and-lists-show-only-what-you-can-do.md) | lifecycle / work tracking |
|