@bongos/core 1.23.22 → 1.24.2
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 +933 -413
- 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 +96 -0
- package/clients/bongos-client/index.cjs +96 -0
- package/clients/bongos-client/index.d.ts +155 -4
- package/clients/bongos-client/index.mjs +96 -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 +12 -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 +6 -2
- package/docs/api/openapi.json +8651 -5833
- package/docs/api-reference.md +66 -10
- package/docs/architecture.md +37 -1
- package/docs/canonical-permissions.md +1 -1
- package/docs/copy-inventory.md +427 -339
- package/docs/copy-registry.json +1376 -500
- package/docs/file-map.md +1 -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 +57 -7
- package/docs/page-readings.json +1210 -1027
- 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/access-routes.js +70 -0
- package/modules/government/access.js +88 -0
- package/modules/government/asks.js +5 -2
- package/modules/government/catalog.js +6 -0
- package/modules/government/db.js +72 -0
- package/modules/government/docket.js +48 -2
- package/modules/government/migrations/government_035_payout_approve.sql +29 -0
- package/modules/government/migrations/government_036_access_holders.sql +33 -0
- package/modules/government/pollers/board-expiry.js +108 -20
- package/modules/government/protected-surfaces.json +1 -0
- package/modules/government/routes/government.js +31 -0
- package/modules/government/routing.js +49 -7
- package/modules/government/waiting.js +78 -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 +158 -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/government-access.js +108 -0
- package/modules/hall-ui/public/government.html +21 -0
- 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 +38 -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 +230 -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/government-walk.js +3 -0
- 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 +7 -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/access_holders.mjs +214 -0
- 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_board_membership.mjs +4 -1
- package/tests/government_protected_surfaces.mjs +1 -1
- package/tests/government_routes.mjs +1 -1
- package/tests/government_seed.mjs +1 -0
- 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
package/src/bongos/routes.js
CHANGED
|
@@ -22,6 +22,8 @@ const buildInstanceRouter = require('./routes/instance');
|
|
|
22
22
|
const buildCoreUpdateRouter = require('./routes/core-update');
|
|
23
23
|
const buildFeedbackRouter = require('./routes/feedback');
|
|
24
24
|
const buildAuthRouter = require('./routes/auth');
|
|
25
|
+
// Google sign-in + the browser page that approves a CLI sign-in (task 1004993).
|
|
26
|
+
const buildAuthGoogleRouter = require('./routes/auth-google');
|
|
25
27
|
const buildGithubRouter = require('./routes/github');
|
|
26
28
|
const buildMeRouter = require('./routes/me');
|
|
27
29
|
// The own-scoped session self-service (list / revoke one / sign out everywhere,
|
|
@@ -29,6 +31,11 @@ const buildMeRouter = require('./routes/me');
|
|
|
29
31
|
// aggregator or the admin pair in routes/auth.js — see that file's header.
|
|
30
32
|
const buildMySessionsRouter = require('./routes/my-sessions');
|
|
31
33
|
const buildMyPasskeysRouter = require('./routes/my-passkeys');
|
|
34
|
+
// The payout approval step and its append-only log (task 1004913, ADR 0385 D4).
|
|
35
|
+
const buildPayoutApprovalsRouter = require('./routes/payout-approvals');
|
|
36
|
+
// Who may be paid at all: age tier, sanctions, a hold that tells the person (task 1004914).
|
|
37
|
+
const buildPayoutGatesRouter = require('./routes/payout-gates');
|
|
38
|
+
const buildMyPayoutsRouter = require('./routes/my-payouts');
|
|
32
39
|
const buildBuildersRouter = require('./routes/builders');
|
|
33
40
|
// The LIFECYCLE routes (tasks · claims · versions · done-when · goals ·
|
|
34
41
|
// dependencies · analytics) carved into modules/lifecycle/
|
|
@@ -121,7 +128,10 @@ function buildGdsRouter() {
|
|
|
121
128
|
/^\/provisioning\/instances\/\d+\/logo$/.test(req.path) ||
|
|
122
129
|
// task 1004496: a reference image on a page feedback doc — up to 4 MB decoded,
|
|
123
130
|
// ~5.3 MB as base64. modules/page-notes/routes/page-notes.js mounts its own 8 MB parser.
|
|
124
|
-
/^\/page-notes\/\d+\/images$/.test(req.path)
|
|
131
|
+
/^\/page-notes\/\d+\/images$/.test(req.path) ||
|
|
132
|
+
// task 1004917: one sketch, image or PDF for an AI studio request — up to
|
|
133
|
+
// 8 MB decoded. modules/ai-studio/routes/ai-studio.js mounts its own 12 MB parser.
|
|
134
|
+
req.path === '/ai-studio/uploads'
|
|
125
135
|
) return next();
|
|
126
136
|
return globalJson(req, res, next);
|
|
127
137
|
});
|
|
@@ -159,10 +169,14 @@ function buildGdsRouter() {
|
|
|
159
169
|
router.use(buildCoreUpdateRouter());
|
|
160
170
|
router.use(buildFeedbackRouter());
|
|
161
171
|
router.use(buildAuthRouter());
|
|
172
|
+
router.use(buildAuthGoogleRouter());
|
|
162
173
|
router.use(buildGithubRouter());
|
|
163
174
|
router.use(buildMeRouter());
|
|
164
175
|
router.use(buildMySessionsRouter());
|
|
165
176
|
router.use(buildMyPasskeysRouter());
|
|
177
|
+
router.use(buildPayoutApprovalsRouter());
|
|
178
|
+
router.use(buildPayoutGatesRouter());
|
|
179
|
+
router.use(buildMyPayoutsRouter());
|
|
166
180
|
router.use(buildBuildersRouter());
|
|
167
181
|
// tasks · claims · versions · done-when · goals · dependencies · analytics now
|
|
168
182
|
// mount via moduleLoader.mountModuleRoutes below (lifecycle module, BV1.R86) —
|
package/src/bongos/secret-box.js
CHANGED
|
@@ -26,6 +26,7 @@ const crypto = require('node:crypto');
|
|
|
26
26
|
const fs = require('node:fs');
|
|
27
27
|
const path = require('node:path');
|
|
28
28
|
const ic = require('../instance-config');
|
|
29
|
+
const { readConfigFileCached, clearConfigReadCache } = require('../instance-config'); // destructured: knip credits only this form
|
|
29
30
|
|
|
30
31
|
const VERSION = 'v1';
|
|
31
32
|
const IV_BYTES = 12;
|
|
@@ -58,9 +59,22 @@ function decodeMasterKey(raw) {
|
|
|
58
59
|
return null;
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
// task 1004924: encrypt/decrypt/isConfigured run on request paths, and each used
|
|
63
|
+
// to decode the key afresh and, with no env key, read the key file from disk —
|
|
64
|
+
// a sync read blocking the event loop on every call. The decode is memoised on
|
|
65
|
+
// the raw string (one entry per source: an env change is simply a new string),
|
|
66
|
+
// and the file goes through the cached config read (60 s TTL, keyed by the
|
|
67
|
+
// resolved paths, dropped by provisionMasterKey).
|
|
68
|
+
const _decoded = { env: { raw: undefined, key: null }, file: { raw: undefined, key: null } };
|
|
69
|
+
function decodeMemo(slot, raw) {
|
|
70
|
+
const m = _decoded[slot];
|
|
71
|
+
if (m.raw !== raw) { m.raw = raw; m.key = decodeMasterKey(raw); }
|
|
72
|
+
return m.key;
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
// Load the master key from env, then the local operator file. Returns Buffer|null.
|
|
62
76
|
function loadMasterKey() {
|
|
63
|
-
const fromEnv =
|
|
77
|
+
const fromEnv = decodeMemo('env', process.env.BUILDER_SECRET_KEY);
|
|
64
78
|
if (fromEnv) return fromEnv;
|
|
65
79
|
// A set-but-malformed env value is an operator mistake worth one loud line —
|
|
66
80
|
// but we still fail closed (return null) rather than fall back to something weak.
|
|
@@ -69,8 +83,8 @@ function loadMasterKey() {
|
|
|
69
83
|
console.error('[gds] BUILDER_SECRET_KEY is set but is not a 32-byte hex/base64 value — secret storage disabled until fixed.');
|
|
70
84
|
}
|
|
71
85
|
try {
|
|
72
|
-
const raw =
|
|
73
|
-
const fromFile = raw &&
|
|
86
|
+
const raw = readConfigFileCached('builder-secret.key');
|
|
87
|
+
const fromFile = raw && decodeMemo('file', raw);
|
|
74
88
|
if (fromFile) return fromFile;
|
|
75
89
|
} catch (_) { /* not configured */ }
|
|
76
90
|
return null;
|
|
@@ -121,6 +135,7 @@ function provisionMasterKey(opts = {}) {
|
|
|
121
135
|
return { ok: false, error: (e && (e.code || e.message)) || 'write_failed' };
|
|
122
136
|
}
|
|
123
137
|
try { fs.chmodSync(file, 0o600); } catch (_) { /* best-effort tighten */ }
|
|
138
|
+
clearConfigReadCache(); // a cached "no key file" must not outlive the write
|
|
124
139
|
return { ok: true, created: true };
|
|
125
140
|
}
|
|
126
141
|
|
|
@@ -41,7 +41,7 @@ const gdsAuth = require('./auth');
|
|
|
41
41
|
const { loadIdpConfig } = require('./auth-config');
|
|
42
42
|
const { buildStampedHtml, hashFile } = require('../asset-version');
|
|
43
43
|
const { buildInfo } = require('../build-info');
|
|
44
|
-
const { buildBongosDownloads } = require('../bongos-downloads');
|
|
44
|
+
const { buildBongosDownloads, installScriptReader } = require('../bongos-downloads');
|
|
45
45
|
const { clientBranding, branding, applyBrandConditionals } = require('../branding');
|
|
46
46
|
const { nightTintCss } = require('../night-tint');
|
|
47
47
|
const { prelaunchGate } = require('./prelaunch-gate');
|
|
@@ -500,8 +500,22 @@ function injectModuleHallWidgets(html, scripts = hallWidgetScripts({ isEnabled:
|
|
|
500
500
|
// while the builders host keeps its own isBuildersHost guard (it's served by the
|
|
501
501
|
// richer dedicated block, not the generic loop).
|
|
502
502
|
function isStatusHost(req) {
|
|
503
|
-
|
|
504
|
-
|
|
503
|
+
return siteOf(req).host.startsWith('status.');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// WHICH SITE IS THIS REQUEST FOR, worked out ONCE per request (task 1004963) into
|
|
507
|
+
// req.bongosSite, which every host predicate reads instead of re-parsing the Host
|
|
508
|
+
// header (and the buildersOrigin URL) in each of ~15 handlers. Keyed on the raw
|
|
509
|
+
// Host it came from, so a changed header is re-classified, never answered stale.
|
|
510
|
+
// `surface` is filled by the mounted app's surfaceOf(), which knows its surfaces.
|
|
511
|
+
function siteOf(req) {
|
|
512
|
+
const raw = req.headers.host || '';
|
|
513
|
+
const cached = req.bongosSite;
|
|
514
|
+
if (cached && cached.raw === raw) return cached;
|
|
515
|
+
const host = raw.toLowerCase().split(':')[0];
|
|
516
|
+
const cfg = host.startsWith('builders.') ? '' : configuredBuildersHost();
|
|
517
|
+
const builders = host.startsWith('builders.') || (cfg !== '' && host === cfg);
|
|
518
|
+
return (req.bongosSite = { raw, host, builders, surface: null, surfaceFor: null });
|
|
505
519
|
}
|
|
506
520
|
// A single-host instance — one with no dedicated builders.<apex> subdomain, e.g. a
|
|
507
521
|
// co-located instance on a one-level subdomain like emersonian-circles.cloudbongos.com
|
|
@@ -521,10 +535,7 @@ function configuredBuildersHost() {
|
|
|
521
535
|
} catch { return ''; }
|
|
522
536
|
}
|
|
523
537
|
function isBuildersHost(req) {
|
|
524
|
-
|
|
525
|
-
if (host.startsWith('builders.')) return true;
|
|
526
|
-
const cfg = configuredBuildersHost();
|
|
527
|
-
return cfg !== '' && host === cfg;
|
|
538
|
+
return siteOf(req).builders;
|
|
528
539
|
}
|
|
529
540
|
|
|
530
541
|
// Is this instance served entirely from ONE host — publicOrigin and buildersOrigin
|
|
@@ -575,6 +586,13 @@ function mountInternalSurfaces(app) {
|
|
|
575
586
|
// It sits ABOVE the metrics middleware deliberately: a refused knock is still
|
|
576
587
|
// a request this instance served, and hiding gate 401s would make the traffic
|
|
577
588
|
// graph lie about how much attention an unannounced instance is getting.
|
|
589
|
+
// Ahead of even the gate: classify the request's site ONCE (siteOf, task 1004963).
|
|
590
|
+
// Pure string work, no I/O. Both gates were checked for per-request I/O then, and
|
|
591
|
+
// need no new cache: they read process.env and the memoised branding singleton; a
|
|
592
|
+
// 'gated' instance's session lookup is already memoised per request (RESOLVED_SESSIONS
|
|
593
|
+
// in auth.js), and caching it across requests would let a revoked session through.
|
|
594
|
+
app.use((req, _res, next) => { surfaceOf(req); next(); });
|
|
595
|
+
|
|
578
596
|
app.use(prelaunchGate());
|
|
579
597
|
|
|
580
598
|
app.use(metrics.httpMiddleware());
|
|
@@ -706,11 +724,11 @@ function mountInternalSurfaces(app) {
|
|
|
706
724
|
// a spoofed Host can't redirect a `curl | sh`). The fallback comes from the
|
|
707
725
|
// branding pack, not a hardcoded host brand (ADR 0062 §3).
|
|
708
726
|
const BONGOS_PUBLIC_ORIGIN = resolveEnv('PUBLIC_ORIGIN') || branding().domains.publicOrigin;
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
};
|
|
727
|
+
// Read + substituted once per name, not per request (task 1004924).
|
|
728
|
+
const readBongosInstallScript = installScriptReader({
|
|
729
|
+
readFile: (name) => fs.readFileSync(path.join(ROOT, 'infra', name), 'utf8'),
|
|
730
|
+
origin: BONGOS_PUBLIC_ORIGIN,
|
|
731
|
+
});
|
|
714
732
|
app.get('/downloads/bongos/latest.json', (_req, res) => {
|
|
715
733
|
res.set('Cache-Control', 'public, max-age=60');
|
|
716
734
|
res.json(bongosDownloads.latestJson());
|
|
@@ -790,6 +808,17 @@ function mountInternalSurfaces(app) {
|
|
|
790
808
|
// hall's). urlPrefix '/' because the surface is served at the host root.
|
|
791
809
|
html: injectBrandingInto(buildStampedHtml(s.dir, '/')),
|
|
792
810
|
}));
|
|
811
|
+
// The generic module surface this request's Host is for (first match, as the old
|
|
812
|
+
// some()/find() pair), or null — kept on req.bongosSite (task 1004963). First called
|
|
813
|
+
// by the top-of-chain classifier, which only runs once webSurfaces is built.
|
|
814
|
+
function surfaceOf(req) {
|
|
815
|
+
const site = siteOf(req);
|
|
816
|
+
if (site.surfaceFor !== webSurfaces) {
|
|
817
|
+
site.surface = webSurfaces.find((s) => site.host.startsWith(s.host)) || null;
|
|
818
|
+
site.surfaceFor = webSurfaces;
|
|
819
|
+
}
|
|
820
|
+
return site.surface;
|
|
821
|
+
}
|
|
793
822
|
// True iff this request's Host matches a GENERIC module web surface (status-style,
|
|
794
823
|
// served by the loop below) — replaces the literal isStatusHost() guard the
|
|
795
824
|
// /builders middleware used, so those guards skip every generic surface host with
|
|
@@ -797,13 +826,11 @@ function mountInternalSurfaces(app) {
|
|
|
797
826
|
// partitioned out above), so the dedicated /builders blocks below do NOT self-skip
|
|
798
827
|
// the builders host — they are what serves it.
|
|
799
828
|
function isModuleSurfaceHost(req) {
|
|
800
|
-
|
|
801
|
-
return webSurfaces.some((s) => host.startsWith(s.host));
|
|
829
|
+
return surfaceOf(req) !== null;
|
|
802
830
|
}
|
|
803
831
|
|
|
804
832
|
app.use((req, res, next) => {
|
|
805
|
-
const
|
|
806
|
-
const surface = webSurfaces.find((s) => host.startsWith(s.host));
|
|
833
|
+
const surface = surfaceOf(req);
|
|
807
834
|
if (!surface) return next();
|
|
808
835
|
// /api/* already handled above; everything else on a surface host serves from
|
|
809
836
|
// the module's static dir. The index page comes from the stamped in-memory copy
|
|
@@ -909,7 +936,7 @@ function mountInternalSurfaces(app) {
|
|
|
909
936
|
app.use((req, res, next) => {
|
|
910
937
|
if (isModuleSurfaceHost(req) || isBuildersHost(req)) return next();
|
|
911
938
|
if (req.path !== '/builders' && !req.path.startsWith('/builders/')) return next();
|
|
912
|
-
const apex = (req.
|
|
939
|
+
const apex = siteOf(req).host;
|
|
913
940
|
const rest = req.path.replace(/^\/builders\/?/, ''); // '/builders'→'', '/builders/watch'→'watch'
|
|
914
941
|
const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
|
|
915
942
|
return res.redirect(301, `https://builders.${apex}/${rest}${qs}`);
|
|
@@ -1296,8 +1323,9 @@ function mountInternalSurfaces(app) {
|
|
|
1296
1323
|
// Claude works for them. Every read behind it is the viewer's own and
|
|
1297
1324
|
// requireBuilder (GET /me and its /me/* prefs, /specialities), so sign-in is the
|
|
1298
1325
|
// whole gate; the governing reads it adds are gated by their own routes.
|
|
1326
|
+
// task 1004921: `ai-studio`, the studio's AI pages; every route behind it is the viewer's own.
|
|
1299
1327
|
const SIGNEDIN_BUILDERS_PAGE_RE =
|
|
1300
|
-
/^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|roadmap|copy-desk|studio|tweak-editor|ideas|thinking|collab|task|idea|primer(?:\.md)?|diagrams|my-agents|commands)(?:\.html)?\/?$/;
|
|
1328
|
+
/^\/builders\/(?:work|settings|atlas|modules|goals|goal-map|roadmap|copy-desk|studio|ai-studio|tweak-editor|ideas|thinking|collab|task|idea|primer(?:\.md)?|diagrams|my-agents|commands)(?:\.html)?\/?$/;
|
|
1301
1329
|
// The diagram image assets under /builders/diagrams/ ride the same tier as the
|
|
1302
1330
|
// page that embeds them.
|
|
1303
1331
|
const SIGNEDIN_BUILDERS_PREFIX_RE = /^\/builders\/diagrams\//;
|
|
@@ -1467,4 +1495,4 @@ function mountInternalSurfaces(app) {
|
|
|
1467
1495
|
return app;
|
|
1468
1496
|
}
|
|
1469
1497
|
|
|
1470
|
-
module.exports = { mountInternalSurfaces, isStatusHost, isBuildersHost, injectBrandingGlobal, injectBrandingInto, injectModuleHallWidgets, themeMarkup, applyBrandTokens, resolveOpenapiJson, openapiJson, resolveClientBundleJs, generateClientBundleJs, clientBundleJs, tryLoadInstanceConfig };
|
|
1498
|
+
module.exports = { mountInternalSurfaces, isStatusHost, isBuildersHost, siteOf, injectBrandingGlobal, injectBrandingInto, injectModuleHallWidgets, themeMarkup, applyBrandTokens, resolveOpenapiJson, openapiJson, resolveClientBundleJs, generateClientBundleJs, clientBundleJs, tryLoadInstanceConfig };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// src/bongos/sign-in-pages.js — the small server-rendered pages a sign-in shows
|
|
2
|
+
// before a session exists: the "Continue with GitHub / Continue with Google"
|
|
3
|
+
// chooser, and the page where a person approves a command-line sign-in in their
|
|
4
|
+
// browser (task 1004993, ADR 0396).
|
|
5
|
+
//
|
|
6
|
+
// WHY ITS OWN FILE. Both are pure over their inputs and the branding pack, so a
|
|
7
|
+
// test can render them without a server, and routes/auth.js (which shows the
|
|
8
|
+
// chooser) and routes/auth-google.js (which shows the approval page) can share
|
|
9
|
+
// one look without requiring each other. Same palette and stylesheet as the
|
|
10
|
+
// auth error page in routes/auth.js, so every sign-in screen reads as one place.
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const { branding } = require('../branding');
|
|
14
|
+
const { escapeHtml } = require('../html-escape');
|
|
15
|
+
|
|
16
|
+
const GITHUB_MARK = '<svg aria-hidden="true" width="18" height="18" viewBox="0 0 16 16" style="vertical-align:-3px;margin-right:8px"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59 .4 .07 .55-.17 .55-.38 0-.19-.01-.82-.01-1.49-2.01 .37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53 .63-.01 1.08 .58 1.23 .82 .72 1.21 1.87 .87 2.33 .66 .07-.52 .28-.87 .51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87 .31-1.59 .82-2.15-.08-.2-.36-1.02 .08-2.12 0 0 .67-.21 2.2 .82 .64-.18 1.32-.27 2-.27 .68 0 1.36 .09 2 .27 1.53-1.04 2.2-.82 2.2-.82 .44 1.1 .16 1.92 .08 2.12 .51 .56 .82 1.27 .82 2.15 0 3.07-1.87 3.75-3.65 3.95 .29 .25 .54 .73 .54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21 .15 .46 .55 .38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>';
|
|
17
|
+
const GOOGLE_MARK = '<svg aria-hidden="true" width="18" height="18" viewBox="0 0 48 48" style="vertical-align:-3px;margin-right:8px"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/><path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/></svg>';
|
|
18
|
+
|
|
19
|
+
function worldName() {
|
|
20
|
+
try { return branding().identity?.worldName || 'Sign in'; } catch { return 'Sign in'; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// One page shell: the hall stylesheet, a header, and a body the caller built
|
|
24
|
+
// (already escaped). Never a script: these pages work with forms and links alone.
|
|
25
|
+
function renderPage({ title, heading, subtitle, bodyHtml }) {
|
|
26
|
+
return `<!doctype html>
|
|
27
|
+
<html lang="en"><head>
|
|
28
|
+
<meta charset="utf-8">
|
|
29
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
30
|
+
<meta name="referrer" content="no-referrer">
|
|
31
|
+
<title>${escapeHtml(worldName())} — ${escapeHtml(title)}</title>
|
|
32
|
+
<link rel="stylesheet" href="/builders/style.css">
|
|
33
|
+
<style>
|
|
34
|
+
.signinChoices{display:flex;flex-direction:column;gap:12px;max-width:340px;margin:8px auto 0}
|
|
35
|
+
.signinChoices .landing__btn{display:block;text-align:center}
|
|
36
|
+
.signinCode{font:600 1.6rem/1.2 ui-monospace,Menlo,Consolas,monospace;letter-spacing:.12em;text-align:center;margin:12px 0}
|
|
37
|
+
.signinRow{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}
|
|
38
|
+
.signinRow form{margin:0}
|
|
39
|
+
.signinRow button{cursor:pointer}
|
|
40
|
+
.signinCodeForm{display:flex;gap:8px;justify-content:center;flex-wrap:wrap}
|
|
41
|
+
.signinCodeForm input{font:inherit;padding:8px 10px;text-transform:uppercase;letter-spacing:.1em;max-width:12em}
|
|
42
|
+
</style>
|
|
43
|
+
</head><body>
|
|
44
|
+
<header class="hall">
|
|
45
|
+
<h1>${escapeHtml(heading)}</h1>
|
|
46
|
+
${subtitle ? `<p class="hall__sub">${escapeHtml(subtitle)}</p>` : ''}
|
|
47
|
+
</header>
|
|
48
|
+
<main>
|
|
49
|
+
<section class="scroll">
|
|
50
|
+
<div class="landing">
|
|
51
|
+
${bodyHtml}
|
|
52
|
+
</div>
|
|
53
|
+
</section>
|
|
54
|
+
</main>
|
|
55
|
+
</body></html>`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The two ways in, side by side. Both hrefs are built by the caller from
|
|
59
|
+
// validated values; they are escaped here all the same.
|
|
60
|
+
function choiceButtons({ githubHref, googleHref }) {
|
|
61
|
+
return ` <div class="signinChoices">
|
|
62
|
+
<a class="landing__btn" href="${escapeHtml(githubHref)}">${GITHUB_MARK}Continue with GitHub</a>
|
|
63
|
+
<a class="landing__btn" href="${escapeHtml(googleHref)}">${GOOGLE_MARK}Continue with Google</a>
|
|
64
|
+
</div>`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The sign-in chooser /auth/web/start shows when Google sign-in is switched on.
|
|
68
|
+
function renderSignInChooser({ githubHref, googleHref }) {
|
|
69
|
+
return renderPage({
|
|
70
|
+
title: 'sign in',
|
|
71
|
+
heading: 'Sign in',
|
|
72
|
+
subtitle: 'New here? Either button creates your account.',
|
|
73
|
+
bodyHtml: `${choiceButtons({ githubHref, googleHref })}
|
|
74
|
+
<p class="landing__note">GitHub is only needed to create a project or to write code. Ideas, art and reviews work with either.</p>`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The command-line approval page, in its four states:
|
|
79
|
+
// 'enter' — no code, or one that is unknown or expired: ask for it
|
|
80
|
+
// 'signin' — a live code, nobody signed in: offer both ways in
|
|
81
|
+
// 'confirm' — a live code and a signed-in person: show it, Approve or Cancel
|
|
82
|
+
// 'done' — after the person decided (approved or cancelled)
|
|
83
|
+
function renderDeviceApprovalPage(view) {
|
|
84
|
+
const { state } = view;
|
|
85
|
+
if (state === 'signin') {
|
|
86
|
+
return renderPage({
|
|
87
|
+
title: 'approve command-line sign-in',
|
|
88
|
+
heading: 'Sign in to continue',
|
|
89
|
+
subtitle: 'Your command line is waiting for you to sign in.',
|
|
90
|
+
bodyHtml: ` <p class="signinCode">${escapeHtml(view.userCode)}</p>
|
|
91
|
+
${choiceButtons(view)}
|
|
92
|
+
<p class="landing__note">After you sign in you come straight back here to approve it.</p>`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (state === 'confirm') {
|
|
96
|
+
return renderPage({
|
|
97
|
+
title: 'approve command-line sign-in',
|
|
98
|
+
heading: 'Approve this sign-in?',
|
|
99
|
+
subtitle: `A command line is asking to sign in as ${view.name}.`,
|
|
100
|
+
bodyHtml: ` <p>Check this code matches the one in your terminal:</p>
|
|
101
|
+
<p class="signinCode">${escapeHtml(view.userCode)}</p>
|
|
102
|
+
<div class="signinRow">
|
|
103
|
+
<form method="post" action="${escapeHtml(view.action)}">
|
|
104
|
+
<input type="hidden" name="user_code" value="${escapeHtml(view.userCode)}">
|
|
105
|
+
<input type="hidden" name="decision" value="approve">
|
|
106
|
+
<button class="landing__btn" type="submit">Approve</button>
|
|
107
|
+
</form>
|
|
108
|
+
<form method="post" action="${escapeHtml(view.action)}">
|
|
109
|
+
<input type="hidden" name="user_code" value="${escapeHtml(view.userCode)}">
|
|
110
|
+
<input type="hidden" name="decision" value="deny">
|
|
111
|
+
<button class="landing__btn" type="submit">Cancel</button>
|
|
112
|
+
</form>
|
|
113
|
+
</div>
|
|
114
|
+
<p class="landing__note">Only approve if you just started a sign-in on your own computer. If someone sent you this link, press Cancel.</p>`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (state === 'done') {
|
|
118
|
+
return renderPage({
|
|
119
|
+
title: view.approved ? 'signed in' : 'cancelled',
|
|
120
|
+
heading: view.approved ? 'You’re signed in' : 'Sign-in cancelled',
|
|
121
|
+
subtitle: view.approved ? 'Go back to your terminal — it carries on by itself.' : 'Nothing was signed in. You can close this page.',
|
|
122
|
+
bodyHtml: ' <p class="landing__note">You can close this page.</p>',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return renderPage({
|
|
126
|
+
title: 'approve command-line sign-in',
|
|
127
|
+
heading: 'Enter your code',
|
|
128
|
+
subtitle: view.problem || 'Type the code your terminal shows.',
|
|
129
|
+
bodyHtml: ` <form class="signinCodeForm" method="get" action="${escapeHtml(view.action)}">
|
|
130
|
+
<label for="code" class="landing__note" style="width:100%;text-align:center">Code</label>
|
|
131
|
+
<input id="code" name="code" autocomplete="off" autocapitalize="characters" spellcheck="false" placeholder="ABCD-EFGH" maxlength="12">
|
|
132
|
+
<button class="landing__btn" type="submit">Continue</button>
|
|
133
|
+
</form>
|
|
134
|
+
<p class="landing__note">Codes last ten minutes. If yours ran out, start the sign-in again in your terminal.</p>`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = { renderSignInChooser, renderDeviceApprovalPage };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// A task says what it NEEDS; a builder has CAPABILITIES (task 1004950, ADR 0395).
|
|
2
|
+
//
|
|
3
|
+
// Most work needs nothing beyond the ordinary build loop. Some needs a person who
|
|
4
|
+
// holds something an agent on a builder machine does not:
|
|
5
|
+
//
|
|
6
|
+
// owner — a decision, account or permission only the project owner holds.
|
|
7
|
+
// server — hands on the live host: its DNS, its secrets, its database by hand.
|
|
8
|
+
//
|
|
9
|
+
// `tasks.needs` is its OWN set, deliberately not a value of `automation_tag`.
|
|
10
|
+
// That tag answers "can this run unattended overnight?" (it feeds the session-fit
|
|
11
|
+
// projection); needs answers "who can do this at all?". A task can be
|
|
12
|
+
// overnight-safe and need the server, or need the owner and be live-only — one
|
|
13
|
+
// field cannot carry both answers without making one of them lie.
|
|
14
|
+
//
|
|
15
|
+
// A builder's capabilities are resolved server-side, per request, uncached — like
|
|
16
|
+
// rank (ADR 0016). Nothing in a request body can assert one:
|
|
17
|
+
// owner from the government's owner seat (positionsFor), with the founding
|
|
18
|
+
// builder as the fallback — read exactly the way the criterion UAT
|
|
19
|
+
// sign-off reads it (criterion-uat-db.holdsOwnerSeat).
|
|
20
|
+
// server from builders.capability_grants, which only the owner sets. Default
|
|
21
|
+
// off. Holding the owner seat does NOT imply it: the owner's own agent
|
|
22
|
+
// runs on a builder machine too.
|
|
23
|
+
//
|
|
24
|
+
// What narrows and what does not: the lists that RECOMMEND work (the claimable
|
|
25
|
+
// feed, the session optimizer, the autonomous runner) leave out a task whose
|
|
26
|
+
// needs the viewer lacks. Every task stays readable everywhere else. The claim
|
|
27
|
+
// itself is refused only on `server` (an agent without the grant cannot do it);
|
|
28
|
+
// `owner` work stays claimable, because the owner claims it.
|
|
29
|
+
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
const TASK_NEEDS = Object.freeze(['owner', 'server']);
|
|
33
|
+
// The capabilities an owner can grant by hand. `owner` is never granted — it is
|
|
34
|
+
// the seat itself.
|
|
35
|
+
const GRANTABLE_CAPABILITIES = Object.freeze(['server']);
|
|
36
|
+
|
|
37
|
+
// normalizeNeeds — validate a request value into a sorted, de-duplicated array.
|
|
38
|
+
// Accepts an array, a comma-separated string, null/'' (= none). Returns
|
|
39
|
+
// { ok: true, needs } or { ok: false, bad: [...] } naming the unknown values.
|
|
40
|
+
function normalizeNeeds(value) {
|
|
41
|
+
if (value === null || value === undefined || value === '') return { ok: true, needs: [] };
|
|
42
|
+
const list = Array.isArray(value) ? value : (typeof value === 'string' ? value.split(',') : null);
|
|
43
|
+
if (!list) return { ok: false, bad: [String(value)] };
|
|
44
|
+
const cleaned = list.map((v) => (typeof v === 'string' ? v.trim().toLowerCase() : v)).filter((v) => v !== '');
|
|
45
|
+
const bad = cleaned.filter((v) => !TASK_NEEDS.includes(v));
|
|
46
|
+
if (bad.length > 0) return { ok: false, bad: bad.map(String) };
|
|
47
|
+
return { ok: true, needs: TASK_NEEDS.filter((n) => cleaned.includes(n)) };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// missingNeeds — the needs of a task the holder of `capabilities` lacks. Pure.
|
|
51
|
+
// An unknown need on a row (a value written by a newer release) counts as
|
|
52
|
+
// missing: fail closed, never advertise work nobody here can do.
|
|
53
|
+
function missingNeeds(needs, capabilities) {
|
|
54
|
+
const have = new Set(Array.isArray(capabilities) ? capabilities : []);
|
|
55
|
+
return (Array.isArray(needs) ? needs : []).filter((n) => !have.has(n));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// claimRefusalNeeds — the subset of missing needs that REFUSES a claim. Only
|
|
59
|
+
// `server`: owner work is claimable (by the owner, who the feed shows it to).
|
|
60
|
+
function claimRefusalNeeds(needs, capabilities) {
|
|
61
|
+
return missingNeeds(needs, capabilities).filter((n) => n !== 'owner');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const SERVER_REFUSAL_MESSAGE =
|
|
65
|
+
'This task needs server access, which you do not have yet, so pick another task or ask the project owner for it.';
|
|
66
|
+
|
|
67
|
+
// holdsOwnerSeatVia — the government port's answer. No port or a failed read
|
|
68
|
+
// answers false; the founding-builder fallback still applies.
|
|
69
|
+
async function holdsOwnerSeatVia(government, builderId) {
|
|
70
|
+
if (!government || typeof government.positionsFor !== 'function') return false;
|
|
71
|
+
try {
|
|
72
|
+
const p = await government.positionsFor(builderId);
|
|
73
|
+
return !!(p && Array.isArray(p.held) && p.held.some((h) => h && h.kind === 'owner'));
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// resolveCapabilities — the capabilities `builderId` holds right now, as a sorted
|
|
80
|
+
// array. `deps` (tests): { pool, government, getFoundingBuilder }.
|
|
81
|
+
async function resolveCapabilities(builderId, deps = {}) {
|
|
82
|
+
if (builderId === null || builderId === undefined) return [];
|
|
83
|
+
const pool = deps.pool || require('./pool').pool;
|
|
84
|
+
const government = deps.government !== undefined
|
|
85
|
+
? deps.government
|
|
86
|
+
: require('../module-seams').resolveOptional('government');
|
|
87
|
+
const getFounder = deps.getFoundingBuilder || require('./db-kernel').getFoundingBuilder;
|
|
88
|
+
|
|
89
|
+
const caps = new Set();
|
|
90
|
+
let isOwner = await holdsOwnerSeatVia(government, builderId);
|
|
91
|
+
if (!isOwner) {
|
|
92
|
+
const founder = await Promise.resolve().then(getFounder).catch(() => null);
|
|
93
|
+
isOwner = !!(founder && String(founder.id) === String(builderId));
|
|
94
|
+
}
|
|
95
|
+
if (isOwner) caps.add('owner');
|
|
96
|
+
|
|
97
|
+
const { rows } = await pool.query('SELECT capability_grants FROM builders WHERE id = $1', [builderId]);
|
|
98
|
+
const grants = rows[0] && Array.isArray(rows[0].capability_grants) ? rows[0].capability_grants : [];
|
|
99
|
+
for (const g of grants) if (GRANTABLE_CAPABILITIES.includes(g)) caps.add(g);
|
|
100
|
+
|
|
101
|
+
return TASK_NEEDS.filter((c) => caps.has(c));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// setCapabilityGrants — the owner grants or withdraws a builder's capabilities
|
|
105
|
+
// (PATCH /builders/:id/capabilities). The owner check lives HERE, at the write,
|
|
106
|
+
// not only in the route: `actorBuilderId` must hold the owner seat right now, or
|
|
107
|
+
// it throws NOT_PROJECT_OWNER — so no other caller can grant server access by
|
|
108
|
+
// reaching this function. It is deliberately NOT on the module doorway (see
|
|
109
|
+
// READ_SURFACE). A machine principal holds none: an agent's access is the
|
|
110
|
+
// person's it runs for. Returns the row, null when absent; throws
|
|
111
|
+
// TARGET_IS_SYSTEM_PRINCIPAL like setBuilderTester.
|
|
112
|
+
async function setCapabilityGrants({ actorBuilderId, builderId, grants }, deps = {}) {
|
|
113
|
+
const pool = deps.pool || require('./pool').pool;
|
|
114
|
+
const actorCaps = await resolveCapabilities(actorBuilderId, deps);
|
|
115
|
+
if (!actorCaps.includes('owner')) throw { code: 'NOT_PROJECT_OWNER' };
|
|
116
|
+
const { rows: found } = await pool.query('SELECT id, system_role FROM builders WHERE id = $1', [builderId]);
|
|
117
|
+
if (!found[0]) return null;
|
|
118
|
+
if (found[0].system_role) throw { code: 'TARGET_IS_SYSTEM_PRINCIPAL' };
|
|
119
|
+
const { rows } = await pool.query(
|
|
120
|
+
`UPDATE builders SET capability_grants = $2::text[] WHERE id = $1
|
|
121
|
+
RETURNING id, github_login, display_name, capability_grants`,
|
|
122
|
+
[builderId, GRANTABLE_CAPABILITIES.filter((g) => grants.includes(g))]
|
|
123
|
+
);
|
|
124
|
+
return rows[0] || null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// feedNeedsClause — the SQL predicate a recommend list applies: the row's needs
|
|
128
|
+
// are a subset of the viewer's capabilities. `param` is the placeholder holding
|
|
129
|
+
// the capabilities text[]. A NULL needs reads as none.
|
|
130
|
+
function feedNeedsClause(alias, param) {
|
|
131
|
+
return `COALESCE(${alias}.needs, '{}'::text[]) <@ ${param}::text[]`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// READ_SURFACE — what the module doorway publishes (src/module-api.js taskNeeds):
|
|
135
|
+
// the vocabulary, the reads and the SQL predicate. Never the grant write.
|
|
136
|
+
const READ_SURFACE = Object.freeze({
|
|
137
|
+
TASK_NEEDS, GRANTABLE_CAPABILITIES, SERVER_REFUSAL_MESSAGE,
|
|
138
|
+
normalizeNeeds, claimRefusalNeeds, resolveCapabilities, feedNeedsClause,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
READ_SURFACE,
|
|
143
|
+
resolveCapabilities,
|
|
144
|
+
setCapabilityGrants,
|
|
145
|
+
feedNeedsClause,
|
|
146
|
+
};
|
package/src/bongos-downloads.js
CHANGED
|
@@ -62,4 +62,21 @@ function buildBongosDownloads(cfg) {
|
|
|
62
62
|
return { redirectFor, latestJson, versionJson, version };
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
// installScriptReader — the one-line installers (/downloads/bongos/install.sh and
|
|
66
|
+
// install.ps1) with the API base substituted in, read from disk ONCE per name
|
|
67
|
+
// (task 1004924). They were re-read and re-substituted on every request, a sync
|
|
68
|
+
// read blocking the event loop. The files only change on a deploy, which restarts
|
|
69
|
+
// the process, so a per-process memo is exact. A missing file (null) is memoised
|
|
70
|
+
// too. `readFile(name)` returns the raw text or throws; it is the test seam.
|
|
71
|
+
function installScriptReader({ readFile, origin }) {
|
|
72
|
+
const memo = new Map();
|
|
73
|
+
return function read(name) {
|
|
74
|
+
if (memo.has(name)) return memo.get(name);
|
|
75
|
+
let out = null;
|
|
76
|
+
try { out = String(readFile(name)).split('__GDS_API_BASE__').join(origin); } catch { out = null; }
|
|
77
|
+
memo.set(name, out);
|
|
78
|
+
return out;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { buildBongosDownloads, installScriptReader };
|
package/src/instance-config.js
CHANGED
|
@@ -178,6 +178,32 @@ function readConfigFileSync(name, { encoding = 'utf8', b = safeBrand() } = {}) {
|
|
|
178
178
|
return null;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
// readConfigFileCached — readConfigFileSync for the REQUEST path (task 1004924).
|
|
182
|
+
// authConfigured()/discordConfigured() and the secret box run per request, and on
|
|
183
|
+
// a box with no env creds each call was up to four sync ENOENT reads blocking the
|
|
184
|
+
// event loop. This answers from memory for `ttlMs` (default 60 s, so a creds file
|
|
185
|
+
// an operator drops in still takes effect without a restart), caching a miss
|
|
186
|
+
// (null) as well as a hit. The key is the resolved read-path list, so a changed
|
|
187
|
+
// home or config dir is a fresh read, never a stale answer from another dir.
|
|
188
|
+
// A non-ENOENT error is not cached: it propagates, exactly as the uncached read.
|
|
189
|
+
const CONFIG_READ_TTL_MS = 60_000;
|
|
190
|
+
const _configReadCache = new Map(); // key -> { value, at }
|
|
191
|
+
function readConfigFileCached(name, { ttlMs = CONFIG_READ_TTL_MS, now = Date.now } = {}) {
|
|
192
|
+
const b = safeBrand();
|
|
193
|
+
const key = configReadPaths(name, b).join('\n');
|
|
194
|
+
const hit = _configReadCache.get(key);
|
|
195
|
+
const t = now();
|
|
196
|
+
if (hit && t - hit.at < ttlMs) return hit.value;
|
|
197
|
+
const value = readConfigFileSync(name, { b });
|
|
198
|
+
_configReadCache.set(key, { value, at: t });
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
// Drop the cached reads (all of them). For a writer that just changed a config
|
|
202
|
+
// file in this process (provisionMasterKey) and for tests.
|
|
203
|
+
function clearConfigReadCache() {
|
|
204
|
+
_configReadCache.clear();
|
|
205
|
+
}
|
|
206
|
+
|
|
181
207
|
// Resolve an env var by SUFFIX across the configured prefix + legacy prefixes.
|
|
182
208
|
// resolveEnv('API_BASE') tries <PREFIX>_API_BASE (BONGOS_ on vanilla, OTB_ on
|
|
183
209
|
// the OTB instance), then CLOUDBONGOS_/OTB_/GDS_/PMS_API_BASE. Returns undefined
|
|
@@ -349,6 +375,8 @@ module.exports = {
|
|
|
349
375
|
configPath,
|
|
350
376
|
configReadPaths,
|
|
351
377
|
readConfigFileSync,
|
|
378
|
+
readConfigFileCached,
|
|
379
|
+
clearConfigReadCache,
|
|
352
380
|
SESSION_FILENAMES,
|
|
353
381
|
readSessionConfigSync,
|
|
354
382
|
resolveEnv,
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.
|
|
84
|
+
const CORE_VERSION = '1.24.2'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -197,6 +197,12 @@ module.exports = {
|
|
|
197
197
|
// which a module cannot enforce without being able to ask who that is. It is
|
|
198
198
|
// a READ of an existing row; it grants nothing by itself.
|
|
199
199
|
getFoundingBuilder: (...a) => require('./bongos/db-kernel').getFoundingBuilder(...a),
|
|
200
|
+
// taskNeeds — what a task needs (owner, server) and what a builder can do
|
|
201
|
+
// (task 1004950, ADR 0395). Core because the session optimizer applies the
|
|
202
|
+
// same feed rule and cannot require a module; capabilities are read fresh
|
|
203
|
+
// per call, like rank. The READ surface only — granting server access is
|
|
204
|
+
// core's owner-seat route, never a module's call.
|
|
205
|
+
get taskNeeds() { const { READ_SURFACE } = require('./bongos/task-needs'); return READ_SURFACE; },
|
|
200
206
|
insertAuditLog: (...a) => require('./bongos/db-kernel').insertAuditLog(...a),
|
|
201
207
|
get LIVE_RANK_LADDER() { return require('./bongos/db-kernel').LIVE_RANK_LADDER; },
|
|
202
208
|
// The ladder as the canonical 1-based rank → tier map (BV1.R108) — modules
|
|
@@ -244,6 +250,13 @@ module.exports = {
|
|
|
244
250
|
// therefore reaches via the exposed `pool` and its own client instead.
|
|
245
251
|
get grantedScopeCoversRepos() { return require('./bongos/auth-config').grantedScopeCoversRepos; },
|
|
246
252
|
|
|
253
|
+
// --- prelaunchActive() — is this instance in pre-launch (stealth: the shared
|
|
254
|
+
// password gate is on)? Exposed for the provisioning module's adopt preflight
|
|
255
|
+
// (task 1004998): while it is on, a project may not adopt a PUBLIC repo. Read
|
|
256
|
+
// here rather than from the env vars so the gate and its callers can never
|
|
257
|
+
// disagree about which variables switch it on.
|
|
258
|
+
get prelaunchActive() { return require('./bongos/prelaunch-switch').enabled; },
|
|
259
|
+
|
|
247
260
|
// --- KERNEL trust classifiers (ADR 0091 §1, KERNEL_FILES) — the deterministic
|
|
248
261
|
// route→rank + protected-path matchers. They are KERNEL (the trust
|
|
249
262
|
// boundary, never "called" by a domain), exposed so a module that runs the
|