@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
|
@@ -15,6 +15,8 @@ const { pool, withTx } = api;
|
|
|
15
15
|
const log = api.logger('lifecycle');
|
|
16
16
|
const seams = api;
|
|
17
17
|
const { touchesOverlap } = api.pathMatch;
|
|
18
|
+
const { notPageMarkerSql } = require('./page-tweak-artist.js');
|
|
19
|
+
const { heldPagesForTouches } = require('./page-notice.js');
|
|
18
20
|
// CLAIM_ANY / CLAIM_NEWCOMER name the two atoms in the refusal message. The
|
|
19
21
|
// decision itself moved to db-rank-authz.claimGate (task 1003196), so this file
|
|
20
22
|
// no longer calls claimEligibilityAllows / rankAllowsTask / xenosClaimAllowed
|
|
@@ -27,6 +29,9 @@ const { goalMembershipFailures } = require('./claim-feed.js');
|
|
|
27
29
|
// called from all three claim surfaces below, so a flagged task can't be
|
|
28
30
|
// claimed via the batch path as a bypass of the single-claim gate.
|
|
29
31
|
const { relevanceFrictionFailures } = require('./db-relevance-flags.js');
|
|
32
|
+
// task 1004950 / ADR 0395: a task that needs server access refuses a claimer
|
|
33
|
+
// without the grant — the third extracted gate in the same shape.
|
|
34
|
+
const { taskNeedsFailures } = require('./task-needs-gate.js');
|
|
30
35
|
const { unsatisfiedDeps } = require('./db-deps-criteria.js');
|
|
31
36
|
// depRefusalRow: the ONE shape a DEPS_NOT_SHIPPED refusal carries. All three
|
|
32
37
|
// claim paths below use it — hand-building the object here is what made task
|
|
@@ -251,6 +256,13 @@ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSess
|
|
|
251
256
|
const relevanceRefusals = await relevanceFrictionFailures({ client, tasks: [task] });
|
|
252
257
|
if (relevanceRefusals.length > 0) throw relevanceRefusals[0].refusal;
|
|
253
258
|
|
|
259
|
+
// task 1004950 / ADR 0395: a task that needs the live server refuses a
|
|
260
|
+
// claimer without the server grant. Same extracted check on all three
|
|
261
|
+
// surfaces (task-needs-gate.js). `owner` never refuses here — the feed is
|
|
262
|
+
// where owner work narrows to the owner.
|
|
263
|
+
const needsRefusals = await taskNeedsFailures({ client, tasks: [task], builderId });
|
|
264
|
+
if (needsRefusals.length > 0) throw needsRefusals[0].refusal;
|
|
265
|
+
|
|
254
266
|
// The claim gate (ADR 0015, generalized in task 1516 / ADR 0086 §5): a task
|
|
255
267
|
// can't be claimed while any dependency is un-done — its OWN edges plus those
|
|
256
268
|
// inherited from its goal/criteria, where "done" = task shipped | criterion
|
|
@@ -300,7 +312,7 @@ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSess
|
|
|
300
312
|
const { rows: activeClaimRows } = await client.query(
|
|
301
313
|
`SELECT c.id, t.id AS task_id, t.title, t.touches, t.module_key FROM claims c
|
|
302
314
|
JOIN tasks t ON t.id = c.task_id
|
|
303
|
-
WHERE c.released_at IS NULL AND t.id <> $1`,
|
|
315
|
+
WHERE c.released_at IS NULL AND t.id <> $1 AND ${notPageMarkerSql('c', 't')}`,
|
|
304
316
|
[taskId]
|
|
305
317
|
);
|
|
306
318
|
// BV1.R53 (task 1422): module-disjointness is a HARD gate (not advisory) when
|
|
@@ -460,7 +472,10 @@ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSess
|
|
|
460
472
|
console.log('[gds] graduation broadcast post-claim failed:', e && e.message);
|
|
461
473
|
}
|
|
462
474
|
}
|
|
463
|
-
|
|
475
|
+
// task 1004948: a gentle heads-up, never a refusal, when this task's files belong
|
|
476
|
+
// to a page an artist is updating. Read after the commit; a failed read is "none".
|
|
477
|
+
const pageNotice = await heldPagesForTouches(task.touches, client, { exceptTaskId: taskId });
|
|
478
|
+
return { ...insertClaim.rows[0], just_unlocked: justUnlocked, overlap_warning: overlapWarning, sibling_overlap_warning: siblingWarning, page_notice: pageNotice };
|
|
464
479
|
} catch (err) {
|
|
465
480
|
await client.query('ROLLBACK').catch(() => {});
|
|
466
481
|
if (err && err.code === '23505') {
|
|
@@ -658,6 +673,12 @@ async function claimTasksBatch({ taskIds, builderId, worktreePrefix, creatorSess
|
|
|
658
673
|
failures.push({ task_id: f.task_id, reason: code, ...rest });
|
|
659
674
|
}
|
|
660
675
|
|
|
676
|
+
// task 1004950: the SAME extracted server-access gate as claimTask.
|
|
677
|
+
for (const f of await taskNeedsFailures({ client, tasks: taskRows, builderId })) {
|
|
678
|
+
const { code, ...rest } = f.refusal;
|
|
679
|
+
failures.push({ task_id: f.task_id, reason: code, ...rest });
|
|
680
|
+
}
|
|
681
|
+
|
|
661
682
|
// Per-task dependency check (ADR 0015, generalized task 1516). An EXTERNAL dep
|
|
662
683
|
// (not in this batch) must already be done; a batch-internal dep isn't done yet
|
|
663
684
|
// at claim time but doesn't block — the batch is worked serially. "Internal"
|
|
@@ -724,7 +745,8 @@ async function claimTasksBatch({ taskIds, builderId, worktreePrefix, creatorSess
|
|
|
724
745
|
FROM claims c
|
|
725
746
|
JOIN tasks t ON t.id = c.task_id
|
|
726
747
|
WHERE c.released_at IS NULL
|
|
727
|
-
AND t.id <> ALL($1::bigint[])
|
|
748
|
+
AND t.id <> ALL($1::bigint[])
|
|
749
|
+
AND ${notPageMarkerSql('c', 't')}`,
|
|
728
750
|
[uniqIds]
|
|
729
751
|
);
|
|
730
752
|
for (const t of taskRows) {
|
|
@@ -966,7 +988,7 @@ async function validateClaimBatch({ taskIds, builderId }) {
|
|
|
966
988
|
const { rows: activeClaims } = await pool.query(
|
|
967
989
|
`SELECT c.id AS claim_id, t.title, t.touches, t.module_key
|
|
968
990
|
FROM claims c JOIN tasks t ON t.id = c.task_id
|
|
969
|
-
WHERE c.released_at IS NULL AND t.id <> ALL($1::bigint[])`,
|
|
991
|
+
WHERE c.released_at IS NULL AND t.id <> ALL($1::bigint[]) AND ${notPageMarkerSql('c', 't')}`,
|
|
970
992
|
[uniqIds]
|
|
971
993
|
);
|
|
972
994
|
|
|
@@ -1014,6 +1036,12 @@ async function validateClaimBatch({ taskIds, builderId }) {
|
|
|
1014
1036
|
push(f.task_id, { reason: code, ...rest });
|
|
1015
1037
|
}
|
|
1016
1038
|
|
|
1039
|
+
// task 1004950: server-access, kept in sync with claimTask + claimTasksBatch.
|
|
1040
|
+
for (const f of await taskNeedsFailures({ client: pool, tasks: taskRows, builderId })) {
|
|
1041
|
+
const { code, ...rest } = f.refusal;
|
|
1042
|
+
push(f.task_id, { reason: code, ...rest });
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1017
1045
|
// Dependencies — external to the batch only (a batch-internal dep is fine;
|
|
1018
1046
|
// the batch is worked serially, same rule as claimTasksBatch).
|
|
1019
1047
|
const batchIdSet = new Set(uniqIds);
|
|
@@ -16,7 +16,7 @@ const doneWhen = require('./done-when.js');
|
|
|
16
16
|
const { deriveRequiredRank, highestRank } = require('./db-rank-authz.js');
|
|
17
17
|
const { nonTerminalSql, isTerminalStatus } = require('./task-status');
|
|
18
18
|
const { maintenanceGoalTitle } = require('./goal-advisory.js');
|
|
19
|
-
const { SMOKE_NOT_LIKE } = require('./db-shared.js');
|
|
19
|
+
const { SMOKE_NOT_LIKE, idKey } = require('./db-shared.js');
|
|
20
20
|
const { executorFor, inTransaction } = require('./db-executor.js');
|
|
21
21
|
// Task 1003660 — closing a goal cancels its pending invites + join requests.
|
|
22
22
|
const { cancelPendingMembershipRequests } = require('./goal-close-sweep.js');
|
|
@@ -487,14 +487,19 @@ async function reorderGoals({ versionId, orderedIds }, deps = {}) {
|
|
|
487
487
|
// FOR UPDATE on its transaction client, and this UPDATE on a pool connection
|
|
488
488
|
// blocks on that lock — while the transaction is awaiting this UPDATE before it
|
|
489
489
|
// can COMMIT and release it. Neither side can move.
|
|
490
|
-
|
|
490
|
+
//
|
|
491
|
+
// `fromStatus` (task 1001693) makes the move conditional: the row flips only if it
|
|
492
|
+
// still reads that status, and null comes back otherwise. The achieve path uses it
|
|
493
|
+
// so exactly one caller wins a goal's flip to `achieved`, and only the winner books
|
|
494
|
+
// the goal-achievement karma.
|
|
495
|
+
async function setGoalStatus({ goalId, status, fromStatus = null }, deps = {}) {
|
|
491
496
|
const activePool = executorFor(deps, pool);
|
|
492
497
|
const { rows } = await activePool.query(
|
|
493
498
|
`UPDATE goals
|
|
494
499
|
SET status = $2, updated_at = now()
|
|
495
|
-
WHERE id = $1
|
|
500
|
+
WHERE id = $1 AND ($3::text IS NULL OR status = $3::text)
|
|
496
501
|
RETURNING ${GOAL_COLS}`,
|
|
497
|
-
[goalId, status]
|
|
502
|
+
[goalId, status, fromStatus]
|
|
498
503
|
);
|
|
499
504
|
// A goal leaving `open` takes no new members, so its pending asks are dead.
|
|
500
505
|
if (rows[0] && status !== 'open') await cancelPendingMembershipRequests(activePool, goalId);
|
|
@@ -547,7 +552,16 @@ async function achieveGoalIfComplete({ goalId }, deps = {}) {
|
|
|
547
552
|
// This never blocks a SHIP; it declines only the goal's flip to achieved, and
|
|
548
553
|
// the goal simply stays open until its last task lands or is abandoned.
|
|
549
554
|
if (unfinishedTasks > 0) return { achieved: false, goal, unfinishedTasks };
|
|
550
|
-
|
|
555
|
+
// Conditional on still reading `open` (task 1001693): two overrides closing a
|
|
556
|
+
// goal's last criteria at once both reach here, and only one may flip it and
|
|
557
|
+
// book its karma. The loser reports the goal as achieved, which it now is, and
|
|
558
|
+
// leaves the reward and the version close to the winner.
|
|
559
|
+
const updated = await setGoalStatus({ goalId, status: 'achieved', fromStatus: 'open' }, deps);
|
|
560
|
+
if (!updated) return { achieved: true, goal: await getGoal(goalId, { ...deps, forUpdate: false }), versionsClosed: [] };
|
|
561
|
+
const exec = executorFor(deps, pool);
|
|
562
|
+
// The same reward the ship-time flip books, through the same helper, so a failed
|
|
563
|
+
// booking is logged the same way on both doors.
|
|
564
|
+
await doneWhen.rewardAchievedGoals(exec, [updated]);
|
|
551
565
|
|
|
552
566
|
// BV1.R16 (task 1003603, ADR 0263 §§3–4): the SECOND achievement writer. A goal
|
|
553
567
|
// reaching `achieved` may have been the version's last non-maintenance goal, and
|
|
@@ -559,7 +573,6 @@ async function achieveGoalIfComplete({ goalId }, deps = {}) {
|
|
|
559
573
|
//
|
|
560
574
|
// Runs on the caller's exec so it sees the flip above, which in the ship path is
|
|
561
575
|
// still uncommitted. Never throws: autoCloseVersionsForGoals catches per version.
|
|
562
|
-
const exec = executorFor(deps, pool);
|
|
563
576
|
const versionsClosed = await autoCloseVersionsForGoals(exec, [updated]);
|
|
564
577
|
return { achieved: true, goal: updated, versionsClosed };
|
|
565
578
|
}
|
|
@@ -733,28 +746,43 @@ async function archiveGoalWithDispositions({ goalId, plan = [], reason }, deps =
|
|
|
733
746
|
return { goal: goalRows[0], applied: [], alreadyArchived: true };
|
|
734
747
|
}
|
|
735
748
|
|
|
736
|
-
|
|
737
|
-
|
|
749
|
+
// task 1004929: every plan task is locked and read in ONE statement, and the
|
|
750
|
+
// active claims on them in one more, instead of two round trips per step. The
|
|
751
|
+
// locks are taken in id order (no deadlock against another batch), then the
|
|
752
|
+
// plan is walked in memory in PLAN order, so the applied list and the first
|
|
753
|
+
// error are what the per-step loop produced. `inGoal` tracks this call's own
|
|
754
|
+
// writes, so a task named twice reads the state the first step left behind.
|
|
755
|
+
const inGoal = new Map();
|
|
756
|
+
const claimedIds = new Set();
|
|
757
|
+
if (plan.length > 0) {
|
|
758
|
+
const ids = plan.map((step) => step.taskId);
|
|
738
759
|
const { rows: tRows } = await client.query(
|
|
739
|
-
`SELECT id, title, status FROM tasks WHERE id = $1 AND goal_id = $2 FOR UPDATE`,
|
|
740
|
-
[
|
|
760
|
+
`SELECT id, title, status FROM tasks WHERE id = ANY($1::bigint[]) AND goal_id = $2 ORDER BY id FOR UPDATE`,
|
|
761
|
+
[ids, goalId]
|
|
762
|
+
);
|
|
763
|
+
for (const row of tRows) inGoal.set(idKey(row.id), row);
|
|
764
|
+
const { rows: claimed } = await client.query(
|
|
765
|
+
`SELECT DISTINCT task_id FROM claims WHERE task_id = ANY($1::bigint[]) AND released_at IS NULL`, [ids]
|
|
741
766
|
);
|
|
767
|
+
for (const row of claimed) claimedIds.add(idKey(row.task_id));
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const applied = [];
|
|
771
|
+
for (const step of plan) {
|
|
772
|
+
const task = inGoal.get(idKey(step.taskId));
|
|
742
773
|
// The task left this goal (or shipped) while the caller was deciding. Not an
|
|
743
774
|
// error: the disposition is moot, and the final re-count is what actually
|
|
744
775
|
// guards the invariant. Recorded as 'skipped' so the response tells the
|
|
745
776
|
// truth about what this call did rather than claiming a write it never made.
|
|
746
|
-
if (!
|
|
747
|
-
if (isTerminalStatus(
|
|
748
|
-
applied.push({ task_id: String(step.taskId), verb: step.verb, result: 'skipped_already_terminal', status:
|
|
777
|
+
if (!task) { applied.push({ task_id: String(step.taskId), verb: step.verb, result: 'skipped_not_in_goal' }); continue; }
|
|
778
|
+
if (isTerminalStatus(task.status)) {
|
|
779
|
+
applied.push({ task_id: String(step.taskId), verb: step.verb, result: 'skipped_already_terminal', status: task.status });
|
|
749
780
|
continue;
|
|
750
781
|
}
|
|
751
782
|
|
|
752
|
-
|
|
753
|
-
`SELECT id FROM claims WHERE task_id = $1 AND released_at IS NULL LIMIT 1`, [step.taskId]
|
|
754
|
-
);
|
|
755
|
-
if (claimed[0]) {
|
|
783
|
+
if (claimedIds.has(idKey(step.taskId))) {
|
|
756
784
|
throw Object.assign(new Error('task has an active claim'), {
|
|
757
|
-
code: 'TASK_HAS_ACTIVE_CLAIM', taskId: String(step.taskId), taskTitle:
|
|
785
|
+
code: 'TASK_HAS_ACTIVE_CLAIM', taskId: String(step.taskId), taskTitle: task.title,
|
|
758
786
|
});
|
|
759
787
|
}
|
|
760
788
|
|
|
@@ -766,6 +794,7 @@ async function archiveGoalWithDispositions({ goalId, plan = [], reason }, deps =
|
|
|
766
794
|
WHERE id = $1`,
|
|
767
795
|
[step.taskId, `Abandoned: ${reason}`]
|
|
768
796
|
);
|
|
797
|
+
task.status = 'abandoned';
|
|
769
798
|
applied.push({ task_id: String(step.taskId), verb: 'abandon', result: 'abandoned' });
|
|
770
799
|
} else {
|
|
771
800
|
const { rows: dest } = await client.query(
|
|
@@ -780,6 +809,7 @@ async function archiveGoalWithDispositions({ goalId, plan = [], reason }, deps =
|
|
|
780
809
|
});
|
|
781
810
|
}
|
|
782
811
|
await client.query(`UPDATE tasks SET goal_id = $2, updated_at = now() WHERE id = $1`, [step.taskId, step.toGoalId]);
|
|
812
|
+
if (idKey(step.toGoalId) !== idKey(goalId)) inGoal.delete(idKey(step.taskId));
|
|
783
813
|
applied.push({ task_id: String(step.taskId), verb: 'move', result: 'moved', to_goal_id: String(step.toGoalId) });
|
|
784
814
|
}
|
|
785
815
|
}
|
|
@@ -104,10 +104,19 @@ async function mostRecentClaimHolderId(client, taskId) {
|
|
|
104
104
|
return rows[0]?.builder_id ?? null;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// One spelling of a bigint id as a Map key (task 1004929). pg returns bigint
|
|
108
|
+
// columns as strings while a plan may carry numbers, so a batched locked read
|
|
109
|
+
// (the goal archive, the version close) matches its rows back to the plan steps
|
|
110
|
+
// through this on both sides. A value BigInt cannot read keeps its own string.
|
|
111
|
+
function idKey(v) {
|
|
112
|
+
try { return BigInt(v).toString(); } catch { return String(v); }
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
module.exports = {
|
|
108
116
|
SMOKE_NOT_LIKE,
|
|
109
117
|
gradePassCount,
|
|
110
118
|
gradeQualityWhere,
|
|
119
|
+
idKey,
|
|
111
120
|
median,
|
|
112
121
|
mostRecentClaimHolderId,
|
|
113
122
|
sealSessionLog,
|
|
@@ -22,6 +22,8 @@ const { isReservedCriterionSlug } = require('./criterion-ref.js');
|
|
|
22
22
|
const { resolveWriteDiscipline, WRITABLE_DISCIPLINES } = require('./task-classifier.js');
|
|
23
23
|
const { gateOnSpecTask } = require('./goal-spec-task.js');
|
|
24
24
|
const { ensureArtistReviewGoal } = require('./artist-review-goal.js');
|
|
25
|
+
const { notPageMarkerSql } = require('./page-tweak-artist.js');
|
|
26
|
+
const { needsFilterWhere, capabilityFeedWhere } = require('./task-needs-gate.js');
|
|
25
27
|
|
|
26
28
|
// -------------------------------------------------------------------------
|
|
27
29
|
// Tasks
|
|
@@ -147,6 +149,7 @@ async function createTask({
|
|
|
147
149
|
// (requiresRank stays null for the goal vector → derived purely from touches +
|
|
148
150
|
// sensitivity), so goal_id never influences the requires_rank clamp.
|
|
149
151
|
goalId = null,
|
|
152
|
+
needs = [], // task 1004950 / ADR 0395 — ⊆ ['owner','server']; routes normalize, the CHECK backstops
|
|
150
153
|
// task 1003278: WHO created it — the builder id from the authenticated request.
|
|
151
154
|
// Nullable, appended at the end (the goals bind-order convention): a system
|
|
152
155
|
// path with no builder in the request (a seed, a migration backfill) passes
|
|
@@ -267,13 +270,13 @@ async function createTask({
|
|
|
267
270
|
est_minutes, est_cost_usd, manual_degree, priority,
|
|
268
271
|
automation_tag, credits_reward, source, source_ref, value_summary,
|
|
269
272
|
kind, discipline, parallel_safe, newcomer_friendly, needs_migration,
|
|
270
|
-
security_sensitive, requires_rank, module_key, goal_id, created_by, promoted_at)
|
|
273
|
+
security_sensitive, requires_rank, module_key, goal_id, created_by, promoted_at, needs)
|
|
271
274
|
VALUES ($1, $2, $3, $4, $5,
|
|
272
275
|
$6, $7, $8, $9,
|
|
273
276
|
$10, $11, $12, $13, $14,
|
|
274
277
|
$15, $16, $17, $18, $19,
|
|
275
278
|
$20, $21, $22, $23, $24,
|
|
276
|
-
CASE WHEN $4 IN ('ready','active') THEN now() ELSE NULL END)
|
|
279
|
+
CASE WHEN $4 IN ('ready','active') THEN now() ELSE NULL END, $25)
|
|
277
280
|
RETURNING *`,
|
|
278
281
|
[
|
|
279
282
|
versionId,
|
|
@@ -300,6 +303,7 @@ async function createTask({
|
|
|
300
303
|
moduleKey ?? null,
|
|
301
304
|
goalIdEffective ?? null,
|
|
302
305
|
createdBy ?? null,
|
|
306
|
+
Array.isArray(needs) ? needs : [],
|
|
303
307
|
]
|
|
304
308
|
);
|
|
305
309
|
// task 1677 / ADR 0096: audit each creation-time auto-assignment so the trail is
|
|
@@ -357,7 +361,7 @@ async function findTaskBySourceRef({ source, sourceRef } = {}, deps = {}) {
|
|
|
357
361
|
|
|
358
362
|
const TASK_SETTER_COLUMNS = new Set([
|
|
359
363
|
'kind', 'discipline', 'newcomer_friendly', 'security_sensitive', 'parallel_safe', 'automation_tag',
|
|
360
|
-
'needs_migration', 'module_key', 'description', 'title', 'priority', 'value_summary',
|
|
364
|
+
'needs_migration', 'module_key', 'description', 'title', 'priority', 'value_summary', 'needs',
|
|
361
365
|
]);
|
|
362
366
|
|
|
363
367
|
async function updateTaskColumn(id, column, value) {
|
|
@@ -647,6 +651,7 @@ async function updateTaskParallelSafe(id, parallelSafe) { return updateTaskColum
|
|
|
647
651
|
// and leave the tag unset, then later judge that a task is safe to delegate.
|
|
648
652
|
|
|
649
653
|
async function updateTaskAutomationTag(id, automationTag) { return updateTaskColumn(id, 'automation_tag', automationTag); }
|
|
654
|
+
async function updateTaskNeeds(id, needs) { return updateTaskColumn(id, 'needs', needs); }
|
|
650
655
|
|
|
651
656
|
// task 1002648: correct the create-time migration heuristic's verdict — a false
|
|
652
657
|
// flag makes claiming reserve a migration number that goes unused (task 1002614
|
|
@@ -777,7 +782,7 @@ async function getTask(id) {
|
|
|
777
782
|
}
|
|
778
783
|
|
|
779
784
|
|
|
780
|
-
async function listTasks({ versionId, status, kind, discipline, goalId, createdBy, staleDays, limit, offset } = {}) {
|
|
785
|
+
async function listTasks({ versionId, status, kind, discipline, goalId, createdBy, staleDays, limit, offset, needs } = {}) {
|
|
781
786
|
const params = [];
|
|
782
787
|
const where = [];
|
|
783
788
|
if (versionId) {
|
|
@@ -800,6 +805,7 @@ async function listTasks({ versionId, status, kind, discipline, goalId, createdB
|
|
|
800
805
|
params.push(goalId);
|
|
801
806
|
where.push(`goal_id = $${params.length}`);
|
|
802
807
|
}
|
|
808
|
+
const needsWhere = needsFilterWhere(needs, '', params); if (needsWhere) where.push(needsWhere); // task 1004950
|
|
803
809
|
// task 1003278 — the creator + staleness axes, server-side. They exist HERE and
|
|
804
810
|
// not only in the browser because the board was reducing the whole table to
|
|
805
811
|
// answer them: GET /tasks?status=backlog measured 690,625 bytes on 2026-08-25,
|
|
@@ -1015,6 +1021,7 @@ const CLAIMABLE_SQL = `
|
|
|
1015
1021
|
SELECT 1 FROM claims c
|
|
1016
1022
|
JOIN tasks t2 ON t2.id = c.task_id
|
|
1017
1023
|
WHERE c.released_at IS NULL
|
|
1024
|
+
AND ${notPageMarkerSql('c', 't2')}
|
|
1018
1025
|
AND t2.touches && t.touches
|
|
1019
1026
|
)`;
|
|
1020
1027
|
const SESSION_FIT_SQL = `
|
|
@@ -1028,7 +1035,7 @@ const SESSION_FIT_SQL = `
|
|
|
1028
1035
|
END AS fits_session_modes
|
|
1029
1036
|
FROM tasks t`;
|
|
1030
1037
|
|
|
1031
|
-
async function listClaimableTasks({ versionId = null, builderId = null, discipline = null, fit = null, createdBy = null, staleDays = null } = {}) {
|
|
1038
|
+
async function listClaimableTasks({ versionId = null, builderId = null, discipline = null, fit = null, createdBy = null, staleDays = null, needs = null } = {}) {
|
|
1032
1039
|
const params = [];
|
|
1033
1040
|
const where = [];
|
|
1034
1041
|
let goalJoin = '';
|
|
@@ -1037,6 +1044,7 @@ async function listClaimableTasks({ versionId = null, builderId = null, discipli
|
|
|
1037
1044
|
params.push(versionId);
|
|
1038
1045
|
where.push(`v.version_id = $${params.length}`);
|
|
1039
1046
|
}
|
|
1047
|
+
const needsWhere = needsFilterWhere(needs, 'v', params); if (needsWhere) where.push(needsWhere); // task 1004950: the 'waiting on you' queue
|
|
1040
1048
|
if (discipline) {
|
|
1041
1049
|
params.push(discipline);
|
|
1042
1050
|
where.push(`v.discipline = $${params.length}`);
|
|
@@ -1110,6 +1118,7 @@ async function listClaimableTasks({ versionId = null, builderId = null, discipli
|
|
|
1110
1118
|
selectCols = `v.*, g.visibility AS g_visibility, g.scope_modules AS g_scope_modules, g.title AS g_title,
|
|
1111
1119
|
(gm.builder_id IS NOT NULL
|
|
1112
1120
|
OR (SELECT rank FROM builders WHERE id = $${params.length}) = 'archon') AS g_is_member`;
|
|
1121
|
+
where.push(await capabilityFeedWhere(builderId, fit, 'v', params)); // task 1004950 / ADR 0395: only what the viewer can do
|
|
1113
1122
|
}
|
|
1114
1123
|
// Autonomy / parallel-eligibility filter. Joining the session-fit relation
|
|
1115
1124
|
// (also a SELECT t.* shape) makes bare column names like priority/created_at
|
|
@@ -1470,7 +1479,7 @@ module.exports = {
|
|
|
1470
1479
|
suggestCreditsReward,
|
|
1471
1480
|
taskTouchesProtectedPath,
|
|
1472
1481
|
updateCriterionGoal,
|
|
1473
|
-
updateTaskAutomationTag,
|
|
1482
|
+
updateTaskAutomationTag, updateTaskNeeds,
|
|
1474
1483
|
updateTaskDescription,
|
|
1475
1484
|
updateTaskTitle,
|
|
1476
1485
|
updateTaskDiscipline,
|
|
@@ -14,7 +14,7 @@ const api = require('../../src/module-api');
|
|
|
14
14
|
const { pool, withTx } = api;
|
|
15
15
|
const doneWhen = require('./done-when.js');
|
|
16
16
|
const { nonTerminalSql } = require('./task-status');
|
|
17
|
-
const { SMOKE_NOT_LIKE } = require('./db-shared.js');
|
|
17
|
+
const { SMOKE_NOT_LIKE, idKey } = require('./db-shared.js');
|
|
18
18
|
const { executorFor, inTransaction } = require('./db-executor.js');
|
|
19
19
|
// db-goals.js does NOT require this file, so this direction adds no require cycle
|
|
20
20
|
// (fitness pins the count). createVersion needs it to seed a version's FIRST goal
|
|
@@ -372,18 +372,29 @@ async function closeVersion({ versionId, plan = [], reason, planningVersionId =
|
|
|
372
372
|
});
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
// task 1004929: every plan goal is locked and read in ONE statement instead of
|
|
376
|
+
// one round trip per step. Locks are taken in id order (no deadlock against
|
|
377
|
+
// another batch); the plan is then walked in memory in PLAN order, so the
|
|
378
|
+
// applied list is what the per-step loop produced. Each step's own archive is
|
|
379
|
+
// mirrored into `onVersion`, so a goal named twice reads 'archived' second time.
|
|
380
|
+
const onVersion = new Map();
|
|
381
|
+
if (plan.length > 0) {
|
|
377
382
|
const { rows: gRows } = await client.query(
|
|
378
|
-
`SELECT id, title, status FROM goals WHERE id = $1 AND version_id = $2 FOR UPDATE`,
|
|
379
|
-
[step.goalId, versionId]
|
|
383
|
+
`SELECT id, title, status FROM goals WHERE id = ANY($1::bigint[]) AND version_id = $2 ORDER BY id FOR UPDATE`,
|
|
384
|
+
[plan.map((step) => step.goalId), versionId]
|
|
380
385
|
);
|
|
386
|
+
for (const row of gRows) onVersion.set(idKey(row.id), row);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const applied = [];
|
|
390
|
+
for (const step of plan) {
|
|
391
|
+
const goal = onVersion.get(idKey(step.goalId));
|
|
381
392
|
// The goal moved or closed itself while the caller was deciding. Not an
|
|
382
393
|
// error — the final re-count is what guards the invariant — but recorded so
|
|
383
394
|
// the response says what this call actually did.
|
|
384
|
-
if (!
|
|
385
|
-
if (
|
|
386
|
-
applied.push({ goal_id: String(step.goalId), verb: step.verb, result: 'skipped_already_closed', status:
|
|
395
|
+
if (!goal) { applied.push({ goal_id: String(step.goalId), verb: step.verb, result: 'skipped_not_on_version' }); continue; }
|
|
396
|
+
if (goal.status !== 'open') {
|
|
397
|
+
applied.push({ goal_id: String(step.goalId), verb: step.verb, result: 'skipped_already_closed', status: goal.status });
|
|
387
398
|
continue;
|
|
388
399
|
}
|
|
389
400
|
|
|
@@ -410,6 +421,7 @@ async function closeVersion({ versionId, plan = [], reason, planningVersionId =
|
|
|
410
421
|
[step.goalId, `Abandoned: ${reason}`]
|
|
411
422
|
);
|
|
412
423
|
await client.query(`UPDATE goals SET status = 'archived', updated_at = now() WHERE id = $1`, [step.goalId]);
|
|
424
|
+
Object.assign(goal, { status: 'archived' }); // mirror the write above for a goal named twice
|
|
413
425
|
await cancelPendingMembershipRequests(client, step.goalId);
|
|
414
426
|
applied.push({ goal_id: String(step.goalId), verb: 'abandon', result: 'archived', tasks_abandoned: killed.length });
|
|
415
427
|
} else {
|
|
@@ -502,6 +514,7 @@ async function closeVersion({ versionId, plan = [], reason, planningVersionId =
|
|
|
502
514
|
// on the way out rather than being special-cased around it (the brief's
|
|
503
515
|
// explicit ask).
|
|
504
516
|
await client.query(`UPDATE goals SET status = 'archived', updated_at = now() WHERE id = $1`, [step.goalId]);
|
|
517
|
+
Object.assign(goal, { status: 'archived' }); // mirror the write above for a goal named twice
|
|
505
518
|
await cancelPendingMembershipRequests(client, step.goalId);
|
|
506
519
|
|
|
507
520
|
applied.push({
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// POST /api/bongos/done-when/:criterionId/satisfy — auth
|
|
24
24
|
// POST /api/bongos/done-when/:criterionId/unsatisfy — auth
|
|
25
25
|
|
|
26
|
-
const { pool } = require('../../src/module-api');
|
|
26
|
+
const { pool, resolveOptional } = require('../../src/module-api');
|
|
27
27
|
const { nonTerminalSql } = require('./task-status');
|
|
28
28
|
const { SMOKE_NOT_LIKE } = require('./db-shared.js');
|
|
29
29
|
const { executorFor } = require('./db-executor.js');
|
|
@@ -618,9 +618,30 @@ async function achieveCompletedGoals(exec, goalIds) {
|
|
|
618
618
|
// Task 1003660: an achieved goal takes no new members — close its pending asks
|
|
619
619
|
// on the same executor, so the cancel rides the ship transaction.
|
|
620
620
|
await cancelPendingMembershipRequests(exec, rows.map((g) => g.id));
|
|
621
|
+
await rewardAchievedGoals(exec, rows);
|
|
621
622
|
return rows;
|
|
622
623
|
}
|
|
623
624
|
|
|
625
|
+
// rewardAchievedGoals — the goal-achievement karma (task 1001693 / BV1.P5.3, ADR
|
|
626
|
+
// 0101 Part 3), booked through the economy's `reward` port for each goal THIS
|
|
627
|
+
// statement flipped. Only the caller whose UPDATE ... WHERE status = 'open'
|
|
628
|
+
// returned the row gets here, so a concurrent ship or sweep cannot award twice.
|
|
629
|
+
// Optional and best-effort: no economy module means no karma, and the award runs
|
|
630
|
+
// under its own SAVEPOINT (goal-reward.js) so a failed karma write cannot abort
|
|
631
|
+
// the ship that achieved the goal. The catch here is for a provider that throws.
|
|
632
|
+
async function rewardAchievedGoals(exec, goals) {
|
|
633
|
+
const reward = resolveOptional('reward');
|
|
634
|
+
if (!reward || typeof reward.awardGoalAchievementKarma !== 'function') return;
|
|
635
|
+
for (const g of goals) {
|
|
636
|
+
try {
|
|
637
|
+
const r = await reward.awardGoalAchievementKarma(exec, { goalId: g.id });
|
|
638
|
+
if (r && r.reason === 'failed') console.warn(`[lifecycle] goal ${g.id} achieved; its karma was not booked: ${r.error}`);
|
|
639
|
+
} catch (e) {
|
|
640
|
+
console.warn(`[lifecycle] goal ${g.id} achieved; its karma was not booked: ${e && e.message}`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
624
645
|
// describeAutoClose — the one-line summary of what a close actually closed, or
|
|
625
646
|
// null on a no-op. Pure + here rather than in db.js because db.js is over the
|
|
626
647
|
// fitness oversize threshold: growth there has to be paid for, and formatting is
|
|
@@ -674,6 +695,7 @@ module.exports = {
|
|
|
674
695
|
autoSatisfyShippedCriteria,
|
|
675
696
|
describeAutoClose,
|
|
676
697
|
closeCompletedWorkForShip,
|
|
698
|
+
rewardAchievedGoals,
|
|
677
699
|
markCriterion,
|
|
678
700
|
unmarkCriterion,
|
|
679
701
|
};
|
|
@@ -26,6 +26,7 @@ const activity = require('./activity');
|
|
|
26
26
|
const mingle = require('./mingle');
|
|
27
27
|
const pageTweakReads = require('./page-tweak-reads');
|
|
28
28
|
const pageTweakClaim = require('./page-tweak-claim');
|
|
29
|
+
const pageNotice = require('./page-notice');
|
|
29
30
|
const pageTweakWrite = require('./page-tweak-write');
|
|
30
31
|
const pageTweakApprove = require('./page-tweak-approve');
|
|
31
32
|
|
|
@@ -165,6 +166,10 @@ module.exports = {
|
|
|
165
166
|
// read the part-3 claim gate uses, surfaced to me.js so the CLI + hall can
|
|
166
167
|
// show the owed-rebase warning that gates new claims (reward lands on ship).
|
|
167
168
|
needsRebaseTasksForBuilder: (...a) => db.needsRebaseTasksForBuilder(...a),
|
|
169
|
+
// task 1004948: the page-update heads-up lines for a claim's files, for GET /me.
|
|
170
|
+
noticesForClaims: (...a) => pageNotice.noticesForClaims(...a),
|
|
171
|
+
// task 1004948: engineering claims open on a page's files, for the artist's editor read.
|
|
172
|
+
engineeringActiveOnPage: (...a) => pageNotice.engineeringActiveOnPage(...a),
|
|
168
173
|
// builders.js (the Archon roster): the roster + stale-claim + per-builder grade.
|
|
169
174
|
getBuildersRoster: (...a) => db.getBuildersRoster(...a),
|
|
170
175
|
getActiveClaimsForBuilderWithStale: (...a) => db.getActiveClaimsForBuilderWithStale(...a),
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
-- lifecycle_017_notes.sql — notes on a task or a goal (task 1004745, goal 1000095).
|
|
2
|
+
--
|
|
3
|
+
-- WHAT IT IS. A place to put information that helps scope a task or goal, or helps
|
|
4
|
+
-- finish it: a decision, an open question, a link, a risk, progress. Each note is
|
|
5
|
+
-- short, dated and has an author.
|
|
6
|
+
--
|
|
7
|
+
-- WHY `kind` IS A COLUMN. Notes are shown GROUPED BY PURPOSE (scope, decision,
|
|
8
|
+
-- question, reference, progress, risk, other) instead of as one growing list, so the
|
|
9
|
+
-- purpose has to be stored, not re-guessed on every read. `kind_source` records
|
|
10
|
+
-- whether the author chose it or the heuristic did, so a wrong guess can be told
|
|
11
|
+
-- apart from a deliberate choice and a later smarter grouper knows which to revisit.
|
|
12
|
+
--
|
|
13
|
+
-- A note is about a task OR a goal, never both, and never neither.
|
|
14
|
+
--
|
|
15
|
+
-- MODULE-OWNED and `lifecycle_`-prefixed, so fitness check 11 permits it: a NEW
|
|
16
|
+
-- table the lifecycle module owns outright (ADR 0083 §Decision #5).
|
|
17
|
+
|
|
18
|
+
CREATE TABLE IF NOT EXISTS lifecycle_notes (
|
|
19
|
+
id bigserial PRIMARY KEY,
|
|
20
|
+
task_id bigint REFERENCES tasks(id),
|
|
21
|
+
goal_id bigint REFERENCES goals(id),
|
|
22
|
+
author_id bigint NOT NULL REFERENCES builders(id),
|
|
23
|
+
body text NOT NULL,
|
|
24
|
+
kind text NOT NULL DEFAULT 'other',
|
|
25
|
+
kind_source text NOT NULL DEFAULT 'auto',
|
|
26
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
27
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
28
|
+
|
|
29
|
+
CONSTRAINT lifecycle_notes_subject_chk
|
|
30
|
+
CHECK ((task_id IS NOT NULL) <> (goal_id IS NOT NULL)),
|
|
31
|
+
CONSTRAINT lifecycle_notes_body_chk
|
|
32
|
+
CHECK (length(btrim(body)) BETWEEN 1 AND 4000),
|
|
33
|
+
CONSTRAINT lifecycle_notes_kind_chk
|
|
34
|
+
CHECK (kind IN ('scope', 'decision', 'question', 'reference', 'progress', 'risk', 'other')),
|
|
35
|
+
CONSTRAINT lifecycle_notes_kind_source_chk
|
|
36
|
+
CHECK (kind_source IN ('author', 'auto'))
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
-- The two reads: everything on one task, everything on one goal, newest first.
|
|
40
|
+
CREATE INDEX IF NOT EXISTS lifecycle_notes_task_idx
|
|
41
|
+
ON lifecycle_notes (task_id, created_at DESC) WHERE task_id IS NOT NULL;
|
|
42
|
+
CREATE INDEX IF NOT EXISTS lifecycle_notes_goal_idx
|
|
43
|
+
ON lifecycle_notes (goal_id, created_at DESC) WHERE goal_id IS NOT NULL;
|
|
44
|
+
|
|
45
|
+
INSERT INTO schema_migrations (version) VALUES ('lifecycle_017_notes')
|
|
46
|
+
ON CONFLICT (version) DO NOTHING;
|