@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,233 @@
|
|
|
1
|
+
// tests/task_needs.mjs — a task says what it needs; each person's lists show only
|
|
2
|
+
// what they can do (task 1004950, ADR 0395). DB-free: the real feed
|
|
3
|
+
// (listClaimableTasks), the real claim gate (taskNeedsFailures) and the real
|
|
4
|
+
// capability read (resolveCapabilities) run against a stand-in pool that answers
|
|
5
|
+
// each query from fixtures — and the feed's stand-in applies the predicate the
|
|
6
|
+
// SQL names (`needs <@ capabilities`) to the parameter the code actually bound,
|
|
7
|
+
// so a feed that bound the wrong capabilities, or none, fails here.
|
|
8
|
+
// tests/task_needs_db.mjs proves the same rows against real Postgres.
|
|
9
|
+
//
|
|
10
|
+
// Run: node tests/task_needs.mjs
|
|
11
|
+
|
|
12
|
+
import { strict as assert } from 'node:assert';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const api = require('../src/module-api.js');
|
|
18
|
+
const seams = require('../src/module-seams.js');
|
|
19
|
+
const { setCapabilityGrants } = require('../src/bongos/task-needs.js');
|
|
20
|
+
const needs = { ...api.taskNeeds, setCapabilityGrants };
|
|
21
|
+
const gate = require('../modules/lifecycle/task-needs-gate.js');
|
|
22
|
+
const db = require('../modules/lifecycle/db.js');
|
|
23
|
+
|
|
24
|
+
let passed = 0, failed = 0;
|
|
25
|
+
async function t(name, fn) {
|
|
26
|
+
try { await fn(); passed++; console.log(` PASS ${name}`); }
|
|
27
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---- fixtures: four people and four tasks --------------------------------
|
|
31
|
+
const OWNER = 7, GRANTED = 8, PLAIN = 9, FOUNDER = 3;
|
|
32
|
+
const GRANTS = { [OWNER]: [], [GRANTED]: ['server'], [PLAIN]: [], [FOUNDER]: [] };
|
|
33
|
+
const TASKS = [
|
|
34
|
+
{ id: 101, needs: [] },
|
|
35
|
+
{ id: 102, needs: ['owner'] },
|
|
36
|
+
{ id: 103, needs: ['server'] },
|
|
37
|
+
{ id: 104, needs: ['owner', 'server'] },
|
|
38
|
+
];
|
|
39
|
+
const seatHolder = { id: OWNER };
|
|
40
|
+
const governmentPort = { positionsFor: async (id) => ({ held: String(id) === String(seatHolder.id) ? [{ kind: 'owner' }] : [] }) };
|
|
41
|
+
|
|
42
|
+
const subset = (a, b) => a.every((x) => b.includes(x));
|
|
43
|
+
let lastFeed = null;
|
|
44
|
+
const realQuery = api.pool.query;
|
|
45
|
+
api.pool.query = async (sql, params = []) => {
|
|
46
|
+
if (/SELECT capability_grants FROM builders/.test(sql)) return { rows: [{ capability_grants: GRANTS[params[0]] || [] }] };
|
|
47
|
+
if (/FROM builders WHERE system_role IS NULL ORDER BY id/.test(sql)) return { rows: [{ id: String(FOUNDER) }] };
|
|
48
|
+
if (/SELECT ct\.\*, sf\.fits_session_modes/.test(sql)) { // the session optimizer
|
|
49
|
+
const m = sql.match(/COALESCE\(ct\.needs, '\{\}'::text\[\]\) <@ \$(\d+)::text\[\]/);
|
|
50
|
+
lastFeed = { sql, params, capsParam: m ? params[Number(m[1]) - 1] : undefined };
|
|
51
|
+
const caps = m ? lastFeed.capsParam : ['owner', 'server'];
|
|
52
|
+
return { rows: TASKS.filter((r) => subset(r.needs, caps)).map((r) => ({ ...r, touches: [`f${r.id}`], est_minutes: 10 })) };
|
|
53
|
+
}
|
|
54
|
+
if (/FROM \(\s*SELECT t\.\*, pms_calibrated_minutes/.test(sql)) {
|
|
55
|
+
const m = sql.match(/COALESCE\(v\.needs, '\{\}'::text\[\]\) <@ \$(\d+)::text\[\]/);
|
|
56
|
+
lastFeed = { sql, params, capsParam: m ? params[Number(m[1]) - 1] : undefined };
|
|
57
|
+
if (!m) return { rows: TASKS.map((r) => ({ ...r })) }; // no clause bound → nothing narrows
|
|
58
|
+
const caps = lastFeed.capsParam;
|
|
59
|
+
const oneNeed = sql.match(/v\.needs @> ARRAY\[\$(\d+)\]::text\[\]/);
|
|
60
|
+
const none = /cardinality\(v\.needs\) = 0/.test(sql);
|
|
61
|
+
return {
|
|
62
|
+
rows: TASKS
|
|
63
|
+
.filter((r) => subset(r.needs, caps))
|
|
64
|
+
.filter((r) => (none ? r.needs.length === 0 : true))
|
|
65
|
+
.filter((r) => (oneNeed ? r.needs.includes(params[Number(oneNeed[1]) - 1]) : true))
|
|
66
|
+
.map((r) => ({ ...r })),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`unexpected query: ${sql.slice(0, 80)}`);
|
|
70
|
+
};
|
|
71
|
+
if (seams.hasProvider && seams.hasProvider('government')) throw new Error('test expects no government provider');
|
|
72
|
+
seams.registerProvider('government', governmentPort);
|
|
73
|
+
|
|
74
|
+
const feedIds = async (builderId, opts = {}) =>
|
|
75
|
+
(await db.listClaimableTasks({ builderId, ...opts })).map((r) => Number(r.id)).sort();
|
|
76
|
+
|
|
77
|
+
// ---- the vocabulary --------------------------------------------------------
|
|
78
|
+
await t('normalizeNeeds accepts arrays, comma lists and nothing; refuses unknown words', () => {
|
|
79
|
+
assert.deepEqual(needs.normalizeNeeds(['server', 'owner', 'server']).needs, ['owner', 'server']);
|
|
80
|
+
assert.deepEqual(needs.normalizeNeeds('server, owner').needs, ['owner', 'server']);
|
|
81
|
+
assert.deepEqual(needs.normalizeNeeds(null).needs, []);
|
|
82
|
+
assert.deepEqual(needs.normalizeNeeds([]).needs, []);
|
|
83
|
+
assert.deepEqual(needs.normalizeNeeds(['dns']), { ok: false, bad: ['dns'] });
|
|
84
|
+
assert.equal(needs.normalizeNeeds(5).ok, false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await t('the migration CHECK and the code list name the same needs', () => {
|
|
88
|
+
const mig = readFileSync(new URL('../migrations/core_285_task_needs.sql', import.meta.url), 'utf8');
|
|
89
|
+
const tasksCheck = mig.match(/needs <@ ARRAY\[([^\]]+)\]/)[1].match(/'([a-z]+)'/g).map((s) => s.slice(1, -1));
|
|
90
|
+
assert.deepEqual(tasksCheck, [...needs.TASK_NEEDS]);
|
|
91
|
+
const grantCheck = mig.match(/capability_grants <@ ARRAY\[([^\]]+)\]/)[1].match(/'([a-z]+)'/g).map((s) => s.slice(1, -1));
|
|
92
|
+
assert.deepEqual(grantCheck, [...needs.GRANTABLE_CAPABILITIES]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await t('only server refuses a claim; owner never does', () => {
|
|
96
|
+
assert.deepEqual(needs.claimRefusalNeeds(['owner'], []), []);
|
|
97
|
+
assert.deepEqual(needs.claimRefusalNeeds(['server'], []), ['server']);
|
|
98
|
+
assert.deepEqual(needs.claimRefusalNeeds(['owner', 'server'], ['owner']), ['server']);
|
|
99
|
+
assert.deepEqual(needs.claimRefusalNeeds(['server'], ['server']), []);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ---- capabilities are read, never asserted ---------------------------------
|
|
103
|
+
await t('the owner seat gives owner; a grant gives server; a plain builder has neither', async () => {
|
|
104
|
+
assert.deepEqual(await needs.resolveCapabilities(OWNER), ['owner']);
|
|
105
|
+
assert.deepEqual(await needs.resolveCapabilities(GRANTED), ['server']);
|
|
106
|
+
assert.deepEqual(await needs.resolveCapabilities(PLAIN), []);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await t('with no seat held, the founding builder is the owner (the UAT sign-off fallback)', async () => {
|
|
110
|
+
assert.deepEqual(await needs.resolveCapabilities(FOUNDER, { government: null }), ['owner']);
|
|
111
|
+
assert.deepEqual(await needs.resolveCapabilities(OWNER, { government: null }), []);
|
|
112
|
+
const broken = { positionsFor: async () => { throw new Error('down'); } };
|
|
113
|
+
assert.deepEqual(await needs.resolveCapabilities(FOUNDER, { government: broken }), ['owner'], 'a failed seat read falls back, never widens');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ---- the feed --------------------------------------------------------------
|
|
117
|
+
await t('a builder without server access sees no server task, and no owner task', async () => {
|
|
118
|
+
assert.deepEqual(await feedIds(PLAIN), [101]);
|
|
119
|
+
assert.deepEqual(lastFeed.capsParam, [], 'the capabilities bound into the query are the viewer\'s');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await t('the owner sees owner tasks in their own list, but not server work', async () => {
|
|
123
|
+
assert.deepEqual(await feedIds(OWNER), [101, 102]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await t('a builder granted server access sees server tasks', async () => {
|
|
127
|
+
assert.deepEqual(await feedIds(GRANTED), [101, 103]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await t('the owner holding a server grant sees everything', async () => {
|
|
131
|
+
GRANTS[OWNER] = ['server'];
|
|
132
|
+
try { assert.deepEqual(await feedIds(OWNER), [101, 102, 103, 104]); }
|
|
133
|
+
finally { GRANTS[OWNER] = []; }
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await t('the autonomous runner never holds owner, whoever it runs as', async () => {
|
|
137
|
+
assert.deepEqual(await feedIds(OWNER, { fit: 'autonomous' }), [101]);
|
|
138
|
+
assert.deepEqual(lastFeed.capsParam, []);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await t('?needs=owner is the owner\'s "waiting on you" queue', async () => {
|
|
142
|
+
assert.deepEqual(await feedIds(OWNER, { needs: 'owner' }), [102]);
|
|
143
|
+
assert.deepEqual(await feedIds(PLAIN, { needs: 'owner' }), [], 'it never widens what a viewer is shown');
|
|
144
|
+
assert.deepEqual(await feedIds(OWNER, { needs: 'none' }), [101]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await t('the board filter on GET /tasks narrows by need without the capability rule', () => {
|
|
148
|
+
const params = [];
|
|
149
|
+
assert.equal(gate.needsFilterWhere('server', '', params), 'needs @> ARRAY[$1]::text[]');
|
|
150
|
+
assert.deepEqual(params, ['server']);
|
|
151
|
+
assert.equal(gate.needsFilterWhere('none', '', []), 'cardinality(needs) = 0');
|
|
152
|
+
assert.equal(gate.needsFilterWhere(undefined, '', []), null);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ---- the session optimizer (the other recommend list) -----------------------
|
|
156
|
+
const optimizer = require('../src/bongos/optimizer.js');
|
|
157
|
+
const planIds = async (builderId, mode) =>
|
|
158
|
+
[...(await optimizer.optimizeSession({ builderId, mode, timeBudgetMin: 600, maxParallel: 10 })).recommended.map((r) => Number(r.id))].sort();
|
|
159
|
+
|
|
160
|
+
await t('the session plan applies the same rule, per capability', async () => {
|
|
161
|
+
assert.deepEqual(await planIds(PLAIN, 'active'), [101]);
|
|
162
|
+
assert.deepEqual(await planIds(OWNER, 'active'), [101, 102]);
|
|
163
|
+
assert.deepEqual(await planIds(GRANTED, 'active'), [101, 103]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await t('an autonomous session plan never holds owner, even for the owner', async () => {
|
|
167
|
+
assert.deepEqual(await planIds(OWNER, 'autonomous'), [101]);
|
|
168
|
+
assert.deepEqual(lastFeed.capsParam, []);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// ---- the claim gate --------------------------------------------------------
|
|
172
|
+
const fakeClient = {
|
|
173
|
+
query: async (sql, params) => {
|
|
174
|
+
assert.match(sql, /SELECT id, needs FROM tasks WHERE id = ANY/);
|
|
175
|
+
return { rows: TASKS.filter((r) => params[0].includes(r.id) && r.needs.length > 0) };
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
await t('a server task refuses a claimer without the grant, in plain words', async () => {
|
|
180
|
+
const f = await gate.taskNeedsFailures({ client: fakeClient, tasks: [{ id: 103 }], builderId: PLAIN });
|
|
181
|
+
assert.equal(f.length, 1);
|
|
182
|
+
assert.equal(f[0].refusal.code, 'NEEDS_SERVER_ACCESS');
|
|
183
|
+
assert.match(f[0].refusal.message, /server access/);
|
|
184
|
+
assert.doesNotMatch(f[0].refusal.message, /capabilit|grant|NEEDS_/, 'no machinery in the sentence a person reads');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await t('the same server task is claimable with the grant', async () => {
|
|
188
|
+
assert.deepEqual(await gate.taskNeedsFailures({ client: fakeClient, tasks: [{ id: 103 }, { id: 104 }], builderId: GRANTED }), []);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await t('owner work is claimable — by the owner and, if they reach it, by anyone', async () => {
|
|
192
|
+
assert.deepEqual(await gate.taskNeedsFailures({ client: fakeClient, tasks: [{ id: 102 }], builderId: OWNER }), []);
|
|
193
|
+
assert.deepEqual(await gate.taskNeedsFailures({ client: fakeClient, tasks: [{ id: 102 }], builderId: PLAIN }), []);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await t('a task needing owner AND server refuses the owner without a grant (server is the refusal)', async () => {
|
|
197
|
+
const f = await gate.taskNeedsFailures({ client: fakeClient, tasks: [{ id: 104 }], builderId: OWNER });
|
|
198
|
+
assert.deepEqual(f.map((x) => x.refusal.needs), [['server']]);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await t('all three claim surfaces call the gate', () => {
|
|
202
|
+
const src = readFileSync(new URL('../modules/lifecycle/db-claims.js', import.meta.url), 'utf8');
|
|
203
|
+
assert.equal((src.match(/await taskNeedsFailures\(/g) || []).length, 3);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ---- granting server access ------------------------------------------------
|
|
207
|
+
await t('the module doorway publishes the reads, never the grant write', () => {
|
|
208
|
+
assert.equal(typeof api.taskNeeds.resolveCapabilities, 'function');
|
|
209
|
+
assert.equal(api.taskNeeds.setCapabilityGrants, undefined, 'a module must not be able to grant server access');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await t('only the owner seat can grant server access — checked at the write itself', async () => {
|
|
213
|
+
const writes = [];
|
|
214
|
+
const grantPool = {
|
|
215
|
+
query: async (sql, params) => {
|
|
216
|
+
if (/SELECT capability_grants FROM builders/.test(sql)) return { rows: [{ capability_grants: [] }] };
|
|
217
|
+
if (/SELECT id, system_role FROM builders/.test(sql)) return { rows: [{ id: params[0], system_role: null }] };
|
|
218
|
+
if (/UPDATE builders SET capability_grants/.test(sql)) { writes.push(params); return { rows: [{ id: params[0], capability_grants: params[1] }] }; }
|
|
219
|
+
throw new Error(`unexpected: ${sql.slice(0, 60)}`);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
const deps = { pool: grantPool, government: governmentPort, getFoundingBuilder: async () => ({ id: FOUNDER }) };
|
|
223
|
+
let refused = null;
|
|
224
|
+
try { await needs.setCapabilityGrants({ actorBuilderId: PLAIN, builderId: PLAIN, grants: ['server'] }, deps); } catch (e) { refused = e; }
|
|
225
|
+
assert.equal(refused && refused.code, 'NOT_PROJECT_OWNER');
|
|
226
|
+
assert.equal(writes.length, 0, 'nothing written for a non-owner');
|
|
227
|
+
const row = await needs.setCapabilityGrants({ actorBuilderId: OWNER, builderId: PLAIN, grants: ['server', 'owner'] }, deps);
|
|
228
|
+
assert.deepEqual(row.capability_grants, ['server'], 'owner is never granted by hand');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
api.pool.query = realQuery;
|
|
232
|
+
console.log(`\ntask_needs (task 1004950): ${passed} passed, ${failed} failed`);
|
|
233
|
+
if (failed) process.exitCode = 1;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// tests/task_needs_db.mjs — task 1004950 (ADR 0395), real DB.
|
|
2
|
+
//
|
|
3
|
+
// What a task needs, against real Postgres: core_285's columns and CHECKs, the
|
|
4
|
+
// real claimable feed and the real claim gate on all three surfaces, each driven
|
|
5
|
+
// with and without each capability. tests/task_needs.mjs covers the same rules
|
|
6
|
+
// DB-free; this is the proof the SQL says what the stand-in assumed.
|
|
7
|
+
// Real-DB integration test (self-skips without a reachable Postgres; in the
|
|
8
|
+
// INTEGRATION set so the default DB-free lane never runs it).
|
|
9
|
+
//
|
|
10
|
+
// Run: DATABASE_URL=... node tests/task_needs_db.mjs
|
|
11
|
+
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
import { strict as assert } from 'node:assert';
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const { Pool } = require('pg');
|
|
16
|
+
|
|
17
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL || undefined });
|
|
18
|
+
try {
|
|
19
|
+
await pool.query('SELECT 1');
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.log(`task_needs_db.mjs SKIPPED — no reachable Postgres (${e.code || e.message}).`);
|
|
22
|
+
await pool.end().catch(() => {});
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const seams = require('../src/module-seams.js');
|
|
27
|
+
if (!seams.hasProvider('reward')) seams.registerProvider('reward', require('../modules/economy/reward.js'));
|
|
28
|
+
const db = require('../modules/lifecycle/db.js');
|
|
29
|
+
|
|
30
|
+
const VER = 'ZZZ-TN';
|
|
31
|
+
const OWNER = 90000901, GRANTED = 90000902, PLAIN = 90000903;
|
|
32
|
+
const T_NONE = 90000911, T_OWNER = 90000912, T_SERVER = 90000913;
|
|
33
|
+
const BUILDERS = [OWNER, GRANTED, PLAIN];
|
|
34
|
+
const TASKS = [T_NONE, T_OWNER, T_SERVER];
|
|
35
|
+
// The owner seat, as the government port answers it — only OWNER holds it here.
|
|
36
|
+
if (!seams.hasProvider('government')) {
|
|
37
|
+
seams.registerProvider('government', { positionsFor: async (id) => ({ held: Number(id) === OWNER ? [{ kind: 'owner' }] : [] }) });
|
|
38
|
+
}
|
|
39
|
+
const q = (sql, params) => pool.query(sql, params).then((r) => r.rows);
|
|
40
|
+
|
|
41
|
+
async function cleanup() {
|
|
42
|
+
await q(`DELETE FROM credit_log WHERE builder_id = ANY($1::bigint[]) OR task_id = ANY($2::bigint[])`, [BUILDERS, TASKS]).catch(() => {});
|
|
43
|
+
await q(`DELETE FROM claims WHERE task_id = ANY($1::bigint[])`, [TASKS]).catch(() => {});
|
|
44
|
+
await q(`DELETE FROM tasks WHERE id = ANY($1::bigint[])`, [TASKS]).catch(() => {});
|
|
45
|
+
await q(`DELETE FROM builders WHERE id = ANY($1::bigint[])`, [BUILDERS]).catch(() => {});
|
|
46
|
+
await q(`DELETE FROM versions WHERE id=$1`, [VER]).catch(() => {});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let pass = 0;
|
|
50
|
+
const ok = (cond, msg) => { assert.ok(cond, msg); console.log(` ok ${msg}`); pass++; };
|
|
51
|
+
const ours = (rows) => rows.map((r) => Number(r.id)).filter((id) => TASKS.includes(id)).sort();
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await cleanup();
|
|
55
|
+
await q(`INSERT INTO versions (id,name,status,track) VALUES ($1,'tn','building','internal') ON CONFLICT (id) DO NOTHING`, [VER]);
|
|
56
|
+
for (const [id, grants] of [[OWNER, '{}'], [GRANTED, '{server}'], [PLAIN, '{}']]) {
|
|
57
|
+
await q(`INSERT INTO builders (id,github_id,github_login,display_name,rank,capability_grants) VALUES ($1,$2,$3,$3,'metic',$4::text[])`,
|
|
58
|
+
[id, `tn-${id}`, `tn-${id}`, grants]);
|
|
59
|
+
}
|
|
60
|
+
for (const [id, needs] of [[T_NONE, '{}'], [T_OWNER, '{owner}'], [T_SERVER, '{server}']]) {
|
|
61
|
+
await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,discipline,needs) VALUES ($1,$2,$3,'ready',10,'feature','engineer',$4::text[])`,
|
|
62
|
+
[id, VER, `tn ${id}`, needs]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The columns refuse a need nobody knows.
|
|
66
|
+
let bad = null;
|
|
67
|
+
try { await q(`UPDATE tasks SET needs='{dns}' WHERE id=$1`, [T_NONE]); } catch (e) { bad = e; }
|
|
68
|
+
ok(bad && bad.code === '23514', 'tasks.needs refuses an unknown need (CHECK)');
|
|
69
|
+
bad = null;
|
|
70
|
+
try { await q(`UPDATE builders SET capability_grants='{owner}' WHERE id=$1`, [PLAIN]); } catch (e) { bad = e; }
|
|
71
|
+
ok(bad && bad.code === '23514', 'owner can never be granted by hand — it is the seat');
|
|
72
|
+
ok((await q(`SELECT needs FROM claimable_tasks WHERE id=$1`, [T_SERVER]))[0].needs.includes('server'),
|
|
73
|
+
'the claimable_tasks view carries needs (core_285 rebuilt it)');
|
|
74
|
+
|
|
75
|
+
// The feed.
|
|
76
|
+
ok(JSON.stringify(ours(await db.listClaimableTasks({ builderId: PLAIN }))) === JSON.stringify([T_NONE]),
|
|
77
|
+
'without server access or the seat: only the task that needs nothing');
|
|
78
|
+
ok(JSON.stringify(ours(await db.listClaimableTasks({ builderId: OWNER }))) === JSON.stringify([T_NONE, T_OWNER]),
|
|
79
|
+
'the owner sees owner work in their own list');
|
|
80
|
+
ok(JSON.stringify(ours(await db.listClaimableTasks({ builderId: GRANTED }))) === JSON.stringify([T_NONE, T_SERVER]),
|
|
81
|
+
'server access shows server work');
|
|
82
|
+
ok(JSON.stringify(ours(await db.listClaimableTasks({ builderId: OWNER, needs: 'owner' }))) === JSON.stringify([T_OWNER]),
|
|
83
|
+
'?needs=owner is the owner\'s waiting-on-you queue');
|
|
84
|
+
ok(ours(await db.listTasks({ versionId: VER })).length === 3, 'every task stays readable on the board');
|
|
85
|
+
ok(JSON.stringify(ours(await db.listTasks({ versionId: VER, needs: 'server' }))) === JSON.stringify([T_SERVER]),
|
|
86
|
+
'the board filters by need');
|
|
87
|
+
|
|
88
|
+
// The claim gate, three surfaces.
|
|
89
|
+
let threw = null;
|
|
90
|
+
try { await db.claimTask({ taskId: String(T_SERVER), builderId: String(PLAIN), worktreeName: 'tn-plain' }); } catch (e) { threw = e; }
|
|
91
|
+
ok(threw && threw.code === 'NEEDS_SERVER_ACCESS' && /server access/.test(threw.message), 'claimTask refuses server work without the grant, in plain words');
|
|
92
|
+
const dry = await db.validateClaimBatch({ taskIds: [T_SERVER], builderId: String(PLAIN) });
|
|
93
|
+
ok(JSON.stringify(dry).includes('NEEDS_SERVER_ACCESS'), 'the dry run greys it out too');
|
|
94
|
+
let batchThrew = null;
|
|
95
|
+
try { await db.claimTasksBatch({ taskIds: [T_SERVER], builderId: String(PLAIN), worktreePrefix: 'tn-batch' }); } catch (e) { batchThrew = e; }
|
|
96
|
+
ok((batchThrew && batchThrew.failures || []).some((f) => Number(f.task_id) === T_SERVER && f.reason === 'NEEDS_SERVER_ACCESS'),
|
|
97
|
+
'the batch path refuses it the same way');
|
|
98
|
+
const claim = await db.claimTask({ taskId: String(T_SERVER), builderId: String(GRANTED), worktreeName: 'tn-granted' });
|
|
99
|
+
ok(claim && Number(claim.task_id) === T_SERVER, 'with the grant, the claim succeeds');
|
|
100
|
+
const ownerClaim = await db.claimTask({ taskId: String(T_OWNER), builderId: String(OWNER), worktreeName: 'tn-owner' });
|
|
101
|
+
ok(ownerClaim && Number(ownerClaim.task_id) === T_OWNER, 'owner work is claimable by the owner');
|
|
102
|
+
|
|
103
|
+
console.log(`\ntask_needs_db (task 1004950): ${pass} passed, 0 failed`);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
console.error('task_needs_db.mjs FAILED:', e.stack || e.message);
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
} finally {
|
|
108
|
+
await cleanup();
|
|
109
|
+
await pool.end();
|
|
110
|
+
}
|