@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
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
-- market_watch_001_tables.sql - the Market Watch data model (task 1004936, goal 1000137).
|
|
2
|
+
--
|
|
3
|
+
-- What the owner-approved screens (docs/design/mocks/market-watch/) and the run model of
|
|
4
|
+
-- ADR 0393 need stored. Everything else they show is DERIVED in modules/market-watch/db.js
|
|
5
|
+
-- (an item's level and streak, a dimension's sparkline, this month's spend), so there is
|
|
6
|
+
-- one source for each number and nothing to keep in step.
|
|
7
|
+
--
|
|
8
|
+
-- market_watch_settings one row per project: on/off, the monthly AI cap, cadence
|
|
9
|
+
-- market_watch_rule_versions the watch rules as versioned rows; 'proposed' is the next one
|
|
10
|
+
-- market_watch_items what is watched: competitor | partner | topic
|
|
11
|
+
-- market_watch_runs one row per run, including skipped ones (ADR 0393 s3-4)
|
|
12
|
+
-- market_watch_run_items per-item progress inside a run: what makes a cut-off run
|
|
13
|
+
-- resumable without charging an item twice (ADR 0393 s2)
|
|
14
|
+
-- market_watch_log_entries the case file: found, run, type, takeaway, confidence, source
|
|
15
|
+
-- market_watch_score_snapshots one value per item, dimension and run: the sparklines
|
|
16
|
+
-- market_watch_weekly_asks the weekly question to every member
|
|
17
|
+
-- market_watch_ask_answers each member's taps on it
|
|
18
|
+
-- market_watch_proposals a change asked for by a member or born of a weekly-ask tap
|
|
19
|
+
--
|
|
20
|
+
-- PROJECT. Every project is its own instance with its own database (ADR 0393 Context), so a
|
|
21
|
+
-- row's `project` is that instance's public origin, read from branding at write time and
|
|
22
|
+
-- never hardcoded. It is still a column so a read can never mix two projects if a database
|
|
23
|
+
-- is ever shared, and so the hub can tell its own seed rows from anything else.
|
|
24
|
+
--
|
|
25
|
+
-- PEOPLE. A person is a builders(id). Seeded rows have no author (NULL): the 2026-10-28
|
|
26
|
+
-- research was not a member's act, and naming someone here would be inventing a record.
|
|
27
|
+
--
|
|
28
|
+
-- Additive and market_watch_-prefixed (ADR 0083 s5, fitness checkModuleMigrationsAdditive).
|
|
29
|
+
-- Idempotent: every statement survives a re-run on a database that already has it.
|
|
30
|
+
|
|
31
|
+
BEGIN;
|
|
32
|
+
|
|
33
|
+
-- The project's switch, cap and cadence (the settings screen). `enabled` is the owner's
|
|
34
|
+
-- watch switch, distinct from the module being enabled: the module can be on with the
|
|
35
|
+
-- watch paused. The cap is in US dollars, like cost_log.
|
|
36
|
+
CREATE TABLE IF NOT EXISTS market_watch_settings (
|
|
37
|
+
project text PRIMARY KEY,
|
|
38
|
+
enabled boolean NOT NULL DEFAULT false,
|
|
39
|
+
monthly_cap_usd numeric(10,2) NOT NULL DEFAULT 20 CHECK (monthly_cap_usd >= 0),
|
|
40
|
+
cadence text NOT NULL DEFAULT 'weekly'
|
|
41
|
+
CHECK (cadence IN ('weekly', 'biweekly', 'monthly')),
|
|
42
|
+
updated_by bigint REFERENCES builders(id),
|
|
43
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- The watch rules. One row per version; the rules page's slider walks them by `version`.
|
|
47
|
+
-- Exactly one 'active' version per project (the rules in use) and at most one 'proposed'
|
|
48
|
+
-- (the next one), each held by a partial unique index rather than by every writer's care.
|
|
49
|
+
-- rubric is { "<kind>": [ { "key", "label", "definition", "weight" } ] }: competitors and
|
|
50
|
+
-- partners are scored on different dimensions.
|
|
51
|
+
CREATE TABLE IF NOT EXISTS market_watch_rule_versions (
|
|
52
|
+
id bigserial PRIMARY KEY,
|
|
53
|
+
project text NOT NULL,
|
|
54
|
+
version integer NOT NULL CHECK (version >= 1),
|
|
55
|
+
status text NOT NULL DEFAULT 'proposed'
|
|
56
|
+
CHECK (status IN ('proposed', 'active', 'superseded', 'rejected')),
|
|
57
|
+
search_queries jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(search_queries) = 'array'),
|
|
58
|
+
sources jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(sources) = 'array'),
|
|
59
|
+
rubric jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(rubric) = 'object'),
|
|
60
|
+
instructions text NOT NULL DEFAULT '',
|
|
61
|
+
summary text NOT NULL DEFAULT '', -- the slider's one line ("+ GitHub releases as a source")
|
|
62
|
+
reason text NOT NULL CHECK (length(btrim(reason)) > 0),
|
|
63
|
+
author_id bigint REFERENCES builders(id), -- NULL: seeded, not a member's act
|
|
64
|
+
approved_by bigint REFERENCES builders(id),
|
|
65
|
+
approved_at timestamptz,
|
|
66
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
67
|
+
UNIQUE (project, version)
|
|
68
|
+
);
|
|
69
|
+
CREATE UNIQUE INDEX IF NOT EXISTS market_watch_rule_versions_one_active
|
|
70
|
+
ON market_watch_rule_versions (project) WHERE status = 'active';
|
|
71
|
+
CREATE UNIQUE INDEX IF NOT EXISTS market_watch_rule_versions_one_proposed
|
|
72
|
+
ON market_watch_rule_versions (project) WHERE status = 'proposed';
|
|
73
|
+
|
|
74
|
+
-- What is watched. Keyed by (project, kind, url, name): the research scored five names both as
|
|
75
|
+
-- a competitor and as a partner, on different rubrics, so they are two items. Removing an
|
|
76
|
+
-- item is status 'removed', never a DELETE: its log and scores stay readable, and a re-run
|
|
77
|
+
-- of the seed cannot bring it back (ON CONFLICT DO NOTHING finds the removed row).
|
|
78
|
+
CREATE TABLE IF NOT EXISTS market_watch_items (
|
|
79
|
+
id bigserial PRIMARY KEY,
|
|
80
|
+
project text NOT NULL,
|
|
81
|
+
kind text NOT NULL CHECK (kind IN ('competitor', 'partner', 'topic')),
|
|
82
|
+
name text NOT NULL CHECK (length(btrim(name)) > 0),
|
|
83
|
+
url text NOT NULL, -- a topic has none: ''
|
|
84
|
+
category text NOT NULL DEFAULT '',
|
|
85
|
+
description text NOT NULL DEFAULT '', -- "what it is", one line
|
|
86
|
+
why_tracked text NOT NULL DEFAULT '',
|
|
87
|
+
facts jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(facts) = 'object'),
|
|
88
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'paused', 'removed')),
|
|
89
|
+
added_via text NOT NULL DEFAULT 'member'
|
|
90
|
+
CHECK (added_via IN ('seed', 'member', 'proposal', 'run')),
|
|
91
|
+
added_by bigint REFERENCES builders(id),
|
|
92
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
93
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
94
|
+
UNIQUE (project, kind, url, name)
|
|
95
|
+
);
|
|
96
|
+
CREATE INDEX IF NOT EXISTS market_watch_items_project_status
|
|
97
|
+
ON market_watch_items (project, status, kind);
|
|
98
|
+
|
|
99
|
+
-- One row per run, INCLUDING the runs that never started: a skipped run is a row with a
|
|
100
|
+
-- reason, never only a log line (ADR 0393 "What R04 must build" s4). The settings strip
|
|
101
|
+
-- reads these, and a dash there is a 'skipped' row.
|
|
102
|
+
-- status: queued -> running -> done | partial | skipped | failed
|
|
103
|
+
-- reason: why a run is partial, skipped or failed (NULL when done)
|
|
104
|
+
-- trigger: 'poller' (the weekly clock), 'manual' (an owner's "run now"), 'seed' (the
|
|
105
|
+
-- 2026-10-28 research loaded as run zero, so seeded scores carry a run id)
|
|
106
|
+
-- claimed_until is the single-flight lease: the poller takes a run with
|
|
107
|
+
-- UPDATE ... WHERE status IN ('queued','running') AND claimed_until < now() RETURNING, so a
|
|
108
|
+
-- second process on the same database cannot run it too, and a run whose process died
|
|
109
|
+
-- mid-way is picked up again once its lease lapses.
|
|
110
|
+
CREATE TABLE IF NOT EXISTS market_watch_runs (
|
|
111
|
+
id bigserial PRIMARY KEY,
|
|
112
|
+
project text NOT NULL,
|
|
113
|
+
status text NOT NULL DEFAULT 'queued'
|
|
114
|
+
CHECK (status IN ('queued', 'running', 'done', 'partial', 'skipped', 'failed')),
|
|
115
|
+
reason text CHECK (reason IS NULL OR reason IN
|
|
116
|
+
('off', 'no_key', 'cap_reached', 'spend_unreadable', 'timeout', 'error')),
|
|
117
|
+
trigger text NOT NULL DEFAULT 'poller' CHECK (trigger IN ('poller', 'manual', 'seed')),
|
|
118
|
+
rule_version_id bigint REFERENCES market_watch_rule_versions(id),
|
|
119
|
+
cap_usd numeric(10,2), -- the cap as it stood when the run was planned
|
|
120
|
+
spent_before_usd numeric(10,4), -- this month's spend read before the run
|
|
121
|
+
claimed_until timestamptz,
|
|
122
|
+
started_at timestamptz,
|
|
123
|
+
finished_at timestamptz,
|
|
124
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
125
|
+
CHECK (status NOT IN ('skipped', 'partial', 'failed') OR reason IS NOT NULL)
|
|
126
|
+
);
|
|
127
|
+
CREATE INDEX IF NOT EXISTS market_watch_runs_project_created
|
|
128
|
+
ON market_watch_runs (project, created_at DESC);
|
|
129
|
+
-- The 2026-10-28 research is ONE run per project: what makes the seed idempotent.
|
|
130
|
+
CREATE UNIQUE INDEX IF NOT EXISTS market_watch_runs_one_seed
|
|
131
|
+
ON market_watch_runs (project) WHERE trigger = 'seed';
|
|
132
|
+
|
|
133
|
+
-- Per-item progress inside a run (ADR 0393 s2, "resumable"). One row per (run, item),
|
|
134
|
+
-- written as each item finishes. A resumed run does only the rows that are not 'done',
|
|
135
|
+
-- and an item's cost is recorded here once: cost_usd is set in the same statement that
|
|
136
|
+
-- moves the row to 'done', so a retry finds it done and never prices it again. The
|
|
137
|
+
-- cost_log projection (source market-watch:<run id>) keys on (run, item) for the same
|
|
138
|
+
-- reason.
|
|
139
|
+
CREATE TABLE IF NOT EXISTS market_watch_run_items (
|
|
140
|
+
run_id bigint NOT NULL REFERENCES market_watch_runs(id),
|
|
141
|
+
item_id bigint NOT NULL REFERENCES market_watch_items(id),
|
|
142
|
+
status text NOT NULL DEFAULT 'pending'
|
|
143
|
+
CHECK (status IN ('pending', 'running', 'done', 'error', 'skipped')),
|
|
144
|
+
error text, -- refusal | max_tokens | tool_error | ... (ADR 0393 s1)
|
|
145
|
+
cost_usd numeric(10,4) CHECK (cost_usd IS NULL OR cost_usd >= 0),
|
|
146
|
+
started_at timestamptz,
|
|
147
|
+
finished_at timestamptz,
|
|
148
|
+
PRIMARY KEY (run_id, item_id),
|
|
149
|
+
CHECK (status <> 'done' OR cost_usd IS NOT NULL)
|
|
150
|
+
);
|
|
151
|
+
CREATE INDEX IF NOT EXISTS market_watch_run_items_open
|
|
152
|
+
ON market_watch_run_items (run_id) WHERE status <> 'done';
|
|
153
|
+
|
|
154
|
+
-- The case file. confidence is 1 (low) ... 3 (high), the mock's three dots. source_url is
|
|
155
|
+
-- required: an entry without a source is not an entry (ADR 0393 s1 drops them in code;
|
|
156
|
+
-- this is the floor no future writer can step under). One takeaway per item and run is
|
|
157
|
+
-- stored once, so a resumed run that rewrites an item's findings cannot duplicate them.
|
|
158
|
+
CREATE TABLE IF NOT EXISTS market_watch_log_entries (
|
|
159
|
+
id bigserial PRIMARY KEY,
|
|
160
|
+
item_id bigint NOT NULL REFERENCES market_watch_items(id),
|
|
161
|
+
run_id bigint REFERENCES market_watch_runs(id),
|
|
162
|
+
type text NOT NULL CHECK (type IN ('insight', 'news', 'datapoint')),
|
|
163
|
+
found_on date NOT NULL DEFAULT current_date,
|
|
164
|
+
takeaway text NOT NULL CHECK (length(btrim(takeaway)) > 0),
|
|
165
|
+
dimension text, -- the rubric key it bears on, if any
|
|
166
|
+
source_url text NOT NULL CHECK (source_url ~ '^https?://'),
|
|
167
|
+
source_label text NOT NULL DEFAULT '',
|
|
168
|
+
confidence smallint NOT NULL CHECK (confidence BETWEEN 1 AND 3),
|
|
169
|
+
pinned boolean NOT NULL DEFAULT false,
|
|
170
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
171
|
+
);
|
|
172
|
+
CREATE UNIQUE INDEX IF NOT EXISTS market_watch_log_entries_once
|
|
173
|
+
ON market_watch_log_entries (item_id, run_id, md5(takeaway));
|
|
174
|
+
CREATE INDEX IF NOT EXISTS market_watch_log_entries_item
|
|
175
|
+
ON market_watch_log_entries (item_id, found_on DESC, id DESC);
|
|
176
|
+
|
|
177
|
+
-- Scores over time: one value per (item, dimension, run). 'overall' is a dimension like
|
|
178
|
+
-- the others, so the likeness line and the five dimension lines are one query.
|
|
179
|
+
CREATE TABLE IF NOT EXISTS market_watch_score_snapshots (
|
|
180
|
+
id bigserial PRIMARY KEY,
|
|
181
|
+
item_id bigint NOT NULL REFERENCES market_watch_items(id),
|
|
182
|
+
run_id bigint NOT NULL REFERENCES market_watch_runs(id),
|
|
183
|
+
dimension text NOT NULL CHECK (length(btrim(dimension)) > 0),
|
|
184
|
+
value numeric(4,1) NOT NULL CHECK (value BETWEEN 0 AND 10),
|
|
185
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
186
|
+
UNIQUE (item_id, run_id, dimension)
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
-- The weekly question. question_key names which of the card's questions this is
|
|
190
|
+
-- ('rival' = what counts as a competitor, 'watch' = what else should we watch); options
|
|
191
|
+
-- are the chips, each { "label", "example" }.
|
|
192
|
+
CREATE TABLE IF NOT EXISTS market_watch_weekly_asks (
|
|
193
|
+
id bigserial PRIMARY KEY,
|
|
194
|
+
project text NOT NULL,
|
|
195
|
+
week_of date NOT NULL,
|
|
196
|
+
question_key text NOT NULL CHECK (length(btrim(question_key)) > 0),
|
|
197
|
+
prompt text NOT NULL CHECK (length(btrim(prompt)) > 0),
|
|
198
|
+
options jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(options) = 'array'),
|
|
199
|
+
closes_at timestamptz,
|
|
200
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
201
|
+
UNIQUE (project, week_of, question_key)
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
-- A member's taps. A chip is a `choice`; "name one" and "add a topic" carry free_text.
|
|
205
|
+
-- One row per member and choice, so a second tap on the same chip is a no-op.
|
|
206
|
+
CREATE TABLE IF NOT EXISTS market_watch_ask_answers (
|
|
207
|
+
id bigserial PRIMARY KEY,
|
|
208
|
+
ask_id bigint NOT NULL REFERENCES market_watch_weekly_asks(id),
|
|
209
|
+
builder_id bigint NOT NULL REFERENCES builders(id),
|
|
210
|
+
choice text NOT NULL CHECK (length(btrim(choice)) > 0),
|
|
211
|
+
free_text text,
|
|
212
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
213
|
+
UNIQUE (ask_id, builder_id, choice)
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
-- A change someone asked for. origin 'member' is the item page's "Propose a change" or
|
|
217
|
+
-- the rules page; 'weekly_ask' is born of a tap (answer_id says which). What it changes
|
|
218
|
+
-- is `kind` plus the row it points at; `payload` carries the specifics (a new score, the
|
|
219
|
+
-- source to add, the corrected text, the proposed rules version's id).
|
|
220
|
+
CREATE TABLE IF NOT EXISTS market_watch_proposals (
|
|
221
|
+
id bigserial PRIMARY KEY,
|
|
222
|
+
project text NOT NULL,
|
|
223
|
+
kind text NOT NULL CHECK (kind IN
|
|
224
|
+
('rule_change', 'add_item', 'remove_item', 'rescore', 'move_kind', 'add_source', 'correct_entry')),
|
|
225
|
+
origin text NOT NULL CHECK (origin IN ('member', 'weekly_ask')),
|
|
226
|
+
item_id bigint REFERENCES market_watch_items(id),
|
|
227
|
+
log_entry_id bigint REFERENCES market_watch_log_entries(id),
|
|
228
|
+
rule_version_id bigint REFERENCES market_watch_rule_versions(id),
|
|
229
|
+
answer_id bigint REFERENCES market_watch_ask_answers(id),
|
|
230
|
+
payload jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(payload) = 'object'),
|
|
231
|
+
reason text NOT NULL DEFAULT '',
|
|
232
|
+
proposed_by bigint REFERENCES builders(id),
|
|
233
|
+
status text NOT NULL DEFAULT 'open'
|
|
234
|
+
CHECK (status IN ('open', 'approved', 'rejected', 'withdrawn', 'applied')),
|
|
235
|
+
decided_by bigint REFERENCES builders(id),
|
|
236
|
+
decided_at timestamptz,
|
|
237
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
238
|
+
CHECK (origin <> 'weekly_ask' OR answer_id IS NOT NULL),
|
|
239
|
+
CHECK (kind <> 'rule_change' OR rule_version_id IS NOT NULL),
|
|
240
|
+
CHECK (kind NOT IN ('remove_item', 'rescore', 'move_kind', 'add_source') OR item_id IS NOT NULL),
|
|
241
|
+
CHECK (kind <> 'correct_entry' OR log_entry_id IS NOT NULL)
|
|
242
|
+
);
|
|
243
|
+
CREATE INDEX IF NOT EXISTS market_watch_proposals_open
|
|
244
|
+
ON market_watch_proposals (project, created_at DESC) WHERE status = 'open';
|
|
245
|
+
-- One tap is one proposal: re-pooling the answers is a no-op.
|
|
246
|
+
CREATE UNIQUE INDEX IF NOT EXISTS market_watch_proposals_one_per_answer
|
|
247
|
+
ON market_watch_proposals (answer_id) WHERE answer_id IS NOT NULL;
|
|
248
|
+
|
|
249
|
+
COMMIT;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
-- market_watch_002_run.sql - what the weekly run needs beyond the data model (task 1004937,
|
|
2
|
+
-- goal 1000137, ADR 0394).
|
|
3
|
+
--
|
|
4
|
+
-- 1. The project's Anthropic key. ADR 0394 §1: the run calls the Messages API on the
|
|
5
|
+
-- PROJECT's own key, which the owner sets in Market Watch settings. It is stored the
|
|
6
|
+
-- way ai-studio stores a builder's provider keys (task 1004919): the kernel secret
|
|
7
|
+
-- box's "v1.<iv>.<tag>.<ct>" blob, the master key only in server env, so a database
|
|
8
|
+
-- dump decrypts nothing. last4 is what the settings page shows. modules/market-watch/
|
|
9
|
+
-- key.js is the only reader, and it decrypts only inside a run.
|
|
10
|
+
-- 2. One open run per project. The poller creates a run with INSERT ... ON CONFLICT DO
|
|
11
|
+
-- NOTHING against this index, so two processes on one database that both find the
|
|
12
|
+
-- watch due cannot start two runs (ADR 0394 §2 "one run at a time").
|
|
13
|
+
-- 3. A run's own proposals. A 'new candidate' the run finds becomes a proposal, never a
|
|
14
|
+
-- tracked item (the task's rule), and it was not a member's act nor a weekly-ask tap,
|
|
15
|
+
-- so the origin enum gains 'run'. Widening a CHECK enum is the one DROP CONSTRAINT a
|
|
16
|
+
-- module migration may carry (fitness isConstraintWidening): it is re-added, wider.
|
|
17
|
+
--
|
|
18
|
+
-- 4. A score's one-line reason. The run re-scores each dimension "with a one-line
|
|
19
|
+
-- reason" (the task); the reason is stored beside the value it explains.
|
|
20
|
+
--
|
|
21
|
+
-- Additive and idempotent, like 001.
|
|
22
|
+
|
|
23
|
+
BEGIN;
|
|
24
|
+
|
|
25
|
+
ALTER TABLE market_watch_settings ADD COLUMN IF NOT EXISTS anthropic_key_enc text;
|
|
26
|
+
ALTER TABLE market_watch_settings ADD COLUMN IF NOT EXISTS anthropic_key_last4 text;
|
|
27
|
+
ALTER TABLE market_watch_settings ADD COLUMN IF NOT EXISTS anthropic_key_set_at timestamptz;
|
|
28
|
+
ALTER TABLE market_watch_settings DROP CONSTRAINT IF EXISTS market_watch_settings_key_chk;
|
|
29
|
+
ALTER TABLE market_watch_settings ADD CONSTRAINT market_watch_settings_key_chk CHECK (
|
|
30
|
+
(anthropic_key_enc IS NULL AND anthropic_key_last4 IS NULL)
|
|
31
|
+
OR (anthropic_key_enc LIKE 'v1.%' AND length(anthropic_key_last4) = 4));
|
|
32
|
+
|
|
33
|
+
CREATE UNIQUE INDEX IF NOT EXISTS market_watch_runs_one_open
|
|
34
|
+
ON market_watch_runs (project) WHERE status IN ('queued', 'running');
|
|
35
|
+
|
|
36
|
+
ALTER TABLE market_watch_proposals DROP CONSTRAINT IF EXISTS market_watch_proposals_origin_check;
|
|
37
|
+
ALTER TABLE market_watch_proposals ADD CONSTRAINT market_watch_proposals_origin_check
|
|
38
|
+
CHECK (origin IN ('member', 'weekly_ask', 'run'));
|
|
39
|
+
ALTER TABLE market_watch_proposals ADD COLUMN IF NOT EXISTS run_id bigint REFERENCES market_watch_runs(id);
|
|
40
|
+
-- One proposal per candidate name per run, so a resumed run cannot propose it twice.
|
|
41
|
+
CREATE UNIQUE INDEX IF NOT EXISTS market_watch_proposals_one_per_run_candidate
|
|
42
|
+
ON market_watch_proposals (run_id, lower(payload->>'name')) WHERE origin = 'run';
|
|
43
|
+
|
|
44
|
+
ALTER TABLE market_watch_score_snapshots ADD COLUMN IF NOT EXISTS reason text;
|
|
45
|
+
|
|
46
|
+
COMMIT;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"key": "market-watch",
|
|
3
|
+
"title": "Market Watch (a weekly AI watch on a project's market)",
|
|
4
|
+
"description": "A weekly AI watch on a project's competitors, partners and topics, with a sourced log per tracked item and a weekly question to members (goal 1000137). Its data model (tables, read queries and the hub seed of the 2026-10-28 research) and its weekly run (an hourly poller that researches each tracked item on the project's own Anthropic key, under the monthly cap) are in; the pages and weekly ask are not yet, so it mounts no routes. Off by default, and it cannot be turned on behind an open join door with no payer, because every run will spend the project's AI budget.",
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"coreVersion": "^1.19.0",
|
|
7
|
+
"category": {
|
|
8
|
+
"parent": "knowledge"
|
|
9
|
+
},
|
|
10
|
+
"default": false,
|
|
11
|
+
"maintenance": {
|
|
12
|
+
"status": "core-maintained"
|
|
13
|
+
},
|
|
14
|
+
"contributes": {
|
|
15
|
+
"migrations": true,
|
|
16
|
+
"pollers": [
|
|
17
|
+
"hub-seed",
|
|
18
|
+
"weekly-run"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"spend": {
|
|
22
|
+
"requiresPayer": true
|
|
23
|
+
},
|
|
24
|
+
"provides": [],
|
|
25
|
+
"consumes": []
|
|
26
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/market-watch/pollers/hub-seed.js — load the 2026-10-28 research into the hub's
|
|
4
|
+
// Market Watch, once, shortly after boot (task 1004936).
|
|
5
|
+
//
|
|
6
|
+
// WHY A POLLER. The seed needs to know whether this instance IS the hub, which a SQL
|
|
7
|
+
// migration cannot ask, and module migrations only apply once the module is enabled. A
|
|
8
|
+
// poller runs exactly then: the loader starts it only on an instance with Market Watch
|
|
9
|
+
// switched on, after migrations. It fires one time per boot; every boot after the first
|
|
10
|
+
// load is a no-op by constraint (seed.js), so a redeploy on every merge costs a few
|
|
11
|
+
// SELECTs and never a duplicate row. Off the hub it returns 'not_hub' and writes nothing.
|
|
12
|
+
//
|
|
13
|
+
// Never throws into the server: a failure logs and waits for the next boot, and a
|
|
14
|
+
// database that is not reachable (a builder laptop) is a quiet no-op.
|
|
15
|
+
|
|
16
|
+
const api = require('../../../src/module-api');
|
|
17
|
+
const { seedHub } = require('../seed');
|
|
18
|
+
|
|
19
|
+
const log = api.logger('market-watch');
|
|
20
|
+
const FIRST_MS = Number(process.env.MARKET_WATCH_SEED_DELAY_MS) || 2 * 60_000;
|
|
21
|
+
|
|
22
|
+
let timer = null;
|
|
23
|
+
|
|
24
|
+
async function runOnce(deps = {}) {
|
|
25
|
+
try {
|
|
26
|
+
const r = await (deps.seedHub || seedHub)(deps);
|
|
27
|
+
if (r.seeded) {
|
|
28
|
+
const n = r.inserted;
|
|
29
|
+
log.info(`[hub-seed] ${r.project}: ${n.items} items, ${n.scores} scores, ${n.entries} entries, ${n.rules} rules, ${n.runs} runs inserted`);
|
|
30
|
+
} else {
|
|
31
|
+
log.info(`[hub-seed] skipped (${r.skipped})`);
|
|
32
|
+
}
|
|
33
|
+
return r;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
log.warn(`[hub-seed] not loaded: ${e && e.message}`);
|
|
36
|
+
if (typeof deps.reportError === 'function') deps.reportError();
|
|
37
|
+
return { seeded: false, skipped: 'error', error: e && e.message };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The loader passes its own deps plus reportError; only reportError is taken, so a
|
|
42
|
+
// loader dep can never be read as a seed seam (isHub, project, pool).
|
|
43
|
+
function start({ reportError } = {}) {
|
|
44
|
+
if (timer) return;
|
|
45
|
+
timer = setTimeout(() => { timer = null; runOnce({ reportError }); }, FIRST_MS);
|
|
46
|
+
if (timer.unref) timer.unref();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function stop() {
|
|
50
|
+
if (timer) clearTimeout(timer);
|
|
51
|
+
timer = null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = { start, stop, runOnce };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/market-watch/pollers/weekly-run.js — fire the weekly Market Watch run from the
|
|
4
|
+
// server itself (task 1004937, ADR 0394 §2).
|
|
5
|
+
//
|
|
6
|
+
// A COPY OF THE daily-checks SHAPE, not an import of it (modules may not require each
|
|
7
|
+
// other): one unref'd HOURLY tick, the first a few minutes after boot plus jitter,
|
|
8
|
+
// kill-switched by ROUTINE_TIMERS_DISABLED=1, and it never throws into the server.
|
|
9
|
+
//
|
|
10
|
+
// DUE BY THE LEDGER, NOT BY THE BOOT. The hub redeploys on every merge, so a boot-anchored
|
|
11
|
+
// timer would start (and charge) a run once per deploy. Each tick instead asks
|
|
12
|
+
// market_watch_runs whether the newest run is a cadence old (run.js isDue), and a cut-off
|
|
13
|
+
// run is resumed by whichever tick finds its lease lapsed. A deploy restarts this timer
|
|
14
|
+
// and changes nothing about when the next run is due.
|
|
15
|
+
//
|
|
16
|
+
// The loader starts pollers only on an instance with Market Watch enabled, after its
|
|
17
|
+
// migrations. A database that is not reachable (a builder laptop) is a quiet no-op.
|
|
18
|
+
|
|
19
|
+
const api = require('../../../src/module-api');
|
|
20
|
+
const { runDue } = require('../run');
|
|
21
|
+
const { createStore } = require('../store');
|
|
22
|
+
const { readProjectKey } = require('../key');
|
|
23
|
+
const { projectKey } = require('../seed');
|
|
24
|
+
|
|
25
|
+
const log = api.logger('market-watch');
|
|
26
|
+
|
|
27
|
+
const TICK_MS = Number(process.env.MARKET_WATCH_TICK_MS) || 60 * 60_000;
|
|
28
|
+
// After hub-seed's 2 minutes, so the hub's run zero is in before the first due check.
|
|
29
|
+
const FIRST_MS = Number(process.env.MARKET_WATCH_FIRST_MS) || 4 * 60_000;
|
|
30
|
+
const JITTER_MS = 60_000;
|
|
31
|
+
|
|
32
|
+
function defaultProject() {
|
|
33
|
+
try { return projectKey(api.branding().domains.publicOrigin); } catch { return null; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let running = false;
|
|
37
|
+
let warnedUnreachable = false;
|
|
38
|
+
|
|
39
|
+
// tick(deps) — one look at the ledger, and a run if one is due. NEVER throws.
|
|
40
|
+
async function tick(deps = {}) {
|
|
41
|
+
if (running) return { skipped: 'busy' };
|
|
42
|
+
running = true;
|
|
43
|
+
try {
|
|
44
|
+
const project = deps.project !== undefined ? deps.project : defaultProject();
|
|
45
|
+
if (!project) return { skipped: 'no_project_origin' };
|
|
46
|
+
const pool = deps.pool || api.pool;
|
|
47
|
+
const store = deps.store || createStore(pool);
|
|
48
|
+
const r = await runDue({
|
|
49
|
+
...deps,
|
|
50
|
+
project,
|
|
51
|
+
store,
|
|
52
|
+
readKey: deps.readKey || ((p) => readProjectKey(pool, p)),
|
|
53
|
+
});
|
|
54
|
+
if (r.outcome) {
|
|
55
|
+
log.info(`[weekly-run] ${project}: run ${r.runId} ${r.outcome}${r.reason ? ` (${r.reason})` : ''}`
|
|
56
|
+
+ (r.done !== undefined ? ` — ${r.done} done, ${r.errors} errors, ${r.entries} entries, $${(r.costUsd || 0).toFixed(4)}` : ''));
|
|
57
|
+
}
|
|
58
|
+
return r;
|
|
59
|
+
} catch (e) {
|
|
60
|
+
const msg = (e && e.message) || String(e);
|
|
61
|
+
if (/ECONNREFUSED|ENOTFOUND|does not exist|connect/i.test(msg)) {
|
|
62
|
+
if (!warnedUnreachable) { warnedUnreachable = true; log.info(`[weekly-run] skipped — database unreachable (${msg})`); }
|
|
63
|
+
return { skipped: 'db_unreachable' };
|
|
64
|
+
}
|
|
65
|
+
log.error(`[weekly-run] tick failed: ${msg}`);
|
|
66
|
+
if (typeof deps.reportError === 'function') deps.reportError();
|
|
67
|
+
return { skipped: 'error', error: msg };
|
|
68
|
+
} finally {
|
|
69
|
+
running = false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let firstTimer = null;
|
|
74
|
+
let interval = null;
|
|
75
|
+
|
|
76
|
+
// The loader passes its own deps plus reportError; only reportError is taken, so a loader
|
|
77
|
+
// dep can never be read as a run seam (store, readKey, project).
|
|
78
|
+
function start({ reportError } = {}) {
|
|
79
|
+
if (process.env.ROUTINE_TIMERS_DISABLED === '1') {
|
|
80
|
+
log.info('[weekly-run] disabled (ROUTINE_TIMERS_DISABLED=1)');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (firstTimer || interval) return;
|
|
84
|
+
const fire = () => tick({ reportError });
|
|
85
|
+
firstTimer = setTimeout(() => {
|
|
86
|
+
firstTimer = null;
|
|
87
|
+
fire();
|
|
88
|
+
interval = setInterval(fire, TICK_MS);
|
|
89
|
+
if (interval.unref) interval.unref();
|
|
90
|
+
}, FIRST_MS + Math.floor(Math.random() * JITTER_MS));
|
|
91
|
+
if (firstTimer.unref) firstTimer.unref();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function stop() {
|
|
95
|
+
if (firstTimer) clearTimeout(firstTimer);
|
|
96
|
+
if (interval) clearInterval(interval);
|
|
97
|
+
firstTimer = interval = null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { start, stop, tick, TICK_MS };
|