@bongos/core 1.23.21 → 1.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +983 -428
- package/.claude/skills/blocker-review/SKILL.md +9 -1
- package/.claude/skills/blocker-solve/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +1 -1
- package/.claude/skills/builder-reauth/SKILL.md +1 -1
- package/.claude/skills/builder-redteam/SKILL.md +1 -1
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/builder-setup/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -1
- package/.claude/skills/collab-review/SKILL.md +5 -2
- package/.claude/skills/design/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +6 -3
- package/.claude/skills/grade-recover/SKILL.md +1 -1
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/.claude/skills/owner-review/SKILL.md +1 -1
- package/.claude/skills/planning-session/SKILL.md +11 -11
- package/.claude/skills/read-session-export/SKILL.md +1 -1
- package/.claude/skills/recall/SKILL.md +1 -1
- package/.claude/skills/scan-before-install/SKILL.md +1 -1
- package/.claude/skills/session-handoff/SKILL.md +1 -1
- package/.claude/skills/strand-fix/SKILL.md +13 -0
- package/.claude/skills/worktree-clean/SKILL.md +1 -1
- package/AGENTS.md +15 -0
- package/GEMINI.md +5 -0
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +94 -0
- package/clients/bongos-client/index.cjs +94 -0
- package/clients/bongos-client/index.d.ts +154 -4
- package/clients/bongos-client/index.mjs +94 -0
- package/docs/adr/0054-session-token-reward-cost-plus.md +3 -1
- package/docs/adr/0055-server-mediated-branch-publish.md +1 -1
- package/docs/adr/0385-money-lives-with-a-processor-credits-stay-a-project-meter.md +87 -0
- package/docs/adr/0386-the-funding-wall-is-a-per-project-rule-claim-time-hold-by-default.md +37 -0
- package/docs/adr/0387-two-ledgers-credits-measure-work-money-stays-with-the-processor.md +39 -0
- package/docs/adr/0388-a-module-sale-is-split-by-the-project-the-buyer-pays-the-platform-fee.md +42 -0
- package/docs/adr/0392-ai-generation-runs-on-a-processor-held-balance-and-the-builders-own-keys.md +33 -0
- package/docs/adr/0393-a-platform-account-is-a-person-and-github-is-one-way-in.md +37 -0
- package/docs/adr/0394-market-watch-searches-through-the-messages-api-on-the-projects-key-and-runs-from-its-own-poller.md +102 -0
- package/docs/adr/0395-a-task-says-what-it-needs-and-lists-show-only-what-you-can-do.md +36 -0
- package/docs/adr/0396-google-sign-in-a-stand-in-login-and-a-browser-approved-cli-sign-in.md +41 -0
- package/docs/adr/README.md +10 -1
- package/docs/api/openapi.json +8619 -5885
- package/docs/api-reference.md +68 -9
- package/docs/architecture.md +37 -1
- package/docs/canonical-permissions.md +1 -1
- package/docs/copy-inventory.md +420 -337
- package/docs/copy-registry.json +1321 -500
- package/docs/file-map.md +4 -0
- package/docs/module-api-changelog.md +7 -0
- package/docs/onboarding/diagrams/03-drachmae-karma.mmd +3 -3
- package/docs/onboarding/diagrams/README.md +1 -1
- package/docs/onboarding/drachmae-and-karma.md +15 -15
- package/docs/onboarding/primer.md +2 -2
- package/docs/page-inventory.json +55 -6
- package/docs/page-readings.json +1185 -1011
- package/docs/recipes/google-sign-in.md +60 -0
- package/docs/recipes/ops-gotchas.md +1 -1
- package/migrations/core_282_builders_lower_login_idx.sql +33 -0
- package/migrations/core_283_claimable_ignores_page_markers.sql +53 -0
- package/migrations/core_283_payout_approvals.sql +183 -0
- package/migrations/core_284_payout_gates.sql +179 -0
- package/migrations/core_285_task_needs.sql +74 -0
- package/modules/ai-studio/CLAUDE.md +15 -0
- package/modules/ai-studio/billing.js +67 -0
- package/modules/ai-studio/context.js +29 -0
- package/modules/ai-studio/db.js +172 -0
- package/modules/ai-studio/gemini.js +75 -0
- package/modules/ai-studio/generate.js +119 -0
- package/modules/ai-studio/keys.js +73 -0
- package/modules/ai-studio/migrations/ai_studio_001_keys.sql +22 -0
- package/modules/ai-studio/migrations/ai_studio_002_ledger.sql +48 -0
- package/modules/ai-studio/migrations/ai_studio_003_requests.sql +55 -0
- package/modules/ai-studio/module.json +26 -0
- package/modules/ai-studio/openrouter.js +90 -0
- package/modules/ai-studio/payments.js +105 -0
- package/modules/ai-studio/pricing.js +92 -0
- package/modules/ai-studio/routes/ai-studio.js +252 -0
- package/modules/ai-studio/studio.js +121 -0
- package/modules/autonomy/fence.js +64 -1
- package/modules/builder-settings/builder-needs.js +27 -1
- package/modules/copy-desk/routes/copy-desk.js +5 -0
- package/modules/economy/achievements.js +22 -10
- package/modules/economy/cost-classify.js +9 -0
- package/modules/economy/cost-write-guard.js +52 -0
- package/modules/economy/cost.js +2 -1
- package/modules/economy/credits.js +18 -15
- package/modules/economy/goal-reward.js +161 -0
- package/modules/economy/reward.js +4 -0
- package/modules/economy/routes/cost.js +33 -7
- package/modules/government/catalog.js +6 -0
- package/modules/government/db.js +108 -0
- package/modules/government/docket.js +36 -0
- package/modules/government/migrations/government_035_payout_approve.sql +29 -0
- package/modules/government/pollers/board-expiry.js +108 -20
- package/modules/government/protected-surfaces.json +1 -0
- package/modules/government/routes/government.js +50 -18
- package/modules/government/routing.js +39 -5
- package/modules/government/waiting.js +74 -0
- package/modules/grading/grader-subagent.js +30 -11
- package/modules/hall-ui/public/ai-studio-lib.js +268 -0
- package/modules/hall-ui/public/ai-studio.css +193 -0
- package/modules/hall-ui/public/ai-studio.html +98 -0
- package/modules/hall-ui/public/ai-studio.js +556 -0
- package/modules/hall-ui/public/ai-studio.states.json +94 -0
- package/modules/hall-ui/public/blockers-by-person.js +156 -0
- package/modules/hall-ui/public/blockers-lib.js +15 -1
- package/modules/hall-ui/public/blockers.html +8 -0
- package/modules/hall-ui/public/blockers.js +1 -1
- package/modules/hall-ui/public/city.html +15 -15
- package/modules/hall-ui/public/claim-action.js +3 -0
- package/modules/hall-ui/public/collab.html +12 -0
- package/modules/hall-ui/public/craft-mode.js +4 -1
- package/modules/hall-ui/public/criterion-uat-ui.js +4 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/genesis/r5-life-1-primordial-sea.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-2-first-cells-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-2-first-cells.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-3-genome-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-3-genome.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-4-membrane-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-4-membrane.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-5-nervous-system-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-5-nervous-system.jpg +0 -0
- package/modules/hall-ui/public/goal-inbox.js +1 -1
- package/modules/hall-ui/public/goals-render.js +4 -0
- package/modules/hall-ui/public/goals.html +8 -0
- package/modules/hall-ui/public/gov-living.js +6 -2
- package/modules/hall-ui/public/hall-ask.css +5 -0
- package/modules/hall-ui/public/hall-ask.js +39 -3
- package/modules/hall-ui/public/notes-panel.css +20 -0
- package/modules/hall-ui/public/notes-panel.js +146 -0
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/project-settings.js +7 -5
- package/modules/hall-ui/public/settings-ai-keys.js +124 -0
- package/modules/hall-ui/public/settings.html +15 -0
- package/modules/hall-ui/public/street-deck.js +12 -3
- package/modules/hall-ui/public/studio.css +90 -8
- package/modules/hall-ui/public/studio.html +39 -10
- package/modules/hall-ui/public/studio.js +148 -18
- package/modules/hall-ui/public/task.html +2 -0
- package/modules/hall-ui/public/task.js +4 -0
- package/modules/hall-ui/public/tweak-editor.js +12 -3
- package/modules/hall-ui/public/welcome-boot.css +42 -6
- package/modules/hall-ui/public/welcome-boot.js +20 -6
- package/modules/hall-ui/public/work.js +20 -0
- package/modules/hall-ui/records/ai-studio.md +36 -0
- package/modules/hall-ui/records/blockers-people.md +24 -0
- package/modules/lifecycle/db-analytics.js +10 -3
- package/modules/lifecycle/db-claims.js +32 -4
- package/modules/lifecycle/db-goals.js +48 -18
- package/modules/lifecycle/db-shared.js +9 -0
- package/modules/lifecycle/db-tasks.js +15 -6
- package/modules/lifecycle/db-versions.js +21 -8
- package/modules/lifecycle/done-when.js +23 -1
- package/modules/lifecycle/lifecycle.js +5 -0
- package/modules/lifecycle/migrations/lifecycle_017_notes.sql +46 -0
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/notes.js +219 -0
- package/modules/lifecycle/page-notice.js +124 -0
- package/modules/lifecycle/page-tweak-artist.js +18 -1
- package/modules/lifecycle/publish-reconciler.js +67 -20
- package/modules/lifecycle/routes/claims.js +5 -2
- package/modules/lifecycle/routes/goals.js +3 -1
- package/modules/lifecycle/routes/notes.js +91 -0
- package/modules/lifecycle/routes/project-settings.js +43 -35
- package/modules/lifecycle/routes/task-route-helpers.js +21 -0
- package/modules/lifecycle/routes/task-write-routes.js +21 -3
- package/modules/lifecycle/routes/tasks.js +12 -12
- package/modules/lifecycle/ship-assurance.js +4 -1
- package/modules/lifecycle/ship-card.js +8 -2
- package/modules/lifecycle/task-needs-gate.js +70 -0
- package/modules/market-watch/CLAUDE.md +15 -0
- package/modules/market-watch/db.js +340 -0
- package/modules/market-watch/key.js +58 -0
- package/modules/market-watch/migrations/market_watch_001_tables.sql +249 -0
- package/modules/market-watch/migrations/market_watch_002_run.sql +46 -0
- package/modules/market-watch/module.json +26 -0
- package/modules/market-watch/pollers/hub-seed.js +54 -0
- package/modules/market-watch/pollers/weekly-run.js +100 -0
- package/modules/market-watch/research.js +350 -0
- package/modules/market-watch/run.js +245 -0
- package/modules/market-watch/seed/market-data.json +38 -0
- package/modules/market-watch/seed/partners-data.json +497 -0
- package/modules/market-watch/seed.js +252 -0
- package/modules/market-watch/store.js +230 -0
- package/modules/memory/routes/memory.js +14 -2
- package/modules/memory/routes/search.js +8 -2
- package/modules/onboarding/howto-moments.js +8 -1
- package/modules/platform-identity/account-logins.js +192 -0
- package/modules/platform-identity/migrations/platform_identity_034_account_logins.sql +96 -0
- package/modules/platform-identity/platform-identity.js +5 -4
- package/modules/platform-identity/routes/my-projects.js +6 -0
- package/modules/platform-identity/routes/profile.js +18 -0
- package/modules/platform-identity/routes/public-profile.js +10 -1
- package/modules/platform-identity/routes/sso.js +26 -1
- package/modules/platform-identity/tests/platform-identity.mjs +2 -1
- package/modules/provisioning/credential-preflight.js +13 -3
- package/modules/provisioning/pollers/app-liveness.js +25 -5
- package/modules/provisioning/routes/provisioning.js +1 -1
- package/modules/provisioning/starter-bundles.js +8 -2
- package/modules/public-landing/public/account.html +10 -2
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-space-nebula.jpg +0 -0
- package/modules/public-landing/public/assets/world/r3-space-nebula.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg.json +1 -1
- package/modules/public-landing/public/community.html +1 -1
- package/modules/public-landing/public/connections.html +2 -2
- package/modules/public-landing/public/guild.html +1 -1
- package/modules/public-landing/public/invitations.html +2 -2
- package/modules/public-landing/public/projects.html +5 -5
- package/modules/raise/views/nda.html +2 -0
- package/modules/specialities/db.js +32 -1
- package/modules/specialities/routes/specialities.js +10 -6
- package/modules/status-ui/public/index.html +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +286 -0
- package/scripts/bongos/autobongos-tweak-lane.js +46 -1
- package/scripts/bongos/backfill-subagent-rewards.js +2 -2
- package/scripts/bongos/claim.js +22 -1
- package/scripts/bongos/db-backup-nightly.sh +29 -0
- package/scripts/bongos/deploy-divergence.js +7 -0
- package/scripts/bongos/doc-cli-guard.js +6 -0
- package/scripts/bongos/fitness.js +3 -2
- package/scripts/bongos/gen-diagrams.js +3 -3
- package/scripts/bongos/lib.sh +4 -5
- package/scripts/bongos/migration-namespace.js +13 -1
- package/scripts/bongos/plain-cards.js +4 -0
- package/scripts/bongos/provision-github-setup.js +3 -1
- package/scripts/bongos/provision-units.js +6 -1
- package/scripts/bongos/publish-manifest.js +4 -0
- package/scripts/bongos/release.js +2 -0
- package/scripts/bongos/run-unit-tests.js +268 -82
- package/scripts/bongos/seed-tasks.example.json +56 -0
- package/scripts/bongos/seed-tasks.js +222 -0
- package/scripts/bongos/seed-verify-criteria.js +1 -2
- package/scripts/bongos/ship-finish.js +2 -0
- package/scripts/bongos/ship-resume.js +2 -0
- package/scripts/bongos/strand-lease.js +250 -0
- package/scripts/bongos/strand-watch.js +129 -12
- package/scripts/bongos/version-literal-guard.js +2 -2
- package/scripts/discord/post.js +4 -3
- package/scripts/gds/seed-tasks.js +7 -0
- package/scripts/gds/{build-bongos.js → strand-lease.js} +3 -3
- package/scripts/hall-preview/server.js +21 -0
- package/src/bongos/auth-config.js +29 -1
- package/src/bongos/auth-discord.js +3 -3
- package/src/bongos/auth-github.js +3 -6
- package/src/bongos/auth-google.js +217 -0
- package/src/bongos/device-approval.js +140 -0
- package/src/bongos/llm-pricing.js +17 -1
- package/src/bongos/metrics.js +48 -2
- package/src/bongos/middleware/rate-limit.js +30 -11
- package/src/bongos/module-scope-map.js +16 -1
- package/src/bongos/optimizer.js +15 -1
- package/src/bongos/payout-approvals.js +342 -0
- package/src/bongos/payout-gates.js +364 -0
- package/src/bongos/prelaunch-gate.js +7 -9
- package/src/bongos/prelaunch-switch.js +20 -0
- package/src/bongos/route-rank-check.js +3 -0
- package/src/bongos/routes/auth-google.js +246 -0
- package/src/bongos/routes/auth.js +38 -1
- package/src/bongos/routes/backup.js +33 -9
- package/src/bongos/routes/builders.js +57 -30
- package/src/bongos/routes/github.js +9 -0
- package/src/bongos/routes/me.js +18 -0
- package/src/bongos/routes/my-payouts.js +133 -0
- package/src/bongos/routes/payout-approvals.js +312 -0
- package/src/bongos/routes/payout-gates.js +179 -0
- package/src/bongos/routes.js +15 -1
- package/src/bongos/secret-box.js +18 -3
- package/src/bongos/serve-internal.js +47 -19
- package/src/bongos/sign-in-pages.js +138 -0
- package/src/bongos/task-needs.js +146 -0
- package/src/bongos-downloads.js +18 -1
- package/src/instance-config.js +28 -0
- package/src/module-api.js +14 -1
- package/src/platform-server.js +27 -1
- package/tests/account_logins.mjs +326 -0
- package/tests/account_logins_db.mjs +161 -0
- package/tests/ai_studio_billing.mjs +253 -0
- package/tests/ai_studio_generate.mjs +282 -0
- package/tests/ai_studio_keys.mjs +159 -0
- package/tests/ai_studio_requests.mjs +319 -0
- package/tests/auth_backchannel_logout_route.mjs +201 -0
- package/tests/auto_satisfy_criteria.mjs +10 -1
- package/tests/autobongos_tweak_lane.mjs +144 -0
- package/tests/autobongos_tweak_pin_identity.mjs +137 -0
- package/tests/backfill_subagent_rewards.mjs +17 -1
- package/tests/backup_trigger_lock.mjs +119 -0
- package/tests/batched_writes_parity.mjs +340 -0
- package/tests/blockers_people.mjs +198 -0
- package/tests/bounded_rate_maps.mjs +184 -0
- package/tests/claimable_blocker_status.mjs +1 -1
- package/tests/claims_batch_routes.mjs +421 -0
- package/tests/conductor_main_e2e.mjs +4 -0
- package/tests/config_read_cache.mjs +171 -0
- package/tests/copy_desk_page_editor.mjs +12 -0
- package/tests/cost_write_guard.mjs +200 -0
- package/tests/craft_achievements.mjs +33 -10
- package/tests/criterion_goal_attach.mjs +1 -1
- package/tests/db_backup_nightly.mjs +73 -1
- package/tests/deploy_divergence_line.mjs +7 -0
- package/tests/effective_visibility_predicate.mjs +12 -0
- package/tests/fitness.mjs +19 -0
- package/tests/fixtures/per-row-loops-legacy.cjs +245 -0
- package/tests/fixtures/seed-tasks/bongos-v1-idea-routing.json +256 -0
- package/tests/fixtures/seed-tasks/bv1-profiles.json +592 -0
- package/tests/fixtures/seed-tasks/bv2-ideator-surface.json +373 -0
- package/tests/fixtures/seed-tasks/fake-api.mjs +80 -0
- package/tests/github_one_trip.mjs +34 -0
- package/tests/github_repo_visibility.mjs +20 -0
- package/tests/goal_achievement.mjs +30 -1
- package/tests/goal_achievement_reward.mjs +205 -0
- package/tests/goal_archive_disposition.mjs +5 -5
- package/tests/goal_sort_staleness.mjs +2 -1
- package/tests/goal_tier.mjs +11 -1
- package/tests/google_fonts_nonblocking.mjs +84 -0
- package/tests/google_sign_in.mjs +401 -0
- package/tests/government_board_expiry.mjs +94 -0
- package/tests/government_protected_surfaces.mjs +1 -1
- package/tests/government_routes.mjs +146 -23
- package/tests/grader.mjs +31 -0
- package/tests/hall_ai_studio.mjs +347 -0
- package/tests/hall_city.mjs +8 -0
- package/tests/hall_page_gate_map.mjs +3 -0
- package/tests/hall_studio_home.mjs +61 -2
- package/tests/home_attention_cards.mjs +1 -1
- package/tests/hook_shell_tools.mjs +8 -0
- package/tests/hooks_drain.mjs +1 -1
- package/tests/host_topology_skips.mjs +2 -0
- package/tests/howto_moments.mjs +17 -0
- package/tests/human_signal.mjs +141 -0
- package/tests/install_script_reader.mjs +51 -0
- package/tests/integration_fixture_ids.mjs +82 -0
- package/tests/invite_expiry_db.mjs +13 -1
- package/tests/lib_sh_resolution.mjs +0 -4
- package/tests/market_watch_data.mjs +244 -0
- package/tests/market_watch_data_db.mjs +308 -0
- package/tests/market_watch_module.mjs +114 -0
- package/tests/market_watch_run.mjs +503 -0
- package/tests/metrics.mjs +124 -0
- package/tests/migration_namespace.mjs +14 -0
- package/tests/module_api.mjs +2 -0
- package/tests/module_loader.mjs +1 -1
- package/tests/module_skills_hook.mjs +69 -0
- package/tests/notes.mjs +182 -0
- package/tests/own_scope_guard.mjs +5 -0
- package/tests/page_tweak_marker_sql.mjs +76 -0
- package/tests/page_tweak_no_block.mjs +135 -0
- package/tests/page_tweak_notice.mjs +103 -0
- package/tests/payout_approvals.mjs +253 -0
- package/tests/payout_approvals_db.mjs +106 -0
- package/tests/payout_gates.mjs +322 -0
- package/tests/payout_gates_db.mjs +113 -0
- package/tests/planning_session_skill.mjs +13 -3
- package/tests/project_settings_page.mjs +29 -5
- package/tests/project_settings_spend_cap_route.mjs +235 -0
- package/tests/promotion_sweep.mjs +29 -1
- package/tests/provision.mjs +3 -2
- package/tests/provisioning_app_status.mjs +18 -0
- package/tests/provisioning_credential_preflight.mjs +53 -0
- package/tests/provisioning_env_manifest_edit_delete.mjs +223 -0
- package/tests/public_landing_projects.mjs +37 -0
- package/tests/publish_reconciler_overlap.mjs +60 -0
- package/tests/rank_change_broadcast_port.mjs +151 -0
- package/tests/seed_tasks.mjs +176 -0
- package/tests/seed_verify_criteria.mjs +2 -6
- package/tests/session_start_freshness.mjs +66 -1
- package/tests/shell_hooks.mjs +209 -0
- package/tests/ship_assurance.mjs +28 -0
- package/tests/ship_card.mjs +20 -5
- package/tests/site_classified_once.mjs +142 -0
- package/tests/speciality_db_shape.mjs +23 -0
- package/tests/speciality_session_skills.mjs +48 -2
- package/tests/strand_lease.mjs +249 -0
- package/tests/strand_watch.mjs +58 -0
- package/tests/task_criteria_routes.mjs +275 -0
- package/tests/task_criteria_unlink_db.mjs +123 -0
- package/tests/task_needs.mjs +233 -0
- package/tests/task_needs_db.mjs +110 -0
- package/tests/unit_runner_serial.mjs +266 -43
- package/tests/version_close_route.mjs +1 -1
- package/tests/version_literals.mjs +5 -7
- package/tests/welcome_boot.mjs +16 -5
- package/tests/welcome_boot_browser.mjs +36 -4
- package/docs/recipes/bongos-cli-release.md +0 -66
- package/scripts/bongos/backfill-disciplines.js +0 -144
- package/scripts/bongos/backfill-session-earnings.js +0 -123
- package/scripts/bongos/backfill-session-types.js +0 -87
- package/scripts/bongos/build-bongos.js +0 -94
- package/scripts/bongos/cleanup-orphans.js +0 -74
- package/scripts/bongos/seed-bongos-v1-idea-routing-tasks.js +0 -152
- package/scripts/bongos/seed-bv1-profiles-tasks.js +0 -135
- package/scripts/bongos/seed-bv2-ideator-surface-tasks.js +0 -390
- package/scripts/bongos/smoke-batch-claim.sh +0 -238
- package/scripts/bongos/smoke-batch-release.sh +0 -316
- package/scripts/bongos/smoke-claim-consistency.sh +0 -222
- package/scripts/bongos/smoke-dependencies.sh +0 -143
- package/scripts/gds/backfill-disciplines.js +0 -7
- package/scripts/gds/backfill-session-earnings.js +0 -7
- package/scripts/gds/backfill-session-types.js +0 -7
- package/scripts/gds/cleanup-orphans.js +0 -7
- package/scripts/gds/seed-bongos-v1-idea-routing-tasks.js +0 -6
- package/scripts/gds/seed-bv1-profiles-tasks.js +0 -6
- package/scripts/gds/seed-bv2-ideator-surface-tasks.js +0 -6
- package/scripts/gds/smoke-batch-claim.sh +0 -8
- package/scripts/gds/smoke-batch-release.sh +0 -8
- package/scripts/gds/smoke-claim-consistency.sh +0 -8
- package/scripts/gds/smoke-dependencies.sh +0 -8
- package/tests/smoke_dependencies_witness.mjs +0 -46
|
@@ -129,7 +129,7 @@ function heldClock(h, m) {
|
|
|
129
129
|
return Held;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
function bootStudio({ answers = {}, signedIn = true, at = null, body = BODY, rank = 'metic', queue = null } = {}) {
|
|
132
|
+
function bootStudio({ answers = {}, signedIn = true, at = null, body = BODY, rank = 'metic', queue = null, search = '' } = {}) {
|
|
133
133
|
const root = augment(makeEl('body', {}));
|
|
134
134
|
root.innerHTML = body;
|
|
135
135
|
const byId = (id) => walk(root, (c) => (c.id === id ? c : null));
|
|
@@ -166,7 +166,7 @@ function bootStudio({ answers = {}, signedIn = true, at = null, body = BODY, ran
|
|
|
166
166
|
const windowObj = {
|
|
167
167
|
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
168
168
|
BongosClient: { createClient: () => client },
|
|
169
|
-
location: { host: 'builders.cloudbongos.com', hostname: 'builders.cloudbongos.com', protocol: 'https:', search
|
|
169
|
+
location: { host: 'builders.cloudbongos.com', hostname: 'builders.cloudbongos.com', protocol: 'https:', search, pathname: '/studio', assign: (u) => { out.navigated = u; } },
|
|
170
170
|
localStorage: { getItem: () => null, setItem() {} },
|
|
171
171
|
addEventListener() {},
|
|
172
172
|
scrollTo() {},
|
|
@@ -702,6 +702,65 @@ try {
|
|
|
702
702
|
assert.match(down.el('st-panel-body').innerHTML, /could not be read just now/);
|
|
703
703
|
});
|
|
704
704
|
|
|
705
|
+
await test('the full-page reader: a doc at ?note=N and the list at ?view=site, in place of the room, never a dialog', async () => {
|
|
706
|
+
const note = {
|
|
707
|
+
id: 31, page_title: 'Projects', page_id: 'site:projects', author_name: 'Ana', status: 'filed', submitted_at: day(1),
|
|
708
|
+
body_md: ['# Map', 'The labels <b>overlap</b>.', '- one', '- two', '', ''].join('\n'),
|
|
709
|
+
};
|
|
710
|
+
const s = bootStudio({ search: '?note=31', answers: { ...HOME, 'GET /page-notes/31': ok({ note, ideas: [{ id: 7, title: 'Fix the map' }] }) } });
|
|
711
|
+
await s.settle();
|
|
712
|
+
assert.equal(s.el('st-doc').hidden, false);
|
|
713
|
+
assert.ok(s.html.classList.contains('st-reading'), 'the room steps aside');
|
|
714
|
+
assert.equal(s.el('st-doc-title').textContent, 'Projects');
|
|
715
|
+
const b = s.el('st-doc-body').innerHTML;
|
|
716
|
+
assert.match(b, /Ana · /);
|
|
717
|
+
assert.match(b, /<h3>Map<\/h3>/);
|
|
718
|
+
assert.match(b, /<li>one<\/li><li>two<\/li>/);
|
|
719
|
+
assert.match(b, /The labels <b>overlap<\/b>/, 'the doc is escaped');
|
|
720
|
+
assert.match(b, /<img class="st-doc__img" src="\/api\/bongos\/page-notes\/images\/4"/, 'its own pictures are drawn');
|
|
721
|
+
assert.doesNotMatch(b, /<img[^>]*example\.com/, 'any other picture stays text');
|
|
722
|
+
assert.match(b, /It became 1 idea/);
|
|
723
|
+
assert.match(b, /href="\/#\/idea\/7"/);
|
|
724
|
+
|
|
725
|
+
const gone = bootStudio({ search: '?note=99', answers: HOME });
|
|
726
|
+
await gone.settle();
|
|
727
|
+
assert.match(gone.el('st-doc-body').innerHTML, /could not be opened/);
|
|
728
|
+
|
|
729
|
+
const rows = [{ id: 31, page_id: 'site:projects', page_path: '/projects', page_title: 'Projects', status: 'filed', idea_count: 2, submitted_at: day(1), author_name: 'Ana', excerpt: 'Map labels overlap.', link: '/builders/studio?note=31' }];
|
|
730
|
+
const list = bootStudio({ search: '?view=site', answers: { ...HOME, 'GET /page-notes/site': ok({ notes: rows }) } });
|
|
731
|
+
await list.settle();
|
|
732
|
+
assert.equal(list.el('st-doc-title').textContent, 'From the live site');
|
|
733
|
+
assert.match(list.el('st-doc-body').innerHTML, /st-doc-list__row" href="\/builders\/studio\?note=31"/);
|
|
734
|
+
|
|
735
|
+
const plain = bootStudio({ answers: HOME });
|
|
736
|
+
await plain.settle();
|
|
737
|
+
assert.equal(plain.el('st-doc').hidden, true, 'no link, no reader');
|
|
738
|
+
assert.ok(!plain.html.classList.contains('st-reading'));
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
await test('the overview: the two summary rows sit in the quiet Overview, the three things to do in the actions', () => {
|
|
742
|
+
const nc = HTML.replace(/<!--[\s\S]*?-->/g, '');
|
|
743
|
+
const actions = /<div class="st-actions">([\s\S]*?)\n <\/div>/.exec(nc)[1];
|
|
744
|
+
const glance = /<div class="st-glance"[^>]*>([\s\S]*?)\n <\/div>/.exec(nc)[1];
|
|
745
|
+
for (const id of ['st-resume', 'st-flow', 'studio-approvals']) assert.match(actions, new RegExp(`id="${id}"`), `${id} is an action`);
|
|
746
|
+
for (const id of ['st-status', 'st-site']) {
|
|
747
|
+
assert.match(glance, new RegExp(`id="${id}"`), `${id} is an overview row`);
|
|
748
|
+
assert.doesNotMatch(actions, new RegExp(`id="${id}"`), `${id} is not among the actions`);
|
|
749
|
+
}
|
|
750
|
+
const css = fs.readFileSync(new URL('../modules/hall-ui/public/studio.css', import.meta.url), 'utf8');
|
|
751
|
+
assert.match(css, /\.st-resume::after,\s*\.st-action::after \{/, 'Resume and the doors share the arrow');
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
await test('the reader stands in for the side sheet: craft-mode keeps it shut on the Studio, and a signed-out link says why', async () => {
|
|
755
|
+
const craft = fs.readFileSync(new URL('../modules/hall-ui/public/craft-mode.js', import.meta.url), 'utf8');
|
|
756
|
+
assert.match(craft, /getAttribute\('data-page'\) === 'studio'/);
|
|
757
|
+
assert.match(HTML, /<body[^>]*data-page="studio"/, 'the marker craft-mode reads is on the page');
|
|
758
|
+
const out = bootStudio({ search: '?note=31', signedIn: false, answers: {} });
|
|
759
|
+
await out.settle();
|
|
760
|
+
assert.match(out.el('st-doc-body').innerHTML, /Sign in to read page reviews/);
|
|
761
|
+
assert.equal(out.requests.some((r) => r.url.includes('/page-notes/')), false, 'nothing is asked for while signed out');
|
|
762
|
+
});
|
|
763
|
+
|
|
705
764
|
await test('the markup: one h1 (the greeting), the tally and four doors, the libs before the page, the full-screen switch', () => {
|
|
706
765
|
const nc = HTML.replace(/<!--[\s\S]*?-->/g, '');
|
|
707
766
|
assert.equal((nc.match(/<h1\b/g) || []).length, 1);
|
|
@@ -35,7 +35,7 @@ const STUBS = `
|
|
|
35
35
|
return d ? { ok: true, status: 200, data: d } : { ok: false, status: 404, data: null };
|
|
36
36
|
} }) };
|
|
37
37
|
window.OTBKit = { esc: (s) => String(s), fmtAbsDate: () => 'Oct 1' };
|
|
38
|
-
window.OTBBlockersLib = { blockerFacts: (b) => ({ titleHtml: '<a class="ginbox__goal">' + b.title + '</a>', factsHtml: '', noteHtml: '' }) };
|
|
38
|
+
window.OTBBlockersLib = { blockerFacts: (b) => ({ titleHtml: '<a class="ginbox__goal">' + b.title + '</a>', factsHtml: '', noteHtml: '' }), humanKind: () => 'task' };
|
|
39
39
|
window.OTB = {
|
|
40
40
|
escapeHtml: (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'),
|
|
41
41
|
toast() {}, rankLabel: (r) => r,
|
|
@@ -57,13 +57,19 @@ function entries() {
|
|
|
57
57
|
return out;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// A hook may ALSO have an entry for the file-writing tools only (secret-gate does,
|
|
61
|
+
// since task 1004931 moved its shell half into the shell-hooks.js dispatcher). What
|
|
62
|
+
// must never exist is an entry naming SOME shell tools but not all — the bug above.
|
|
60
63
|
test('every shell-reading hook is registered, and its matcher names every shell tool', () => {
|
|
61
64
|
const all = entries();
|
|
62
65
|
for (const hook of SHELL_HOOKS) {
|
|
63
66
|
const found = all.filter((e) => e.command.includes(hook));
|
|
64
67
|
assert.ok(found.length > 0, `${hook} is not registered in settings.json at all`);
|
|
68
|
+
let coversShell = false;
|
|
65
69
|
for (const e of found) {
|
|
66
70
|
const named = e.matcher.split('|').map((s) => s.trim());
|
|
71
|
+
const shellNamed = SHELL_TOOLS.filter((tool) => named.includes(tool));
|
|
72
|
+
if (shellNamed.length === 0) continue; // a file-writing-tools entry
|
|
67
73
|
for (const tool of SHELL_TOOLS) {
|
|
68
74
|
assert.ok(
|
|
69
75
|
named.includes(tool),
|
|
@@ -71,7 +77,9 @@ test('every shell-reading hook is registered, and its matcher names every shell
|
|
|
71
77
|
'that shell tool reaches the hook not at all, silently',
|
|
72
78
|
);
|
|
73
79
|
}
|
|
80
|
+
coversShell = true;
|
|
74
81
|
}
|
|
82
|
+
assert.ok(coversShell, `${hook} has no entry whose matcher names the shell tools`);
|
|
75
83
|
}
|
|
76
84
|
});
|
|
77
85
|
|
package/tests/hooks_drain.mjs
CHANGED
|
@@ -20,7 +20,7 @@ import path from 'node:path';
|
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
21
|
|
|
22
22
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
|
-
const HOOKS = ['recall-hints', 'rank-change-greeting', 'conductor', 'pull-memory', 'edit-hints', 'stuck-detector'].map((n) => path.join(ROOT, '.claude', 'hooks', `${n}.js`));
|
|
23
|
+
const HOOKS = ['recall-hints', 'module-skills', 'rank-change-greeting', 'conductor', 'pull-memory', 'edit-hints', 'stuck-detector'].map((n) => path.join(ROOT, '.claude', 'hooks', `${n}.js`));
|
|
24
24
|
// The SessionStart/SessionEnd scripts that did network work and then process.exit()ed — 59 recorded
|
|
25
25
|
// UV_HANDLE_CLOSING aborts in three days (task 1003548). Same contract: exit 0 on their own, no abort.
|
|
26
26
|
// session-digest requires `pg` at load (via db-kernel), so it can only be exercised where node_modules
|
|
@@ -40,6 +40,7 @@ const ALLOWED = {
|
|
|
40
40
|
first_run_engine_check: '.claude/hooks/session-start.js (package-excluded)',
|
|
41
41
|
hook_core_resolve: '.claude/hooks/_core-resolve.js (package-excluded)',
|
|
42
42
|
'lifecycle-card': '.claude/hooks/lifecycle-card.js (package-excluded)',
|
|
43
|
+
module_skills_hook: '.claude/hooks/module-skills.js (package-excluded)',
|
|
43
44
|
pre_compact: '.claude/hooks/pre-compact.js (package-excluded)',
|
|
44
45
|
preview_server: 'src/preview-server.js (game module, not in the core)',
|
|
45
46
|
recall_hints: '.claude/hooks/recall-hints.js (package-excluded)',
|
|
@@ -47,6 +48,7 @@ const ALLOWED = {
|
|
|
47
48
|
run_routine_two_root: '.claude/hooks/_core-resolve.js (package-excluded)',
|
|
48
49
|
'session-cards': '.claude/hooks/session-cards.js (package-excluded)',
|
|
49
50
|
session_start_freshness: '.claude/hooks/session-start.js (package-excluded)',
|
|
51
|
+
shell_hooks: '.claude/hooks/shell-hooks.js (package-excluded)',
|
|
50
52
|
stop_refcheck: '.claude/hooks/stop-refcheck.js (package-excluded; the task-1002234 precedent)',
|
|
51
53
|
stuck_detector: '.claude/hooks/stuck-strikes.js (package-excluded)',
|
|
52
54
|
worktree_guard: '.claude/hooks/worktree-guard.js (package-excluded)',
|
package/tests/howto_moments.mjs
CHANGED
|
@@ -119,6 +119,23 @@ test('ordinary prompts are not guide requests', () => {
|
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
// Task 1004744: a pasted handoff quoting "skip the tutorial" made the Conductor tell
|
|
123
|
+
// the AI to run `npx bongos howto skip` unasked. Only a prompt that IS the request counts.
|
|
124
|
+
test('a long or multi-line prompt that merely quotes a request is not one', () => {
|
|
125
|
+
const handoff = [
|
|
126
|
+
'Continue the onboarding chain from task 1004636.',
|
|
127
|
+
'Context: the owner said "skip the tutorial" is a valid phrase the guide must accept.',
|
|
128
|
+
'Next: wire the hall card.',
|
|
129
|
+
].join('\n');
|
|
130
|
+
assert.equal(m.guideRequest(handoff), null, 'multi-line handoff');
|
|
131
|
+
const oneLong = 'Reviewing the copy for the how-to card: the button reads "skip the tutorial", which matches what the guide accepts, so leave it.';
|
|
132
|
+
assert.ok(oneLong.length > 100);
|
|
133
|
+
assert.equal(m.guideRequest(oneLong), null, 'one long line');
|
|
134
|
+
assert.equal(m.guideRequest(' skip the tutorial \n'), 'skip', 'surrounding whitespace is still the request');
|
|
135
|
+
const a = howtoAdvisory({ howto: { read: [] }, prompt: handoff, firstPrompt: false, activeClaims: [] });
|
|
136
|
+
assert.ok(!a || !String(a.id).startsWith('howto-request:'), 'the Conductor gives no howto command for the handoff');
|
|
137
|
+
});
|
|
138
|
+
|
|
122
139
|
test('each request tells the AI the command that changes the stored state', () => {
|
|
123
140
|
for (const kind of ['show', 'next', 'skip', 'reset']) assert.match(m.renderRequestForAi(kind), new RegExp(`npx bongos howto ${kind}`));
|
|
124
141
|
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// tests/human_signal.mjs — HUMAN TASK / HUMAN DECISION, the one human signal
|
|
2
|
+
// (task 1004922, owner 2026-10-28).
|
|
3
|
+
//
|
|
4
|
+
// Whatever waits on a person says so with a person, the same two words in the
|
|
5
|
+
// hall (a badge drawn by hall-ask.js) and in every AI chat (a marker line the
|
|
6
|
+
// Conductor teaches Claude and AGENTS.md/GEMINI.md teach every other model).
|
|
7
|
+
// The two halves must say the same words, so this file holds them together.
|
|
8
|
+
//
|
|
9
|
+
// Run: node tests/human_signal.mjs
|
|
10
|
+
|
|
11
|
+
import { strict as assert } from 'node:assert';
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
19
|
+
const ask = require('../modules/hall-ui/public/hall-ask.js');
|
|
20
|
+
const conductor = require('../.claude/hooks/conductor.js');
|
|
21
|
+
const blockersLib = require('../modules/hall-ui/public/blockers-lib.js');
|
|
22
|
+
const read = (p) => fs.readFileSync(path.join(ROOT, p), 'utf8');
|
|
23
|
+
|
|
24
|
+
let passed = 0;
|
|
25
|
+
let failed = 0;
|
|
26
|
+
function t(name, fn) {
|
|
27
|
+
try { fn(); console.log(` PASS ${name}`); passed += 1; }
|
|
28
|
+
catch (e) { console.log(` FAIL ${name}\n ${e.message}`); failed += 1; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const TASK = '🙋 **HUMAN TASK:**';
|
|
32
|
+
const DECISION = '🔀 **HUMAN DECISION:**';
|
|
33
|
+
|
|
34
|
+
t('an ask card is a HUMAN DECISION unless told otherwise', () => {
|
|
35
|
+
const h = ask.html({ q: 'Let Ana join?', where: 'A goal', opts: [{ label: 'Let them in' }] });
|
|
36
|
+
assert.match(h, /hhuman--decision/);
|
|
37
|
+
assert.match(h, />HUMAN DECISION</);
|
|
38
|
+
assert.doesNotMatch(h, /HUMAN TASK/);
|
|
39
|
+
assert.match(h, /A goal/, 'the where line still reads beside the badge');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
t('a card for something a person must DO is a HUMAN TASK', () => {
|
|
43
|
+
const h = ask.html({ q: 'Blocker', human: 'task', opts: [{ label: 'Resolve it' }] });
|
|
44
|
+
assert.match(h, /hhuman--task/);
|
|
45
|
+
assert.match(h, />HUMAN TASK</);
|
|
46
|
+
assert.doesNotMatch(h, /HUMAN DECISION/);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
t('a card with no where line carries the badge on its own line, not as an eyebrow', () => {
|
|
50
|
+
const h = ask.html({ q: 'Blocker', human: 'task' });
|
|
51
|
+
assert.match(h, /class="hask__human"><span class="hhuman hhuman--task"/);
|
|
52
|
+
assert.doesNotMatch(h, /hask__where/, 'the Blockers page dropped its repeated eyebrow (task 1004824)');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
t('human:false drops the badge and keeps the old dot', () => {
|
|
56
|
+
const h = ask.html({ q: 'x', where: 'w', human: false });
|
|
57
|
+
assert.doesNotMatch(h, /hhuman/);
|
|
58
|
+
assert.match(h, /hask__dot/);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
t('an unknown kind draws nothing rather than a wrong signal', () => {
|
|
62
|
+
assert.equal(ask.human('maybe'), '');
|
|
63
|
+
assert.equal(ask.human(undefined), '');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
t('the badge is a drawn person, never an emoji (voice V4)', () => {
|
|
67
|
+
for (const k of ['task', 'decision']) {
|
|
68
|
+
const b = ask.human(k);
|
|
69
|
+
assert.match(b, /<svg[^>]*aria-hidden="true"/);
|
|
70
|
+
assert.doesNotMatch(b, /\p{Extended_Pictographic}/u, `${k} badge carries an emoji`);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
t('an Awaiting UAT row is a HUMAN TASK', () => {
|
|
75
|
+
assert.match(read('modules/hall-ui/public/criterion-uat-ui.js'), /awaiting_uat' && ask \? ask\.human\('task'\)/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// task 1004946: a blocker's own stored ask says whether it is a task or a choice.
|
|
79
|
+
// The six asks open on the live hub on 2026-10-28, cut to their option keys.
|
|
80
|
+
const opts = (...keys) => ({ ask: { options: keys.map((key) => ({ key, label: key })) } });
|
|
81
|
+
const LIVE = [
|
|
82
|
+
['1000193 V2 or V3 or hold', opts('v2', 'v3', 'hold'), 'decision'],
|
|
83
|
+
['1000194 bucket or copy or leave', opts('bucket', 'copy', 'none'), 'decision'],
|
|
84
|
+
['1000195 set it up / not yet', opts('yes', 'later'), 'task'],
|
|
85
|
+
['1000196 do it / not yet', opts('do', 'later'), 'task'],
|
|
86
|
+
['1000197 restart / not yet', opts('yes', 'later'), 'task'],
|
|
87
|
+
['1000198 scope or split', opts('scope', 'split'), 'decision'],
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
t('a blocker with two or more real options is a HUMAN DECISION; do-it-or-not-yet is a HUMAN TASK', () => {
|
|
91
|
+
for (const [name, b, want] of LIVE) assert.equal(blockersLib.humanKind(b), want, name);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
t('a blocker with no ask, or only deferrals, is a HUMAN TASK', () => {
|
|
95
|
+
assert.equal(blockersLib.humanKind({}), 'task');
|
|
96
|
+
assert.equal(blockersLib.humanKind({ ask: null }), 'task');
|
|
97
|
+
assert.equal(blockersLib.humanKind(null), 'task');
|
|
98
|
+
assert.equal(blockersLib.humanKind(opts('do')), 'task');
|
|
99
|
+
assert.equal(blockersLib.humanKind(opts('later', 'not_yet', 'wait')), 'task');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
t('the Blockers page and Home both ask the one rule, never their own', () => {
|
|
103
|
+
assert.match(read('modules/hall-ui/public/blockers.js'), /human: lib\.humanKind\(b\)/);
|
|
104
|
+
assert.match(read('modules/hall-ui/public/goal-inbox.js'), /human: lib\.humanKind\(b\),\s*\n\s*where: 'Waiting on the project owner'/);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
t('the rendered blocker card wears the badge the rule picks', () => {
|
|
108
|
+
for (const [name, b, want] of LIVE) {
|
|
109
|
+
const h = ask.html({ q: name, human: blockersLib.humanKind(b) });
|
|
110
|
+
assert.match(h, new RegExp(`hhuman--${want}`), name);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
t('the Conductor teaches both markers on a session\'s first prompt, and only then', () => {
|
|
115
|
+
const first = conductor.evaluateTriggers({ firstPrompt: true }).find((a) => a.id === 'human-signal');
|
|
116
|
+
assert.ok(first, 'no human-signal advisory on the first prompt');
|
|
117
|
+
assert.ok(first.text.includes(TASK) && first.text.includes(DECISION), first.text);
|
|
118
|
+
assert.ok(!conductor.evaluateTriggers({ firstPrompt: false }).some((a) => a.id === 'human-signal'));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
t('the one-time human line does not count toward the block fingerprint, so prompt two is still a repeat', () => {
|
|
122
|
+
const first = conductor.evaluateTriggers({ firstPrompt: true, interaction: { inject: true, contract: 'P' } });
|
|
123
|
+
const second = conductor.evaluateTriggers({ firstPrompt: false, interaction: { inject: true, contract: 'P' } });
|
|
124
|
+
assert.equal(conductor.fingerprint(first), conductor.fingerprint(second));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
t('an ask is a decision, so the ask-shape reminder wears the decision mark', () => {
|
|
128
|
+
const a = conductor.evaluateTriggers({ prompt: 'file a blocker' }).find((x) => x.id === 'ask-shape');
|
|
129
|
+
assert.equal(a.icon, '🔀');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
t('every other model reads the same two markers', () => {
|
|
133
|
+
for (const f of ['AGENTS.md', 'GEMINI.md', 'docs/voice.md']) {
|
|
134
|
+
const s = read(f);
|
|
135
|
+
assert.ok(s.includes(TASK) || s.includes('`🙋 **HUMAN TASK:**`'), `${f} lacks the task marker`);
|
|
136
|
+
assert.ok(s.includes(DECISION), `${f} lacks the decision marker`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
141
|
+
process.exitCode = failed ? 1 : 0;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// tests/install_script_reader.mjs
|
|
2
|
+
//
|
|
3
|
+
// task 1004924 — the one-line installers (/downloads/bongos/install.sh and
|
|
4
|
+
// install.ps1) were read from disk and re-substituted on EVERY request, a sync
|
|
5
|
+
// read blocking the event loop. src/bongos-downloads.js installScriptReader reads
|
|
6
|
+
// and substitutes each once per process; serve-internal mounts the routes on it.
|
|
7
|
+
//
|
|
8
|
+
// Its own file, not tests/bongos_downloads.mjs: that file needs the host-only
|
|
9
|
+
// config/bongos-cli.json and exits as HOST-TOPOLOGY-SKIP on the neutral core, so
|
|
10
|
+
// a case added there never runs here or in core CI.
|
|
11
|
+
//
|
|
12
|
+
// Run: node tests/install_script_reader.mjs
|
|
13
|
+
|
|
14
|
+
import { strict as assert } from 'node:assert';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
import { makeRunner } from './helpers.mjs';
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const { installScriptReader } = require('../src/bongos-downloads.js');
|
|
21
|
+
const { test, summary } = makeRunner();
|
|
22
|
+
|
|
23
|
+
await test('substitutes the pinned origin, and reads each script from disk once', () => {
|
|
24
|
+
const reads = [];
|
|
25
|
+
const read = installScriptReader({
|
|
26
|
+
readFile: (name) => { reads.push(name); return `BASE=__GDS_API_BASE__\nAGAIN=__GDS_API_BASE__/x\n# ${name}`; },
|
|
27
|
+
origin: 'https://hub.example',
|
|
28
|
+
});
|
|
29
|
+
for (let i = 0; i < 25; i++) {
|
|
30
|
+
assert.equal(read('bongos-install.sh'), 'BASE=https://hub.example\nAGAIN=https://hub.example/x\n# bongos-install.sh');
|
|
31
|
+
assert.match(read('bongos-install.ps1'), /^BASE=https:\/\/hub\.example\n/);
|
|
32
|
+
}
|
|
33
|
+
assert.deepEqual(reads, ['bongos-install.sh', 'bongos-install.ps1'], 'one disk read per script, not one per request');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await test('a missing script is null (the route 404s), and is not re-read either', () => {
|
|
37
|
+
let n = 0;
|
|
38
|
+
const read = installScriptReader({ readFile: () => { n++; throw Object.assign(new Error('nope'), { code: 'ENOENT' }); }, origin: 'https://o' });
|
|
39
|
+
assert.equal(read('bongos-install.sh'), null);
|
|
40
|
+
assert.equal(read('bongos-install.sh'), null);
|
|
41
|
+
assert.equal(n, 1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await test('serve-internal mounts the installer routes on the memoised reader', () => {
|
|
45
|
+
const src = readFileSync(new URL('../src/bongos/serve-internal.js', import.meta.url), 'utf8');
|
|
46
|
+
assert.match(src, /const readBongosInstallScript = installScriptReader\(\{/);
|
|
47
|
+
assert.doesNotMatch(src, /\.split\('__GDS_API_BASE__'\)/, 'no per-request read + split left in the route file');
|
|
48
|
+
assert.equal((src.match(/readBongosInstallScript\('bongos-install\.(sh|ps1)'\)/g) || []).length, 2, 'both installer routes use it');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
summary();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// tests/integration_fixture_ids.mjs — no two real-Postgres tests hand-pick the same
|
|
2
|
+
// fixture id (task 1005008).
|
|
3
|
+
//
|
|
4
|
+
// The integration tests share ONE database and seed rows at fixed ids in the
|
|
5
|
+
// 9xxxxxxx range. Task 1004950's task_needs_db.mjs picked builder ids 90000401-403,
|
|
6
|
+
// which page_tweak_no_block.mjs already used; that test failed part-way and its
|
|
7
|
+
// cleanup could not delete its builders (rows its claims had written still pointed
|
|
8
|
+
// at them), so the next file's INSERT hit a duplicate key and main's integration
|
|
9
|
+
// job went red, blocking every release. An id two files share is a collision
|
|
10
|
+
// waiting for one of them to leave a row behind.
|
|
11
|
+
//
|
|
12
|
+
// The ids already shared when this guard landed are frozen below. The list may
|
|
13
|
+
// only shrink: give a new test ids nobody else uses (grep the range first).
|
|
14
|
+
//
|
|
15
|
+
// Run: node tests/integration_fixture_ids.mjs
|
|
16
|
+
|
|
17
|
+
import { strict as assert } from 'node:assert';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import url from 'node:url';
|
|
22
|
+
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
const { INTEGRATION, INTEGRATION_PARKED } = require('../scripts/bongos/run-unit-tests.js');
|
|
25
|
+
const HERE = path.dirname(url.fileURLToPath(import.meta.url));
|
|
26
|
+
|
|
27
|
+
// Shared before task 1005008, each by tests that have not collided in practice.
|
|
28
|
+
const KNOWN_SHARED = new Set([
|
|
29
|
+
'90000301', '90000302', '90000303', // claim_gate_open_blocker, clawback_confirmed_backlog, reward_mode_cost_plus_only
|
|
30
|
+
'90000401', // page_tweak_no_block, goal_spec_task_db
|
|
31
|
+
'90000411', // page_tweak_no_block, task_criteria_unlink_db, session_cost_row_raise
|
|
32
|
+
'90000801', '90000802', // acclaim_feed_evidence_db, blocker_hall_live_proof
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
function sharedIds() {
|
|
36
|
+
const owners = new Map();
|
|
37
|
+
let scanned = 0;
|
|
38
|
+
for (const name of [...INTEGRATION, ...INTEGRATION_PARKED]) {
|
|
39
|
+
const file = path.join(HERE, `${name}.mjs`);
|
|
40
|
+
if (!fs.existsSync(file)) continue;
|
|
41
|
+
scanned++;
|
|
42
|
+
for (const id of new Set(fs.readFileSync(file, 'utf8').match(/\b9\d{7}\b/g) || [])) {
|
|
43
|
+
if (!owners.has(id)) owners.set(id, []);
|
|
44
|
+
owners.get(id).push(name);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { scanned, shared: [...owners].filter(([, names]) => names.length > 1) };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let passed = 0, failed = 0;
|
|
51
|
+
function t(name, fn) {
|
|
52
|
+
try { fn(); passed++; console.log(` ok ${name}`); }
|
|
53
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { scanned, shared } = sharedIds();
|
|
57
|
+
|
|
58
|
+
t('the scan actually read the integration tests', () => {
|
|
59
|
+
assert.ok(scanned >= 40, `expected the integration set on disk, read ${scanned} file(s)`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
t('no new fixture id is shared between two integration tests', () => {
|
|
63
|
+
const fresh = shared.filter(([id]) => !KNOWN_SHARED.has(id));
|
|
64
|
+
assert.deepEqual(fresh.map(([id, names]) => `${id}: ${names.join(', ')}`), [],
|
|
65
|
+
'pick an unused id range for the newer test');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
t('the frozen list only names ids that are still shared (shrink it when one is fixed)', () => {
|
|
69
|
+
const still = new Set(shared.map(([id]) => id));
|
|
70
|
+
assert.deepEqual([...KNOWN_SHARED].filter((id) => !still.has(id)), []);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
t('task_needs_db uses its own ids (the collision that reddened main)', () => {
|
|
74
|
+
const src = fs.readFileSync(path.join(HERE, 'task_needs_db.mjs'), 'utf8');
|
|
75
|
+
const pt = fs.readFileSync(path.join(HERE, 'page_tweak_no_block.mjs'), 'utf8');
|
|
76
|
+
const ids = (s) => new Set(s.match(/\b9\d{7}\b/g) || []);
|
|
77
|
+
const overlap = [...ids(src)].filter((id) => ids(pt).has(id));
|
|
78
|
+
assert.deepEqual(overlap, []);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
console.log(`\nintegration_fixture_ids (task 1005008): ${passed} passed, ${failed} failed`);
|
|
82
|
+
if (failed) process.exitCode = 1;
|
|
@@ -110,7 +110,7 @@ try {
|
|
|
110
110
|
await q(`INSERT INTO builders (id, github_login) VALUES (42, 'owner')`);
|
|
111
111
|
for (const f of ['060_access_requests', 'core_230_access_requests_applicant_vouch',
|
|
112
112
|
'core_254_access_requests_applicant_handle', 'core_255_access_requests_invite_kind',
|
|
113
|
-
'core_266_access_requests_invited_login_idx']) await q(coreSql(f));
|
|
113
|
+
'core_266_access_requests_invited_login_idx', 'core_282_builders_lower_login_idx']) await q(coreSql(f));
|
|
114
114
|
|
|
115
115
|
// ── The instance: the door, the queues, the probe ────────────────────────
|
|
116
116
|
console.log('the instance:');
|
|
@@ -135,6 +135,18 @@ try {
|
|
|
135
135
|
await q('RESET enable_seqscan');
|
|
136
136
|
ok(/idx_access_requests_invited_login/.test(plan), 'the sign-in gate can read invited rows through core_266\'s index');
|
|
137
137
|
|
|
138
|
+
// core_282 (task 1004923): the onboarding routes' "already a member?" probe
|
|
139
|
+
// compares lower(github_login), which only an expression index can serve.
|
|
140
|
+
await q('SET enable_seqscan = off');
|
|
141
|
+
const memberPlan = (await q('EXPLAIN SELECT 1 FROM builders WHERE lower(github_login) = lower($1) LIMIT 1', ['Owner']))
|
|
142
|
+
.rows.map((r) => r['QUERY PLAN']).join('\n');
|
|
143
|
+
await q('RESET enable_seqscan');
|
|
144
|
+
ok(/idx_builders_lower_login/.test(memberPlan), 'the member-by-login probe reads builders through core_282\'s index');
|
|
145
|
+
ok((await q('SELECT 1 FROM builders WHERE lower(github_login) = lower($1)', ['OWNER'])).rowCount === 1,
|
|
146
|
+
'and still matches a login case-insensitively');
|
|
147
|
+
await q(coreSql('core_282_builders_lower_login_idx'));
|
|
148
|
+
ok(true, 'core_282 re-runs cleanly (IF NOT EXISTS, ON CONFLICT DO NOTHING)');
|
|
149
|
+
|
|
138
150
|
const hist = Object.fromEntries((await call('GET', '/api/gds/access-requests?status=invited')).json.requests.map((r) => [r.github_login, r]));
|
|
139
151
|
ok(hist.fay.expired === true && hist.eve.expired === false && hist.cy.expired === false, 'the queue marks only the expired invite');
|
|
140
152
|
ok(hist.cy.expires_at === null && new Date(hist.fay.expires_at) <= new Date() && new Date(hist.eve.expires_at) > new Date(),
|
|
@@ -39,10 +39,6 @@ const fitness = require(path.join(GDS, 'fitness.js'));
|
|
|
39
39
|
const SMOKES = [
|
|
40
40
|
'lib.sh',
|
|
41
41
|
'smoke-write.sh',
|
|
42
|
-
'smoke-batch-claim.sh',
|
|
43
|
-
'smoke-batch-release.sh',
|
|
44
|
-
'smoke-claim-consistency.sh',
|
|
45
|
-
'smoke-dependencies.sh',
|
|
46
42
|
'portability-smoke-memory.sh',
|
|
47
43
|
];
|
|
48
44
|
|