@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
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
|
|
26
26
|
const fs = require('node:fs');
|
|
27
27
|
const path = require('node:path');
|
|
28
|
-
const
|
|
28
|
+
const os = require('node:os');
|
|
29
|
+
const { execFile, execFileSync } = require('node:child_process');
|
|
29
30
|
const { isModuleEnabled } = require('../../src/modules');
|
|
30
31
|
|
|
31
32
|
const TESTS_DIR = path.resolve(__dirname, '..', '..', 'tests');
|
|
@@ -79,10 +80,19 @@ const INTEGRATION = new Set([
|
|
|
79
80
|
// keeps the week's baseline rule, the year counts from a row's first snapshot inside
|
|
80
81
|
// it, the pin under a hide and two racing writers. The DB-free sibling is community_crowns.
|
|
81
82
|
'community_crowns_db',
|
|
83
|
+
// task 1004992: the ways a person signs in against real SQL — the UNIQUE constraints,
|
|
84
|
+
// the minted-key sequence, the GitHub-born trigger, the backfill and a real sign-up
|
|
85
|
+
// race. The DB-free sibling is account_logins.
|
|
86
|
+
'account_logins_db',
|
|
82
87
|
// task 1002983: the rising builder and the projects on the move against real SQL — the
|
|
83
88
|
// year's baseline over 30 days, a quiet row's live total, hidden and private left out.
|
|
84
89
|
// The DB-free sibling is community_movers.
|
|
85
90
|
'community_movers_db',
|
|
91
|
+
// task 1004936: the Market Watch tables, hub seed and read queries against real SQL. The
|
|
92
|
+
// CHECKs, partial unique indexes and ON CONFLICT targets are what make the seed
|
|
93
|
+
// idempotent and a resumed run unable to record an item twice. DB-free sibling:
|
|
94
|
+
// market_watch_data.
|
|
95
|
+
'market_watch_data_db',
|
|
86
96
|
// bfg, bfg_write, memory_isolation, memory_pull and setup_smoke LEFT this set under task
|
|
87
97
|
// 1004865: none of them ever needed a database. Each failed on a stale test harness (a fake
|
|
88
98
|
// server without res.fail, a stub on the wrong module, an old error shape, a renamed session
|
|
@@ -98,9 +108,16 @@ const INTEGRATION = new Set([
|
|
|
98
108
|
// never was a dedicated consumer-layout-boot workflow, whatever older notes say.
|
|
99
109
|
'consumer_layout_boot',
|
|
100
110
|
'claim_gate_open_blocker', 'claim_gate_rebase',
|
|
111
|
+
// task 1004950: what a task needs — the real feed + all three claim surfaces, per capability.
|
|
112
|
+
'task_needs_db',
|
|
113
|
+
// task 1004947: an artist's page claim never hides or refuses engineering work (real feed + claim).
|
|
114
|
+
'page_tweak_no_block',
|
|
101
115
|
// task 1004585: a hall-made goal's spec task gates its other work — the edge, the
|
|
102
116
|
// claim gate's unsatisfiedDeps read, and the ship-time auto-promotion, on real Postgres.
|
|
103
117
|
'goal_spec_task_db',
|
|
118
|
+
// task 1004984: unlinking an open task from a criterion, then the reconciler's
|
|
119
|
+
// close — the delivery guard and the sign-off recency clause are one SQL statement.
|
|
120
|
+
'task_criteria_unlink_db',
|
|
104
121
|
// task 1003457: proves the blocker-resolve auto-promotion trigger (migration
|
|
105
122
|
// 009, tightened by core_223) fires on the HALL'S OWN write path — the real
|
|
106
123
|
// buildGdsRouter() HTTP routes, not a static SQL-text pin (blocker_resolve_guard.mjs,
|
|
@@ -231,6 +248,13 @@ const INTEGRATION = new Set([
|
|
|
231
248
|
// task 1004750 (ADR 0382 D6): passkey tables from the real migration — single-use
|
|
232
249
|
// challenges, TTL sweep, unique credential id, sign-count regression.
|
|
233
250
|
'passkey_foundation_db',
|
|
251
|
+
// task 1004913 (ADR 0385 D4, ADR 0387 D2): the payout approval log from the real
|
|
252
|
+
// migration — the DATABASE refuses edits, deletes, truncation, a non-web or machine
|
|
253
|
+
// signature, a widening narrowing and anything after a revocation.
|
|
254
|
+
'payout_approvals_db',
|
|
255
|
+
// task 1004914 (ADR 0385 D8): the payout gate tables from the real migration —
|
|
256
|
+
// append-only, no silent hold, who may appeal, decide and co-sign, the gate end to end.
|
|
257
|
+
'payout_gates_db',
|
|
234
258
|
// task 1004408: migration platform_identity_028 deletes the old hub invite's
|
|
235
259
|
// pending rows before a cutoff it reads from the core_upgrades ledger. Only a real
|
|
236
260
|
// engine runs the DO block, the numeric version compare and the one-shot guard.
|
|
@@ -383,6 +407,216 @@ function reportToWorkflow(failures) {
|
|
|
383
407
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `failures=${describeFailures(failures)}\n`);
|
|
384
408
|
}
|
|
385
409
|
|
|
410
|
+
// ── Parallel lane + serial lane (task 1004926) ─────────────────────────────────────────
|
|
411
|
+
// The unit gate used to run every file one at a time (task 1001435 pinned that). Each test
|
|
412
|
+
// already runs in its OWN node process, so nothing in-process can race; what CAN collide is
|
|
413
|
+
// state OUTSIDE the processes. So the runner now runs several files at once, sized to the
|
|
414
|
+
// machine, and keeps every file that touches shared outside state in a SERIAL lane that runs
|
|
415
|
+
// alone, with nothing else running beside it.
|
|
416
|
+
//
|
|
417
|
+
// What sends a file to the serial lane: its name in SERIAL below, or one of SERIAL_MARKERS
|
|
418
|
+
// in its source — a browser launch, a fixed listen port, a fixed temp path, a write inside
|
|
419
|
+
// the repo tree. A NEW test that
|
|
420
|
+
// grows one of those lands in the serial lane on its own; no list to remember.
|
|
421
|
+
//
|
|
422
|
+
// Forcing serial: UNIT_TESTS_SERIAL=1 runs everything one at a time, as before. UNIT_TEST_JOBS=N
|
|
423
|
+
// sets the pool size. --integration and --integration-parked are always serial: their tests
|
|
424
|
+
// share one database.
|
|
425
|
+
//
|
|
426
|
+
// Output does not change shape: each file's lines print in sorted file order (a result that
|
|
427
|
+
// finishes early waits for the ones before it), and the roll-up — teardown aborts, skips,
|
|
428
|
+
// failures, the workflow report, the exit code — is built in that same order.
|
|
429
|
+
const UNIT_TESTS_SERIAL_ENV = 'UNIT_TESTS_SERIAL';
|
|
430
|
+
const UNIT_TEST_JOBS_ENV = 'UNIT_TEST_JOBS';
|
|
431
|
+
|
|
432
|
+
// Files that share state outside their own process in a way the source scan cannot see.
|
|
433
|
+
// Each entry says what it shares. Keep it short: a test that can own its state (mkdtemp,
|
|
434
|
+
// listen(0)) should be fixed to, not listed here.
|
|
435
|
+
//
|
|
436
|
+
// Found by running the pool and diffing against a serial run (task 1004926):
|
|
437
|
+
const SERIAL = new Set([
|
|
438
|
+
// asserts a handler returns in under 50 ms of wall clock; a busy pool makes that a coin toss
|
|
439
|
+
'agents_event_router',
|
|
440
|
+
]);
|
|
441
|
+
|
|
442
|
+
// Writes INSIDE the repo: a write call whose path is built from the repo root (ROOT, REPO,
|
|
443
|
+
// REPO_ROOT, repoRoot, REPO_DIR, or `new URL('../…', import.meta.url)`), inline or through a
|
|
444
|
+
// variable assigned from one. Every other test reads that tree. Found by watching the
|
|
445
|
+
// worktree during a pooled run (task 1004926): prelaunch_gate plants a line into
|
|
446
|
+
// src/bongos/prelaunch-gate.js and platform_boot, booting the real server beside it, crashed;
|
|
447
|
+
// file_map drops a probe module into modules/ and fitness_scope_roster saw it; figma_adapter
|
|
448
|
+
// writes src/ui/ and config/design-sources.json and find_untested tripped on it; repo_map
|
|
449
|
+
// dirties scripts/bongos/CLAUDE.md. A temp dir is not caught here, which is the point.
|
|
450
|
+
const REPO_ROOT_IDS = '(?:ROOT|REPO|REPO_ROOT|repoRoot|REPO_DIR)';
|
|
451
|
+
const REPO_PATH = `(?:(?:path\\.)?(?:join|resolve)\\(\\s*${REPO_ROOT_IDS}\\b|new URL\\(\\s*['"\`]\\.\\.\\/)`;
|
|
452
|
+
const FS_WRITE = '(?:writeFileSync|appendFileSync|mkdirSync|rmSync|rmdirSync|unlinkSync|renameSync|mkdtempSync|symlinkSync|fsp\\.rm|fsp\\.writeFile|fs\\.promises\\.writeFile)';
|
|
453
|
+
const WRITE_INLINE_RE = new RegExp(`${FS_WRITE}\\(\\s*${REPO_PATH}`);
|
|
454
|
+
const REPO_PATH_VAR_RE = new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*=\\s*${REPO_PATH}`, 'g');
|
|
455
|
+
function writesInsideRepo(src) {
|
|
456
|
+
if (WRITE_INLINE_RE.test(src)) return true;
|
|
457
|
+
for (const m of src.matchAll(REPO_PATH_VAR_RE)) {
|
|
458
|
+
if (new RegExp(`${FS_WRITE}\\(\\s*${m[1]}\\b`).test(src)) return true;
|
|
459
|
+
}
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// A fixed name under the shared temp dir: path.join(os.tmpdir(), 'name') NOT wrapped in
|
|
464
|
+
// mkdtemp. Every process on the machine sees the same path. A mkdtemp prefix is unique per call.
|
|
465
|
+
const TMP_JOIN_LITERAL = /\bjoin\(\s*os\.tmpdir\(\)\s*,\s*(['"])[^'"$]*\1\s*[,)]/g;
|
|
466
|
+
function usesFixedTempPath(src) {
|
|
467
|
+
for (const m of src.matchAll(TMP_JOIN_LITERAL)) {
|
|
468
|
+
const before = src.slice(Math.max(0, m.index - 40), m.index);
|
|
469
|
+
if (!/mkdtemp(?:Sync)?\(\s*(?:path\.)?$/.test(before)) return true;
|
|
470
|
+
}
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const SERIAL_MARKERS = [
|
|
475
|
+
// One Chrome per test is heavy, and the launch flake (exit 7 with only "TAP version 13")
|
|
476
|
+
// is a resource-starvation symptom; running them beside a full pool invites it.
|
|
477
|
+
{ why: 'launches a browser', test: (src) => /\b(?:puppeteer|playwright|chromium)\b|headless/i.test(src) },
|
|
478
|
+
// A literal port is shared by every process on the machine. listen(0) is not.
|
|
479
|
+
{ why: 'listens on a fixed port', test: (src) => /\.listen\(\s*[1-9]\d{1,4}\s*[,)]/.test(src) },
|
|
480
|
+
{ why: 'uses a fixed temp path', test: usesFixedTempPath },
|
|
481
|
+
{ why: 'writes inside the repo', test: writesInsideRepo },
|
|
482
|
+
];
|
|
483
|
+
|
|
484
|
+
function serialReason(name, src) {
|
|
485
|
+
if (SERIAL.has(name)) return 'listed in SERIAL';
|
|
486
|
+
for (const m of SERIAL_MARKERS) if (m.test(src)) return m.why;
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function resolveJobs(env = process.env, cpus = defaultCpus()) {
|
|
491
|
+
if (/^(1|true|yes|on)$/i.test(String(env[UNIT_TESTS_SERIAL_ENV] || '').trim())) return 1;
|
|
492
|
+
const raw = String(env[UNIT_TEST_JOBS_ENV] || '').trim();
|
|
493
|
+
if (/^[1-9]\d*$/.test(raw)) return Number(raw);
|
|
494
|
+
return Math.max(1, cpus);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function defaultCpus() {
|
|
498
|
+
return typeof os.availableParallelism === 'function' ? os.availableParallelism() : os.cpus().length;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Run one test file in its own process. Resolves (never rejects) with a normalised result,
|
|
502
|
+
// so the verdict logic below reads the same shape whatever ended the child.
|
|
503
|
+
function runFile(filePath, timeout) {
|
|
504
|
+
return new Promise((resolve) => {
|
|
505
|
+
const child = execFile(process.execPath, [filePath], {
|
|
506
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
507
|
+
timeout,
|
|
508
|
+
env: { ...process.env, NODE_ENV: 'test' },
|
|
509
|
+
windowsHide: true,
|
|
510
|
+
}, (err, stdout, stderr) => {
|
|
511
|
+
resolve({
|
|
512
|
+
ok: !err,
|
|
513
|
+
status: err ? (typeof err.code === 'number' ? err.code : null) : 0,
|
|
514
|
+
signal: err ? err.signal || null : null,
|
|
515
|
+
killed: !!(err && (err.killed || err.code === 'ETIMEDOUT')),
|
|
516
|
+
stdout: String(stdout || ''),
|
|
517
|
+
stderr: String(stderr || ''),
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
// stdin was 'ignore' (EOF at once) under execFileSync; a pipe nobody closes would leave a
|
|
521
|
+
// suite that reads stdin waiting for the whole budget.
|
|
522
|
+
if (child.stdin) child.stdin.end();
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Run `items` through `worker` with at most `jobs` in flight. Plain index hand-out: each
|
|
527
|
+
// slot takes the next index when its last one finishes.
|
|
528
|
+
async function runPool(items, jobs, worker) {
|
|
529
|
+
let next = 0;
|
|
530
|
+
const slot = async () => {
|
|
531
|
+
while (next < items.length) {
|
|
532
|
+
const i = next++;
|
|
533
|
+
await worker(items[i], i);
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
await Promise.all(Array.from({ length: Math.min(Math.max(1, jobs), items.length) }, slot));
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// One file's verdict as printable lines plus its outcome. Pure: the same result always renders
|
|
540
|
+
// the same lines, whichever lane ran it.
|
|
541
|
+
function renderResult(displayName, r, perTestTimeout) {
|
|
542
|
+
const lines = [];
|
|
543
|
+
if (r.ok) {
|
|
544
|
+
const n = countSkips(r.stdout);
|
|
545
|
+
lines.push(` ok ${displayName}${n ? ` (${n} SKIPPED — not run here)` : ''}`);
|
|
546
|
+
return { lines, outcome: 'ok', skips: n };
|
|
547
|
+
}
|
|
548
|
+
// `exit null` on its own is unreadable, and it is what a HANG looks like here: the
|
|
549
|
+
// signal that ended the process is the whole diagnosis, so say it (task 1004052 —
|
|
550
|
+
// a test that hung for the full budget reported `exit null` and nothing else).
|
|
551
|
+
const why = r.killed
|
|
552
|
+
? `TIMEOUT after ${perTestTimeout / 1000}s${r.signal ? `, ${r.signal}` : ''}`
|
|
553
|
+
: `exit ${r.status}${r.signal ? ` (${r.signal})` : ''}`;
|
|
554
|
+
const stdoutText = r.stdout.trim();
|
|
555
|
+
if (!r.killed && isCleanTeardownAbort(stdoutText, r.stderr)) {
|
|
556
|
+
lines.push(` ok ${displayName} (clean verdict, then libuv teardown abort, ${why})`);
|
|
557
|
+
return { lines, outcome: 'teardown', skips: 0 };
|
|
558
|
+
}
|
|
559
|
+
lines.push(` FAIL ${displayName} (${why})`);
|
|
560
|
+
// task 1002644: stdout used to be IGNORED, so the failing assertion (test files print
|
|
561
|
+
// their FAIL lines to stdout) was discarded. The actual failures first: the file's own
|
|
562
|
+
// FAIL lines, then its stdout tail (the last failure's assertion detail + summary).
|
|
563
|
+
// EMPTY STDOUT IS ITSELF THE CLUE: node:test buffers a file's whole report until the file
|
|
564
|
+
// finishes, so a suite that hangs mid-run emits NOTHING.
|
|
565
|
+
if (!stdoutText) {
|
|
566
|
+
lines.push(' (no output at all — node:test buffers a file\'s report until it finishes,');
|
|
567
|
+
lines.push(' so this suite did not reach the end. To find which case: run it on the same');
|
|
568
|
+
lines.push(' platform with each case writing its name to STDERR before it runs; stderr is');
|
|
569
|
+
lines.push(' unbuffered, so the last name with no completion is the one that hung.)');
|
|
570
|
+
}
|
|
571
|
+
const failLines = stdoutText ? stdoutText.split('\n').filter((l) => /\bFAIL\b/.test(l)).slice(0, 5) : [];
|
|
572
|
+
for (const l of failLines) lines.push(` ${l.trim()}`);
|
|
573
|
+
if (stdoutText) {
|
|
574
|
+
for (const l of stdoutText.split('\n').slice(-5)) lines.push(` ${l}`);
|
|
575
|
+
}
|
|
576
|
+
const stderr = r.stderr.trim().split('\n').slice(-3).join('\n ');
|
|
577
|
+
if (stderr) lines.push(` stderr: ${stderr}`);
|
|
578
|
+
return { lines, outcome: 'fail', skips: 0 };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Run every entry — the serial lane first, alone, one at a time; then the rest `jobs` at a
|
|
582
|
+
// time — print each entry's lines in entry order, and return the results in entry order.
|
|
583
|
+
async function runEntries(entries, { jobs, perTestTimeout, run = runFile, print = (l) => console.log(l) }) {
|
|
584
|
+
const results = new Array(entries.length);
|
|
585
|
+
let printed = 0;
|
|
586
|
+
const flush = () => {
|
|
587
|
+
while (printed < entries.length && results[printed]) {
|
|
588
|
+
for (const l of results[printed].lines) print(l);
|
|
589
|
+
printed++;
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
// A TIMEOUT inside the pool can be the pool's fault: a heavy suite that spawns its own
|
|
593
|
+
// children can starve beside `jobs - 1` others. So a pooled file that times out is not
|
|
594
|
+
// judged there — it is re-run ALONE at the end, with the same budget, and that run is its
|
|
595
|
+
// verdict. A file that hangs alone still fails exactly as before. Only a timeout earns the
|
|
596
|
+
// re-run: an assertion failure in the pool is a failure.
|
|
597
|
+
const rerunAlone = [];
|
|
598
|
+
const reran = new Set();
|
|
599
|
+
const work = async (idx, { pooled }) => {
|
|
600
|
+
const e = entries[idx];
|
|
601
|
+
const r = await run(e.file, perTestTimeout);
|
|
602
|
+
if (pooled && r.killed) { rerunAlone.push(idx); return; }
|
|
603
|
+
const out = renderResult(e.displayName, r, perTestTimeout);
|
|
604
|
+
if (!pooled && reran.has(idx)) out.lines.splice(1, 0, ` (timed out beside ${jobs - 1} other file(s) in the pool; this verdict is from a re-run alone)`);
|
|
605
|
+
results[idx] = out;
|
|
606
|
+
flush();
|
|
607
|
+
};
|
|
608
|
+
const all = entries.map((_, i) => i);
|
|
609
|
+
const serialIdx = jobs > 1 ? all.filter((i) => entries[i].serial) : all;
|
|
610
|
+
const poolIdx = jobs > 1 ? all.filter((i) => !entries[i].serial) : [];
|
|
611
|
+
await runPool(serialIdx, 1, (idx) => work(idx, { pooled: false }));
|
|
612
|
+
await runPool(poolIdx, jobs, (idx) => work(idx, { pooled: true }));
|
|
613
|
+
rerunAlone.sort((a, b) => a - b);
|
|
614
|
+
for (const idx of rerunAlone) reran.add(idx);
|
|
615
|
+
await runPool(rerunAlone, 1, (idx) => work(idx, { pooled: false }));
|
|
616
|
+
flush();
|
|
617
|
+
return results;
|
|
618
|
+
}
|
|
619
|
+
|
|
386
620
|
// Discover modules/<key>/tests/*.mjs. Pure-ish (reads disk; `isEnabled` is
|
|
387
621
|
// injected so this is unit-testable with zero real modules/ or DB config —
|
|
388
622
|
// mirrors the injected-dependency pattern the module loader itself uses).
|
|
@@ -495,7 +729,7 @@ function discoverModuleTests({ modulesDir = MODULES_DIR, isEnabled = isModuleEna
|
|
|
495
729
|
const INTEGRATION_PARKED = new Set([
|
|
496
730
|
]);
|
|
497
731
|
|
|
498
|
-
function main() {
|
|
732
|
+
async function main() {
|
|
499
733
|
// Two gates from one runner (task 1002221 / audit H1):
|
|
500
734
|
// default — the DB-free UNIT gate: every test file MINUS the integration set (+ known-
|
|
501
735
|
// failing / support / disabled-module). Fast, runs in a clean checkout / per PR.
|
|
@@ -571,88 +805,35 @@ function main() {
|
|
|
571
805
|
}
|
|
572
806
|
const total = files.length + moduleTests.length;
|
|
573
807
|
|
|
574
|
-
const failures = [];
|
|
575
|
-
const teardownAborts = [];
|
|
576
808
|
// A GREEN suite that skipped cases is the shape this roll-up used to hide: the suite said so
|
|
577
809
|
// on its own stdout, which was discarded on success, so the only visible line was `ok` and a
|
|
578
810
|
// session reading the roll-up could not tell exercised coverage from absent coverage.
|
|
579
|
-
const skipped = [];
|
|
580
|
-
const runOne = (displayName, filePath) => {
|
|
581
|
-
try {
|
|
582
|
-
const out = execFileSync(process.execPath, [filePath], {
|
|
583
|
-
// task 1002644: stdout used to be IGNORED, so the failing assertion (test
|
|
584
|
-
// files print their FAIL lines to stdout) was discarded and the only
|
|
585
|
-
// context shown was the last 3 STDERR lines — often stale diagnostic
|
|
586
|
-
// noise from an EARLIER, PASSING test. The live example: gate_approvals'
|
|
587
|
-
// route-catch console.error dump made every failure in that file read as
|
|
588
|
-
// a GraphQL/network outage, and the real flaky assertion was invisible.
|
|
589
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
590
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
591
|
-
timeout: perTestTimeout,
|
|
592
|
-
env: { ...process.env, NODE_ENV: 'test' },
|
|
593
|
-
});
|
|
594
|
-
const n = countSkips(out ? out.toString() : '');
|
|
595
|
-
if (n) skipped.push({ name: displayName, n });
|
|
596
|
-
console.log(` ok ${displayName}${n ? ` (${n} SKIPPED — not run here)` : ''}`);
|
|
597
|
-
} catch (err) {
|
|
598
|
-
// `exit null` on its own is unreadable, and it is what a HANG looks like here: the
|
|
599
|
-
// signal that ended the process is the whole diagnosis, so say it (task 1004052 —
|
|
600
|
-
// a test that hung for the full budget reported `exit null` and nothing else).
|
|
601
|
-
const why = err.killed
|
|
602
|
-
? `TIMEOUT after ${perTestTimeout / 1000}s${err.signal ? `, ${err.signal}` : ''}`
|
|
603
|
-
: `exit ${err.status}${err.signal ? ` (${err.signal})` : ''}`;
|
|
604
|
-
const stdoutText = (err.stdout ? err.stdout.toString() : '').trim();
|
|
605
|
-
if (!err.killed && isCleanTeardownAbort(stdoutText, err.stderr ? err.stderr.toString() : '')) {
|
|
606
|
-
console.log(` ok ${displayName} (clean verdict, then libuv teardown abort, ${why})`);
|
|
607
|
-
teardownAborts.push(displayName);
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
console.log(` FAIL ${displayName} (${why})`);
|
|
611
|
-
// The actual failures first: the file's own FAIL lines, then its stdout
|
|
612
|
-
// tail (the last failure's assertion detail + summary), both bounded.
|
|
613
|
-
// EMPTY STDOUT IS ITSELF THE CLUE, and it used to print as a blank gap. node:test
|
|
614
|
-
// buffers a file's whole report until the file finishes, so a suite that hangs
|
|
615
|
-
// mid-run emits NOTHING — which reads as "no detail available" when it actually
|
|
616
|
-
// means "it never got to the end". Name that, and name how to find the case.
|
|
617
|
-
if (!stdoutText) {
|
|
618
|
-
console.log(' (no output at all — node:test buffers a file\'s report until it finishes,');
|
|
619
|
-
console.log(' so this suite did not reach the end. To find which case: run it on the same');
|
|
620
|
-
console.log(' platform with each case writing its name to STDERR before it runs; stderr is');
|
|
621
|
-
console.log(' unbuffered, so the last name with no completion is the one that hung.)');
|
|
622
|
-
}
|
|
623
|
-
const failLines = stdoutText ? stdoutText.split('\n').filter((l) => /\bFAIL\b/.test(l)).slice(0, 5) : [];
|
|
624
|
-
for (const l of failLines) console.log(` ${l.trim()}`);
|
|
625
|
-
if (stdoutText) {
|
|
626
|
-
for (const l of stdoutText.split('\n').slice(-5)) console.log(` ${l}`);
|
|
627
|
-
}
|
|
628
|
-
const stderr = (err.stderr ? err.stderr.toString() : '').trim().split('\n').slice(-3).join('\n ');
|
|
629
|
-
if (stderr) console.log(` stderr: ${stderr}`);
|
|
630
|
-
failures.push(displayName);
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
// EXECUTION IS SERIAL, ONE PROCESS PER TEST — and that is load-bearing, not
|
|
635
|
-
// incidental (task 1001435). runOne uses execFileSync, which blocks, and these
|
|
636
|
-
// are plain sequential loops: no Promise.all, no worker pool, no concurrency
|
|
637
|
-
// anywhere in this file. Pinned by tests/unit_runner_serial.mjs.
|
|
638
811
|
//
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
// the
|
|
642
|
-
//
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
812
|
+
// Execution: see "Parallel lane + serial lane" above (task 1004926). Every file still runs in
|
|
813
|
+
// its own node process with the same budget and the same verdict rules; up to `jobs` of them
|
|
814
|
+
// run at once, and the files that share state outside their process run alone first. The
|
|
815
|
+
// integration gate is always serial — its tests share one database.
|
|
816
|
+
const entries = [
|
|
817
|
+
...files.map((name) => ({ displayName: name, file: path.join(TESTS_DIR, `${name}.mjs`) })),
|
|
818
|
+
...moduleTests.map(({ key, name, file }) => ({ displayName: `${key}/${name}`, file })),
|
|
819
|
+
];
|
|
820
|
+
const jobs = integrationMode ? 1 : resolveJobs();
|
|
821
|
+
if (jobs > 1) {
|
|
822
|
+
for (const e of entries) e.serial = serialReason(path.basename(e.file, '.mjs'), fs.readFileSync(e.file, 'utf8'));
|
|
823
|
+
const serialCount = entries.filter((e) => e.serial).length;
|
|
824
|
+
console.log(`run-unit-tests: ${jobs} at a time; ${serialCount} file(s) in the serial lane run alone first (set ${UNIT_TESTS_SERIAL_ENV}=1 to run everything one at a time)`);
|
|
825
|
+
}
|
|
826
|
+
const results = await runEntries(entries, { jobs, perTestTimeout });
|
|
827
|
+
|
|
828
|
+
const failures = [];
|
|
829
|
+
const teardownAborts = [];
|
|
830
|
+
const skipped = [];
|
|
831
|
+
results.forEach((r, i) => {
|
|
832
|
+
const name = entries[i].displayName;
|
|
833
|
+
if (r.outcome === 'fail') failures.push(name);
|
|
834
|
+
else if (r.outcome === 'teardown') teardownAborts.push(name);
|
|
835
|
+
else if (r.skips) skipped.push({ name, n: r.skips });
|
|
836
|
+
});
|
|
656
837
|
|
|
657
838
|
const label = integrationMode ? 'run-unit-tests --integration' : 'run-unit-tests';
|
|
658
839
|
console.log('');
|
|
@@ -673,6 +854,11 @@ function main() {
|
|
|
673
854
|
process.exit(1);
|
|
674
855
|
}
|
|
675
856
|
|
|
676
|
-
if (require.main === module)
|
|
857
|
+
if (require.main === module) {
|
|
858
|
+
main().catch((err) => {
|
|
859
|
+
console.error(err && err.stack ? err.stack : err);
|
|
860
|
+
process.exit(1);
|
|
861
|
+
});
|
|
862
|
+
}
|
|
677
863
|
|
|
678
|
-
module.exports = { discoverModuleTests, isCleanTeardownAbort, countSkips, describeFailures, STATUS_DESCRIPTION_MAX, INTEGRATION, INTEGRATION_PARKED, KNOWN_FAILING };
|
|
864
|
+
module.exports = { resolveJobs, serialReason, runFile, renderResult, runEntries, SERIAL, UNIT_TESTS_SERIAL_ENV, UNIT_TEST_JOBS_ENV, discoverModuleTests, isCleanTeardownAbort, countSkips, describeFailures, STATUS_DESCRIPTION_MAX, INTEGRATION, INTEGRATION_PARKED, KNOWN_FAILING };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Example spec for scripts/bongos/seed-tasks.js (task 1004979). Copy it, fill in your goal, and run with --dry-run first. criterion_ids are the goal's criterion SLUGS, never positional Cn refs (learning 1000188). A links[] body value {\"$task\": \"R03\"} becomes that task's id.",
|
|
3
|
+
"version_id": "EXAMPLE-V1",
|
|
4
|
+
"goal_id": 1000001,
|
|
5
|
+
"source": "planning-session",
|
|
6
|
+
"source_ref_prefix": "example-v1-onboarding",
|
|
7
|
+
"tasks": [
|
|
8
|
+
{
|
|
9
|
+
"ref": "R01",
|
|
10
|
+
"title": "Spike: where the welcome checklist is stored",
|
|
11
|
+
"description": "Output an ADR naming the table and who writes it.",
|
|
12
|
+
"kind": "spike",
|
|
13
|
+
"discipline": "engineer",
|
|
14
|
+
"priority": 4,
|
|
15
|
+
"est_minutes": 90,
|
|
16
|
+
"criterion_ids": ["newcomer-knows-what-to-do-next"]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"ref": "R02",
|
|
20
|
+
"title": "The welcome checklist API",
|
|
21
|
+
"description": "Build to R01's ADR. Done-when: a new builder's checklist is returned by one GET.",
|
|
22
|
+
"kind": "feature",
|
|
23
|
+
"discipline": "engineer",
|
|
24
|
+
"priority": 4,
|
|
25
|
+
"est_minutes": 180,
|
|
26
|
+
"criterion_ids": ["newcomer-knows-what-to-do-next"],
|
|
27
|
+
"deps": ["R01"]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"ref": "R03",
|
|
31
|
+
"title": "The welcome checklist on the home page",
|
|
32
|
+
"description": "Render the checklist from R02 on the newcomer's home page.",
|
|
33
|
+
"kind": "feature",
|
|
34
|
+
"discipline": "ui",
|
|
35
|
+
"priority": 3,
|
|
36
|
+
"est_minutes": 150,
|
|
37
|
+
"criterion_ids": ["newcomer-knows-what-to-do-next"],
|
|
38
|
+
"deps": ["R02"]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"ref": "R04",
|
|
42
|
+
"title": "Proof: a newcomer finds their next step without asking",
|
|
43
|
+
"description": "A brand-new builder signs in and finds what to do next unaided.",
|
|
44
|
+
"kind": "verify",
|
|
45
|
+
"discipline": "engineer",
|
|
46
|
+
"priority": 3,
|
|
47
|
+
"est_minutes": 60,
|
|
48
|
+
"criterion_ids": ["newcomer-knows-what-to-do-next"],
|
|
49
|
+
"deps": ["R03"]
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"promote": ["R01"],
|
|
53
|
+
"links": [
|
|
54
|
+
{ "method": "PATCH", "path": "/inbox/1000002", "body": { "status": "promoted", "promoted_to_task_id": { "$task": "R03" } } }
|
|
55
|
+
]
|
|
56
|
+
}
|