@bongos/core 1.23.21 → 1.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +983 -428
- 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 +6 -3
- 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 +94 -0
- package/clients/bongos-client/index.cjs +94 -0
- package/clients/bongos-client/index.d.ts +154 -4
- package/clients/bongos-client/index.mjs +94 -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/0385-money-lives-with-a-processor-credits-stay-a-project-meter.md +87 -0
- package/docs/adr/0386-the-funding-wall-is-a-per-project-rule-claim-time-hold-by-default.md +37 -0
- package/docs/adr/0387-two-ledgers-credits-measure-work-money-stays-with-the-processor.md +39 -0
- package/docs/adr/0388-a-module-sale-is-split-by-the-project-the-buyer-pays-the-platform-fee.md +42 -0
- package/docs/adr/0392-ai-generation-runs-on-a-processor-held-balance-and-the-builders-own-keys.md +33 -0
- 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 +10 -1
- package/docs/api/openapi.json +8619 -5885
- package/docs/api-reference.md +68 -9
- 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 +4 -0
- package/docs/module-api-changelog.md +7 -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/CLAUDE.md +15 -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/module.json +26 -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 +252 -0
- 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 +108 -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 +50 -18
- package/modules/government/routing.js +39 -5
- package/modules/government/waiting.js +74 -0
- package/modules/grading/grader-subagent.js +30 -11
- 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/city.html +15 -15
- 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-analytics.js +10 -3
- package/modules/lifecycle/db-claims.js +32 -4
- package/modules/lifecycle/db-goals.js +48 -18
- package/modules/lifecycle/db-shared.js +9 -0
- package/modules/lifecycle/db-tasks.js +15 -6
- package/modules/lifecycle/db-versions.js +21 -8
- 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/ship-assurance.js +4 -1
- package/modules/lifecycle/ship-card.js +8 -2
- 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/onboarding/howto-moments.js +8 -1
- 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 +8 -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 +286 -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 +4 -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 +129 -12
- 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 +16 -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/src/platform-server.js +27 -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/batched_writes_parity.mjs +340 -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/conductor_main_e2e.mjs +4 -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/per-row-loops-legacy.cjs +245 -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_archive_disposition.mjs +5 -5
- 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 +146 -23
- package/tests/grader.mjs +31 -0
- package/tests/hall_ai_studio.mjs +347 -0
- package/tests/hall_city.mjs +8 -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/hook_shell_tools.mjs +8 -0
- package/tests/hooks_drain.mjs +1 -1
- package/tests/host_topology_skips.mjs +2 -0
- package/tests/howto_moments.mjs +17 -0
- 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/module_skills_hook.mjs +69 -0
- 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/public_landing_projects.mjs +37 -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/session_start_freshness.mjs +66 -1
- package/tests/shell_hooks.mjs +209 -0
- package/tests/ship_assurance.mjs +28 -0
- package/tests/ship_card.mjs +20 -5
- 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 +58 -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_close_route.mjs +1 -1
- 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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/bongos/device-approval.js — command-line sign-in approved in the browser
|
|
2
|
+
// (task 1004993, ADR 0396).
|
|
3
|
+
//
|
|
4
|
+
// WHY. The CLI's sign-in has always been GitHub's own device flow: the terminal
|
|
5
|
+
// shows a code and the person types it at github.com. A person who signs in with
|
|
6
|
+
// Google has no GitHub account to type it into. So when this instance offers
|
|
7
|
+
// Google sign-in, the CLI's device flow is served HERE instead: the terminal gets
|
|
8
|
+
// a code and a link to this instance's own approval page, the person signs in
|
|
9
|
+
// there with GitHub or Google as they would in the browser, checks the code and
|
|
10
|
+
// presses Approve, and the terminal's next poll receives a CLI session.
|
|
11
|
+
//
|
|
12
|
+
// The CLI needs no change for this: /auth/device/start and /auth/device/poll keep
|
|
13
|
+
// their request and response shapes, and the CLI already opens whatever
|
|
14
|
+
// `verification_uri` it is handed.
|
|
15
|
+
//
|
|
16
|
+
// WHAT HOLDS IT SAFE. Approving is a cookie-session POST (the same rule as
|
|
17
|
+
// /auth/cli-token/issue: a bearer cannot mint a bearer), it binds the CLI session
|
|
18
|
+
// to whoever approved, and the page tells the person to check the code matches
|
|
19
|
+
// their terminal and to cancel a link someone sent them — the device-code
|
|
20
|
+
// phishing risk GitHub's own page carries, stated the same way. A code is
|
|
21
|
+
// single-use, lives FLOW_TTL_MS and is forgotten once polled to completion.
|
|
22
|
+
//
|
|
23
|
+
// In-memory, like GitHub's device flows in auth-config.js: a restart drops a
|
|
24
|
+
// pending sign-in and the person runs it again.
|
|
25
|
+
'use strict';
|
|
26
|
+
|
|
27
|
+
const crypto = require('node:crypto');
|
|
28
|
+
|
|
29
|
+
const FLOW_TTL_MS = 10 * 60 * 1000;
|
|
30
|
+
const POLL_INTERVAL_S = 5;
|
|
31
|
+
const FLOWS_MAX = 10000;
|
|
32
|
+
// No vowels, so a code never spells a word; no look-alike letters.
|
|
33
|
+
const USER_CODE_ALPHABET = 'BCDFGHJKLMNPQRSTVWXZ';
|
|
34
|
+
|
|
35
|
+
const flows = new Map(); // device_code → flow
|
|
36
|
+
const byUserCode = new Map(); // user_code → device_code
|
|
37
|
+
|
|
38
|
+
function prune(now) {
|
|
39
|
+
for (const [deviceCode, f] of flows) {
|
|
40
|
+
if (f.expiresAt <= now) forget(deviceCode);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function forget(deviceCode) {
|
|
45
|
+
const f = flows.get(deviceCode);
|
|
46
|
+
if (f) byUserCode.delete(f.userCode);
|
|
47
|
+
flows.delete(deviceCode);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function newUserCode() {
|
|
51
|
+
let out = '';
|
|
52
|
+
for (let i = 0; i < 8; i++) out += USER_CODE_ALPHABET[crypto.randomInt(USER_CODE_ALPHABET.length)];
|
|
53
|
+
return `${out.slice(0, 4)}-${out.slice(4)}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// A code as a person might type it (lower case, spaces, no dash) → canonical
|
|
57
|
+
// XXXX-XXXX, or null when it cannot be one.
|
|
58
|
+
function normalizeUserCode(raw) {
|
|
59
|
+
const letters = String(raw || '').toUpperCase().replace(/[^A-Z]/g, '');
|
|
60
|
+
if (letters.length !== 8 || [...letters].some((c) => !USER_CODE_ALPHABET.includes(c))) return null;
|
|
61
|
+
return `${letters.slice(0, 4)}-${letters.slice(4)}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Start a sign-in for the terminal → the same shape GitHub's device/code answers,
|
|
65
|
+
// so the CLI reads it unchanged. `pageUrl` is this instance's approval page.
|
|
66
|
+
function startBrowserDeviceFlow({ pageUrl, now = Date.now() }) {
|
|
67
|
+
prune(now);
|
|
68
|
+
while (flows.size >= FLOWS_MAX) forget(flows.keys().next().value);
|
|
69
|
+
const deviceCode = crypto.randomBytes(32).toString('hex');
|
|
70
|
+
let userCode = newUserCode();
|
|
71
|
+
while (byUserCode.has(userCode)) userCode = newUserCode();
|
|
72
|
+
flows.set(deviceCode, { userCode, expiresAt: now + FLOW_TTL_MS, lastPolledAt: 0, result: null });
|
|
73
|
+
byUserCode.set(userCode, deviceCode);
|
|
74
|
+
const complete = `${pageUrl}?code=${encodeURIComponent(userCode)}`;
|
|
75
|
+
return {
|
|
76
|
+
device_code: deviceCode,
|
|
77
|
+
user_code: userCode,
|
|
78
|
+
// The CLI opens verification_uri, so it carries the code: the page opens
|
|
79
|
+
// with it filled in. verification_uri_complete is the standard name for it.
|
|
80
|
+
verification_uri: complete,
|
|
81
|
+
verification_uri_complete: complete,
|
|
82
|
+
expires_in: Math.round(FLOW_TTL_MS / 1000),
|
|
83
|
+
interval: POLL_INTERVAL_S,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The pending flow a typed code names, or null (unknown, expired, already decided).
|
|
88
|
+
function pendingFlow(userCode, now = Date.now()) {
|
|
89
|
+
const code = normalizeUserCode(userCode);
|
|
90
|
+
const deviceCode = code && byUserCode.get(code);
|
|
91
|
+
const f = deviceCode && flows.get(deviceCode);
|
|
92
|
+
if (!f || f.expiresAt <= now || f.result) return null;
|
|
93
|
+
return { deviceCode, userCode: code };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// The signed-in person approved (or cancelled) the code. On approve, `mint` is
|
|
97
|
+
// called once to create the CLI session for THIS person and its result is held
|
|
98
|
+
// for the terminal's next poll. → true when a pending code was decided.
|
|
99
|
+
async function decideDeviceFlow({ userCode, approve, mint, now = Date.now() }) {
|
|
100
|
+
const p = pendingFlow(userCode, now);
|
|
101
|
+
if (!p) return false;
|
|
102
|
+
const f = flows.get(p.deviceCode);
|
|
103
|
+
// Decide before the await, so a double-submit cannot mint twice.
|
|
104
|
+
f.result = { state: 'deciding' };
|
|
105
|
+
try {
|
|
106
|
+
f.result = approve ? { state: 'ok', ...(await mint()) } : { state: 'denied' };
|
|
107
|
+
} catch (err) {
|
|
108
|
+
f.result = null; // undecided again: the person can retry
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Is this a device code this file issued (rather than GitHub's)?
|
|
115
|
+
function isBrowserDeviceCode(deviceCode) {
|
|
116
|
+
return typeof deviceCode === 'string' && flows.has(deviceCode);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// The terminal's poll → { state: 'pending' | 'slow_down' | 'ok' | 'denied' | 'expired', … }.
|
|
120
|
+
// The same states pollDeviceFlow answers for GitHub's flow.
|
|
121
|
+
function pollBrowserDeviceFlow(deviceCode, now = Date.now()) {
|
|
122
|
+
const f = flows.get(deviceCode);
|
|
123
|
+
if (!f) return { state: 'expired' };
|
|
124
|
+
if (f.expiresAt <= now) { forget(deviceCode); return { state: 'expired' }; }
|
|
125
|
+
if (now - f.lastPolledAt < POLL_INTERVAL_S * 1000) return { state: 'slow_down', interval: POLL_INTERVAL_S };
|
|
126
|
+
f.lastPolledAt = now;
|
|
127
|
+
if (!f.result || f.result.state === 'deciding') return { state: 'pending' };
|
|
128
|
+
forget(deviceCode); // the token leaves exactly once
|
|
129
|
+
return f.result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
FLOW_TTL_MS,
|
|
134
|
+
normalizeUserCode,
|
|
135
|
+
startBrowserDeviceFlow,
|
|
136
|
+
pendingFlow,
|
|
137
|
+
decideDeviceFlow,
|
|
138
|
+
isBrowserDeviceCode,
|
|
139
|
+
pollBrowserDeviceFlow,
|
|
140
|
+
};
|
|
@@ -283,4 +283,20 @@ function componentsFromModelUsage(modelUsage) {
|
|
|
283
283
|
};
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
|
|
286
|
+
// SERVER-TOOL FEES (task 1004937, ADR 0394 §3). The Messages API's web search is
|
|
287
|
+
// billed per search on top of tokens: $10 per 1,000 searches, i.e. $0.01 each.
|
|
288
|
+
// Web fetch has no fee of its own; the fetched page is billed as input tokens,
|
|
289
|
+
// which priceUsage already prices. This is the one place the search price is
|
|
290
|
+
// spelled. Source: Anthropic's published web-search price as cached in the
|
|
291
|
+
// claude-api reference (2026-10-06), NOT re-verified against the live pricing
|
|
292
|
+
// page; the first real Market Watch run (task 1004942) is the measurement.
|
|
293
|
+
const WEB_SEARCH_USD_PER_REQUEST = 0.01;
|
|
294
|
+
|
|
295
|
+
// priceWebSearches(n) — USD for n web searches (usage.server_tool_use.web_search_requests).
|
|
296
|
+
// A missing or garbage count prices as zero searches, never NaN.
|
|
297
|
+
function priceWebSearches(n) {
|
|
298
|
+
const k = Number(n);
|
|
299
|
+
return Number.isFinite(k) && k > 0 ? k * WEB_SEARCH_USD_PER_REQUEST : 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
module.exports = { PRICES, FAMILY_CURRENT_MODEL, familyFor, priceUsage, canonicalizeModelId, priceModelUsage, componentsFromModelUsage, WEB_SEARCH_USD_PER_REQUEST, priceWebSearches };
|
package/src/bongos/metrics.js
CHANGED
|
@@ -51,9 +51,28 @@ function renderLabels(labels) {
|
|
|
51
51
|
return '{' + keys.map((k) => `${k}="${escapeLabelValue(labels[k])}"`).join(',') + '}';
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// --- series cap (task 1004924) ---------------------------------------------
|
|
55
|
+
// Every distinct label set is a series held in memory forever, so a label fed by
|
|
56
|
+
// request input can grow the heap without bound. Past this many series a metric
|
|
57
|
+
// takes no NEW label set: it folds into an overflow series instead. A metric with
|
|
58
|
+
// a route label folds only the route to 'other' (method and status are bounded
|
|
59
|
+
// sets already, so the overflow is a handful of series); any other metric folds
|
|
60
|
+
// every label to 'other', one series.
|
|
61
|
+
const MAX_SERIES_PER_METRIC = 2000;
|
|
62
|
+
const OVERFLOW = 'other';
|
|
63
|
+
|
|
64
|
+
function capLabels(series, labels) {
|
|
65
|
+
if (series.size < MAX_SERIES_PER_METRIC || series.has(labelKey(labels))) return labels;
|
|
66
|
+
if ('route' in labels) return { ...labels, route: OVERFLOW };
|
|
67
|
+
const all = {};
|
|
68
|
+
for (const k of Object.keys(labels)) all[k] = OVERFLOW;
|
|
69
|
+
return all;
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
function incCounter(name, help, labels = {}, by = 1) {
|
|
55
73
|
let c = counters.get(name);
|
|
56
74
|
if (!c) { c = { help, series: new Map() }; counters.set(name, c); }
|
|
75
|
+
labels = capLabels(c.series, labels);
|
|
57
76
|
const key = labelKey(labels);
|
|
58
77
|
const s = c.series.get(key);
|
|
59
78
|
if (s) s.value += by;
|
|
@@ -63,6 +82,7 @@ function incCounter(name, help, labels = {}, by = 1) {
|
|
|
63
82
|
function observeHistogram(name, help, labels, value) {
|
|
64
83
|
let h = histograms.get(name);
|
|
65
84
|
if (!h) { h = { help, series: new Map() }; histograms.set(name, h); }
|
|
85
|
+
labels = capLabels(h.series, labels);
|
|
66
86
|
const key = labelKey(labels);
|
|
67
87
|
let s = h.series.get(key);
|
|
68
88
|
if (!s) { s = { labels, buckets: new Array(HIST_BUCKETS.length).fill(0), sum: 0, count: 0 }; h.series.set(key, s); }
|
|
@@ -74,8 +94,14 @@ function observeHistogram(name, help, labels, value) {
|
|
|
74
94
|
}
|
|
75
95
|
|
|
76
96
|
// --- event-loop lag sampler (unref'd so it never holds the process open) ---
|
|
97
|
+
// Sampled every 10 s, not every second (task 1004961): a 1 s timer woke an idle
|
|
98
|
+
// hosted instance once a second forever, which burns CPU credits on a small
|
|
99
|
+
// droplet. perf_hooks.monitorEventLoopDelay() was considered and rejected — its
|
|
100
|
+
// native sampler is itself a repeating libuv timer firing every `resolution` ms
|
|
101
|
+
// (default 10 ms), so it would wake the loop far MORE often, not less. The gauge
|
|
102
|
+
// keeps its name and meaning: the most recent sampled lag, in seconds.
|
|
77
103
|
let _lagSeconds = 0;
|
|
78
|
-
const _LAG_INTERVAL_MS =
|
|
104
|
+
const _LAG_INTERVAL_MS = 10_000;
|
|
79
105
|
const _lagTimer = setInterval(() => {
|
|
80
106
|
const start = process.hrtime.bigint();
|
|
81
107
|
setImmediate(() => {
|
|
@@ -100,6 +126,26 @@ function normalizeRoute(p) {
|
|
|
100
126
|
return '/' + parts.join('/');
|
|
101
127
|
}
|
|
102
128
|
|
|
129
|
+
// routeLabel — the route label for one finished request (task 1004924). The
|
|
130
|
+
// literal path is caller-chosen (every scanner hit on /wp-admin/x.php or /.env
|
|
131
|
+
// is a new path), so it is never the label on its own:
|
|
132
|
+
// - a matched Express route labels by its PATTERN (mount path + route path),
|
|
133
|
+
// a fixed set only the code can grow;
|
|
134
|
+
// - a 404 with no matched route is the single label 'unmatched';
|
|
135
|
+
// - anything else unrouted (static files) keeps the normalized path, and the
|
|
136
|
+
// per-metric series cap above bounds even that.
|
|
137
|
+
const UNMATCHED = 'unmatched';
|
|
138
|
+
function routeLabel(req, status) {
|
|
139
|
+
const r = req.route;
|
|
140
|
+
if (r && r.path != null) {
|
|
141
|
+
const base = req.baseUrl && req.baseUrl !== '/' ? normalizeRoute(req.baseUrl) : '';
|
|
142
|
+
const p = typeof r.path === 'string' ? r.path : String(r.path);
|
|
143
|
+
return (base + p) || '/';
|
|
144
|
+
}
|
|
145
|
+
if (status === 404) return UNMATCHED;
|
|
146
|
+
return normalizeRoute(req.path);
|
|
147
|
+
}
|
|
148
|
+
|
|
103
149
|
// --- HTTP middleware ------------------------------------------------------
|
|
104
150
|
function httpMiddleware() {
|
|
105
151
|
return (req, res, next) => {
|
|
@@ -107,9 +153,9 @@ function httpMiddleware() {
|
|
|
107
153
|
if (req.path === '/metrics' || req.path === '/healthz') return next();
|
|
108
154
|
const start = process.hrtime.bigint();
|
|
109
155
|
res.on('finish', () => {
|
|
110
|
-
const route = normalizeRoute(req.path);
|
|
111
156
|
const method = req.method;
|
|
112
157
|
const status = res.statusCode;
|
|
158
|
+
const route = routeLabel(req, status);
|
|
113
159
|
incCounter('gds_http_requests_total', 'Total HTTP requests by method, normalized route, and status code.',
|
|
114
160
|
{ method, route, status: String(status) });
|
|
115
161
|
const dur = Number(process.hrtime.bigint() - start) / 1e9;
|
|
@@ -118,6 +118,12 @@ const DEFAULT_READ_LIMIT = 600;
|
|
|
118
118
|
// address-rotating caller must not be able to grow the map without bound between sweeps
|
|
119
119
|
// (same bound as modules/platform-identity/read-rate-limit.js).
|
|
120
120
|
const DEFAULT_READ_MAX_TRACKED_IPS = 10000;
|
|
121
|
+
// Hard size cap on EVERY bucket map below (task 1004964). The sweep bounds them by
|
|
122
|
+
// time only, once per SWEEP_MS; a flood from many source addresses would otherwise
|
|
123
|
+
// grow a map for up to five minutes before it runs. At the cap the oldest-inserted
|
|
124
|
+
// key is evicted first (a Map iterates in insertion order), so the newest callers
|
|
125
|
+
// keep their buckets. A limiter may set a tighter cap (DEFAULT_READ_MAX_TRACKED_IPS).
|
|
126
|
+
const MAX_TRACKED_KEYS = 50000;
|
|
121
127
|
const OPT_OUT_REASON_MIN_LENGTH = 20;
|
|
122
128
|
const SWEEP_MS = 5 * 60 * 1000;
|
|
123
129
|
|
|
@@ -138,6 +144,15 @@ const WRITE_METHODS = new Set(['POST', 'PATCH', 'PUT', 'DELETE']);
|
|
|
138
144
|
// limiter — the public router answers CORS preflights and returns 204 first.
|
|
139
145
|
const READ_METHODS = new Set(['GET', 'HEAD']);
|
|
140
146
|
|
|
147
|
+
// newBucket(map, key, cap) — start an empty bucket for a key not yet tracked,
|
|
148
|
+
// evicting the oldest-inserted key first while the map is at its cap.
|
|
149
|
+
function newBucket(map, key, cap = MAX_TRACKED_KEYS) {
|
|
150
|
+
while (map.size >= cap) map.delete(map.keys().next().value);
|
|
151
|
+
const bucket = [];
|
|
152
|
+
map.set(key, bucket);
|
|
153
|
+
return bucket;
|
|
154
|
+
}
|
|
155
|
+
|
|
141
156
|
function pruneAndCount(bucket, now, windowMs) {
|
|
142
157
|
const cutoff = now - windowMs;
|
|
143
158
|
// arr is the in-place timestamps array; trim in place.
|
|
@@ -219,7 +234,7 @@ function rateLimitMiddleware(req, res, next) {
|
|
|
219
234
|
// --- per-IP ---
|
|
220
235
|
const ip = req.ip || req.socket?.remoteAddress || 'unknown';
|
|
221
236
|
let ipBucket = ipBuckets.get(ip);
|
|
222
|
-
if (!ipBucket)
|
|
237
|
+
if (!ipBucket) ipBucket = newBucket(ipBuckets, ip);
|
|
223
238
|
const ipCount = pruneAndCount(ipBucket, now, PER_IP_WINDOW_MS);
|
|
224
239
|
if (ipCount >= PER_IP_LIMIT) {
|
|
225
240
|
const oldest = ipBucket[0];
|
|
@@ -235,7 +250,7 @@ function rateLimitMiddleware(req, res, next) {
|
|
|
235
250
|
const sessionKey = extractSessionKey(req);
|
|
236
251
|
if (sessionKey) {
|
|
237
252
|
let sBucket = sessionBuckets.get(sessionKey);
|
|
238
|
-
if (!sBucket)
|
|
253
|
+
if (!sBucket) sBucket = newBucket(sessionBuckets, sessionKey);
|
|
239
254
|
const sCount = pruneAndCount(sBucket, now, PER_SESSION_WINDOW_MS);
|
|
240
255
|
if (sCount >= PER_SESSION_LIMIT) {
|
|
241
256
|
const oldest = sBucket[0];
|
|
@@ -266,7 +281,7 @@ function publicReadRateLimitMiddleware(req, res, next) {
|
|
|
266
281
|
const now = Date.now();
|
|
267
282
|
const ip = req.ip || req.socket?.remoteAddress || 'unknown';
|
|
268
283
|
let bucket = publicReadBuckets.get(ip);
|
|
269
|
-
if (!bucket)
|
|
284
|
+
if (!bucket) bucket = newBucket(publicReadBuckets, ip);
|
|
270
285
|
const count = pruneAndCount(bucket, now, PUBLIC_READ_WINDOW_MS);
|
|
271
286
|
if (count >= PUBLIC_READ_LIMIT) {
|
|
272
287
|
const oldest = bucket[0];
|
|
@@ -287,18 +302,14 @@ function publicReadRateLimitMiddleware(req, res, next) {
|
|
|
287
302
|
// names that budget in the 429 body + headers so a caller can tell which ceiling
|
|
288
303
|
// it hit. Keyed on req.ip — the same real-client-IP resolution the write limiter
|
|
289
304
|
// uses (Caddy forwards the client IP, server sets 'trust proxy').
|
|
290
|
-
function perIpSlidingWindow(buckets, { limit, windowMs, scope, maxTracked =
|
|
305
|
+
function perIpSlidingWindow(buckets, { limit, windowMs, scope, maxTracked = MAX_TRACKED_KEYS }) {
|
|
291
306
|
return markMetered(function slidingWindowRateLimit(req, res, next) {
|
|
292
307
|
const now = Date.now();
|
|
293
308
|
const ip = req.ip || req.socket?.remoteAddress || 'unknown';
|
|
294
309
|
let bucket = buckets.get(ip);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (buckets.size >= maxTracked) buckets.delete(buckets.keys().next().value);
|
|
299
|
-
bucket = [];
|
|
300
|
-
buckets.set(ip, bucket);
|
|
301
|
-
}
|
|
310
|
+
// Bound the map: a caller rotating source addresses (an IPv6 /64) mints a fresh
|
|
311
|
+
// bucket per request. Evict the longest-tracked entry (Map iterates oldest first).
|
|
312
|
+
if (!bucket) bucket = newBucket(buckets, ip, maxTracked);
|
|
302
313
|
if (pruneAndCount(bucket, now, windowMs) >= limit) {
|
|
303
314
|
const retryAfter = Math.max(1, Math.ceil((bucket[0] + windowMs - now) / 1000));
|
|
304
315
|
res.set('Retry-After', String(retryAfter));
|
|
@@ -451,6 +462,7 @@ module.exports = {
|
|
|
451
462
|
selfMeteredRoutes,
|
|
452
463
|
DEFAULT_READ_LIMIT,
|
|
453
464
|
DEFAULT_READ_MAX_TRACKED_IPS,
|
|
465
|
+
MAX_TRACKED_KEYS,
|
|
454
466
|
// exported for tests
|
|
455
467
|
PER_IP_WINDOW_MS,
|
|
456
468
|
PER_IP_LIMIT,
|
|
@@ -463,3 +475,10 @@ module.exports = {
|
|
|
463
475
|
PROJECT_FEED_WINDOW_MS,
|
|
464
476
|
PROJECT_FEED_LIMIT,
|
|
465
477
|
};
|
|
478
|
+
|
|
479
|
+
// Test seam (task 1004964): the bucket maps, so a test can show each stays capped.
|
|
480
|
+
// Assigned after the export list, not inside it: an object literal there stops
|
|
481
|
+
// cjs-module-lexer, and ESM tests lose every named export after it.
|
|
482
|
+
module.exports._bucketMaps = {
|
|
483
|
+
ipBuckets, sessionBuckets, publicReadBuckets, accountExistenceBuckets, projectFeedBuckets, defaultReadBuckets,
|
|
484
|
+
};
|
|
@@ -50,7 +50,7 @@ const MODULE_GLOBS = {
|
|
|
50
50
|
'src/bongos/permission-path-check.js', 'src/bongos/sensitive-surfaces.js',
|
|
51
51
|
// task 1004024: the auth.js carve (db-kernel.js, above, is db.js's), now protected.
|
|
52
52
|
'src/bongos/auth-admission.js', 'src/bongos/auth-config.js', 'src/bongos/auth-discord.js',
|
|
53
|
-
'src/bongos/auth-github.js', 'src/bongos/auth-idp.js',
|
|
53
|
+
'src/bongos/auth-github.js', 'src/bongos/auth-google.js', 'src/bongos/auth-idp.js',
|
|
54
54
|
'src/bongos/middleware/', 'src/bongos/live-channel.js', 'src/bongos/secret-box.js',
|
|
55
55
|
'src/bongos/repo-info.js', 'src/bongos/api-path-404.js', 'src/bongos/serve-internal.js',
|
|
56
56
|
'src/bongos/module-scope-map.js', 'src/bongos/hierarchy-config.js',
|
|
@@ -182,10 +182,23 @@ const MODULE_GLOBS = {
|
|
|
182
182
|
'page-notes': [
|
|
183
183
|
'modules/page-notes/',
|
|
184
184
|
],
|
|
185
|
+
// AI generation for artists (task 1004916, ADR 0392): keys, prepaid balance, generate.
|
|
186
|
+
'ai-studio': [
|
|
187
|
+
'modules/ai-studio/',
|
|
188
|
+
],
|
|
185
189
|
// The /raise investor page (task 1004847, ADR 0384): hub-only.
|
|
186
190
|
raise: [
|
|
187
191
|
'modules/raise/',
|
|
188
192
|
],
|
|
193
|
+
// Market Watch (task 1004935, goal 1000137): a weekly AI watch on a project's
|
|
194
|
+
// market. default:false; its data layer landed in task 1004936 (no routes yet). Its tests
|
|
195
|
+
// are listed by EXACT path: this file's format has no wildcards (header above), so a
|
|
196
|
+
// 'tests/market_watch_*' entry would match nothing. Add each new market_watch test here by name.
|
|
197
|
+
'market-watch': [
|
|
198
|
+
'modules/market-watch/', 'tests/market_watch_module.mjs',
|
|
199
|
+
'tests/market_watch_data.mjs', 'tests/market_watch_data_db.mjs',
|
|
200
|
+
'tests/market_watch_run.mjs',
|
|
201
|
+
],
|
|
189
202
|
// --- builder prefs (wandering/skill/sound/render) + builder-needs ---
|
|
190
203
|
// Carved into modules/builder-settings/ (BV1.R80 / ADR 0093 §1): the five pref
|
|
191
204
|
// libs + builder-needs + their db slice + the `/me/*-prefs` routes + the
|
|
@@ -525,6 +538,8 @@ const MODULE_GLOBS = {
|
|
|
525
538
|
// enforcement, not methodology prose. ---
|
|
526
539
|
methodology: [
|
|
527
540
|
'CLAUDE.md',
|
|
541
|
+
// the same instruction core for every other AI model (task 1004922)
|
|
542
|
+
'AGENTS.md', 'GEMINI.md',
|
|
528
543
|
'docs/',
|
|
529
544
|
'.claude/skills/', '.claude/rules/',
|
|
530
545
|
'scripts/bongos/start.js', 'scripts/bongos/discipline-modes.json',
|
package/src/bongos/optimizer.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
// per-task est_minutes <= timeBudgetMin guard already enforces that.
|
|
30
30
|
|
|
31
31
|
const { pool } = require('./pool');
|
|
32
|
+
const { resolveCapabilities, feedNeedsClause } = require('./task-needs');
|
|
32
33
|
// touches[] cleanup [5/8] (task 880): the same prefix-aware overlap matcher the
|
|
33
34
|
// claim guard uses, so the planner's "parallel-safe set" agrees with what
|
|
34
35
|
// claimTask / claimTasksBatch will actually allow.
|
|
@@ -77,6 +78,12 @@ const UNSHIPPED_DEPS_GUARD = `
|
|
|
77
78
|
OR (d.to_kind = 'goal' AND EXISTS (SELECT 1 FROM goals x WHERE x.id = d.to_id AND x.status <> 'achieved')))
|
|
78
79
|
)`;
|
|
79
80
|
|
|
81
|
+
// An artist's page claim is a MARKER, never a file-overlap block (task 1004947).
|
|
82
|
+
// Hand copy of notPageMarkerSql('c', 't2') in modules/lifecycle/page-tweak-artist.js
|
|
83
|
+
// (core cannot require a module, ADR 0083); tests/page_tweak_marker_sql.mjs pins
|
|
84
|
+
// the two together, as claimable_blocker_status.mjs does for the guards above.
|
|
85
|
+
const PAGE_MARKER_GUARD = `NOT (COALESCE(t2.source, '') = 'page-tweak' AND c.worktree_name IS NULL AND c.creator_session_id IS NULL)`;
|
|
86
|
+
|
|
80
87
|
const VALID_MODES = ['active', 'check-in', 'autonomous'];
|
|
81
88
|
const DEFAULT_MAX_PARALLEL = 3;
|
|
82
89
|
|
|
@@ -108,6 +115,11 @@ async function optimizeSession({ builderId, mode, timeBudgetMin, maxParallel })
|
|
|
108
115
|
// whose blocker had been resolved stayed invisible here. A comment asking the
|
|
109
116
|
// next editor to remember is not enforcement: tests/claimable_blocker_status.mjs
|
|
110
117
|
// now asserts this file, db-tasks.js and the migration carry the same predicate.
|
|
118
|
+
// task 1004950 / ADR 0395: recommend only what this builder can do — the same
|
|
119
|
+
// rule as listClaimableTasks. An autonomous plan runs unattended, so it never
|
|
120
|
+
// holds `owner` (an agent cannot make the owner's decision).
|
|
121
|
+
const caps = await resolveCapabilities(builderId);
|
|
122
|
+
const feedCaps = mode === 'autonomous' ? caps.filter((c) => c !== 'owner') : caps;
|
|
111
123
|
const { rows } = await pool.query(
|
|
112
124
|
`SELECT ct.*, sf.fits_session_modes, sf.credits_per_minute
|
|
113
125
|
FROM (
|
|
@@ -120,6 +132,7 @@ ${OPEN_BLOCKER_GUARD}${UNSHIPPED_DEPS_GUARD}
|
|
|
120
132
|
SELECT 1 FROM claims c
|
|
121
133
|
JOIN tasks t2 ON t2.id = c.task_id
|
|
122
134
|
WHERE c.released_at IS NULL
|
|
135
|
+
AND ${PAGE_MARKER_GUARD}
|
|
123
136
|
AND t2.touches && t.touches
|
|
124
137
|
)
|
|
125
138
|
) ct
|
|
@@ -142,6 +155,7 @@ ${OPEN_BLOCKER_GUARD}${UNSHIPPED_DEPS_GUARD}
|
|
|
142
155
|
FROM tasks t
|
|
143
156
|
) sf ON sf.id = ct.id
|
|
144
157
|
WHERE $1 = ANY(sf.fits_session_modes)
|
|
158
|
+
AND ${feedNeedsClause('ct', '$3')}
|
|
145
159
|
AND NOT EXISTS (
|
|
146
160
|
SELECT 1 FROM claims c
|
|
147
161
|
WHERE c.task_id = ct.id
|
|
@@ -152,7 +166,7 @@ ${OPEN_BLOCKER_GUARD}${UNSHIPPED_DEPS_GUARD}
|
|
|
152
166
|
ct.priority ASC NULLS LAST,
|
|
153
167
|
COALESCE(ct.est_minutes_calibrated, ct.est_minutes) ASC NULLS LAST,
|
|
154
168
|
ct.id ASC`,
|
|
155
|
-
[mode, builderId]
|
|
169
|
+
[mode, builderId, feedCaps]
|
|
156
170
|
);
|
|
157
171
|
|
|
158
172
|
const chosen = [];
|