@bongos/core 1.23.22 → 1.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +902 -407
- package/.claude/skills/blocker-review/SKILL.md +9 -1
- package/.claude/skills/blocker-solve/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +1 -1
- package/.claude/skills/builder-reauth/SKILL.md +1 -1
- package/.claude/skills/builder-redteam/SKILL.md +1 -1
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/builder-setup/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -1
- package/.claude/skills/collab-review/SKILL.md +5 -2
- package/.claude/skills/design/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +2 -2
- package/.claude/skills/grade-recover/SKILL.md +1 -1
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/.claude/skills/owner-review/SKILL.md +1 -1
- package/.claude/skills/planning-session/SKILL.md +11 -11
- package/.claude/skills/read-session-export/SKILL.md +1 -1
- package/.claude/skills/recall/SKILL.md +1 -1
- package/.claude/skills/scan-before-install/SKILL.md +1 -1
- package/.claude/skills/session-handoff/SKILL.md +1 -1
- package/.claude/skills/strand-fix/SKILL.md +13 -0
- package/.claude/skills/worktree-clean/SKILL.md +1 -1
- package/AGENTS.md +15 -0
- package/GEMINI.md +5 -0
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +90 -0
- package/clients/bongos-client/index.cjs +90 -0
- package/clients/bongos-client/index.d.ts +149 -4
- package/clients/bongos-client/index.mjs +90 -0
- package/docs/adr/0054-session-token-reward-cost-plus.md +3 -1
- package/docs/adr/0055-server-mediated-branch-publish.md +1 -1
- package/docs/adr/0393-a-platform-account-is-a-person-and-github-is-one-way-in.md +37 -0
- package/docs/adr/0394-market-watch-searches-through-the-messages-api-on-the-projects-key-and-runs-from-its-own-poller.md +102 -0
- package/docs/adr/0395-a-task-says-what-it-needs-and-lists-show-only-what-you-can-do.md +36 -0
- package/docs/adr/0396-google-sign-in-a-stand-in-login-and-a-browser-approved-cli-sign-in.md +41 -0
- package/docs/adr/README.md +5 -1
- package/docs/api/openapi.json +8555 -5876
- package/docs/api-reference.md +63 -10
- package/docs/architecture.md +37 -1
- package/docs/canonical-permissions.md +1 -1
- package/docs/copy-inventory.md +420 -337
- package/docs/copy-registry.json +1321 -500
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +5 -0
- package/docs/onboarding/diagrams/03-drachmae-karma.mmd +3 -3
- package/docs/onboarding/diagrams/README.md +1 -1
- package/docs/onboarding/drachmae-and-karma.md +15 -15
- package/docs/onboarding/primer.md +2 -2
- package/docs/page-inventory.json +55 -6
- package/docs/page-readings.json +1185 -1011
- package/docs/recipes/google-sign-in.md +60 -0
- package/docs/recipes/ops-gotchas.md +1 -1
- package/migrations/core_282_builders_lower_login_idx.sql +33 -0
- package/migrations/core_283_claimable_ignores_page_markers.sql +53 -0
- package/migrations/core_283_payout_approvals.sql +183 -0
- package/migrations/core_284_payout_gates.sql +179 -0
- package/migrations/core_285_task_needs.sql +74 -0
- package/modules/ai-studio/billing.js +67 -0
- package/modules/ai-studio/context.js +29 -0
- package/modules/ai-studio/db.js +172 -0
- package/modules/ai-studio/gemini.js +75 -0
- package/modules/ai-studio/generate.js +119 -0
- package/modules/ai-studio/keys.js +73 -0
- package/modules/ai-studio/migrations/ai_studio_001_keys.sql +22 -0
- package/modules/ai-studio/migrations/ai_studio_002_ledger.sql +48 -0
- package/modules/ai-studio/migrations/ai_studio_003_requests.sql +55 -0
- package/modules/ai-studio/openrouter.js +90 -0
- package/modules/ai-studio/payments.js +105 -0
- package/modules/ai-studio/pricing.js +92 -0
- package/modules/ai-studio/routes/ai-studio.js +235 -5
- package/modules/ai-studio/studio.js +121 -0
- package/modules/autonomy/fence.js +64 -1
- package/modules/builder-settings/builder-needs.js +27 -1
- package/modules/copy-desk/routes/copy-desk.js +5 -0
- package/modules/economy/achievements.js +22 -10
- package/modules/economy/cost-classify.js +9 -0
- package/modules/economy/cost-write-guard.js +52 -0
- package/modules/economy/cost.js +2 -1
- package/modules/economy/credits.js +18 -15
- package/modules/economy/goal-reward.js +161 -0
- package/modules/economy/reward.js +4 -0
- package/modules/economy/routes/cost.js +33 -7
- package/modules/government/catalog.js +6 -0
- package/modules/government/db.js +34 -0
- package/modules/government/docket.js +36 -0
- package/modules/government/migrations/government_035_payout_approve.sql +29 -0
- package/modules/government/pollers/board-expiry.js +108 -20
- package/modules/government/protected-surfaces.json +1 -0
- package/modules/government/routes/government.js +22 -0
- package/modules/government/routing.js +39 -5
- package/modules/government/waiting.js +74 -0
- package/modules/hall-ui/public/ai-studio-lib.js +268 -0
- package/modules/hall-ui/public/ai-studio.css +193 -0
- package/modules/hall-ui/public/ai-studio.html +98 -0
- package/modules/hall-ui/public/ai-studio.js +556 -0
- package/modules/hall-ui/public/ai-studio.states.json +94 -0
- package/modules/hall-ui/public/blockers-by-person.js +156 -0
- package/modules/hall-ui/public/blockers-lib.js +15 -1
- package/modules/hall-ui/public/blockers.html +8 -0
- package/modules/hall-ui/public/blockers.js +1 -1
- package/modules/hall-ui/public/claim-action.js +3 -0
- package/modules/hall-ui/public/collab.html +12 -0
- package/modules/hall-ui/public/craft-mode.js +4 -1
- package/modules/hall-ui/public/criterion-uat-ui.js +4 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/genesis/r5-life-1-primordial-sea.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-2-first-cells-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-2-first-cells.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-3-genome-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-3-genome.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-4-membrane-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-4-membrane.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-5-nervous-system-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-5-nervous-system.jpg +0 -0
- package/modules/hall-ui/public/goal-inbox.js +1 -1
- package/modules/hall-ui/public/goals-render.js +4 -0
- package/modules/hall-ui/public/goals.html +8 -0
- package/modules/hall-ui/public/gov-living.js +6 -2
- package/modules/hall-ui/public/hall-ask.css +5 -0
- package/modules/hall-ui/public/hall-ask.js +39 -3
- package/modules/hall-ui/public/notes-panel.css +20 -0
- package/modules/hall-ui/public/notes-panel.js +146 -0
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/project-settings.js +7 -5
- package/modules/hall-ui/public/settings-ai-keys.js +124 -0
- package/modules/hall-ui/public/settings.html +15 -0
- package/modules/hall-ui/public/street-deck.js +12 -3
- package/modules/hall-ui/public/studio.css +90 -8
- package/modules/hall-ui/public/studio.html +39 -10
- package/modules/hall-ui/public/studio.js +148 -18
- package/modules/hall-ui/public/task.html +2 -0
- package/modules/hall-ui/public/task.js +4 -0
- package/modules/hall-ui/public/tweak-editor.js +12 -3
- package/modules/hall-ui/public/welcome-boot.css +42 -6
- package/modules/hall-ui/public/welcome-boot.js +20 -6
- package/modules/hall-ui/public/work.js +20 -0
- package/modules/hall-ui/records/ai-studio.md +36 -0
- package/modules/hall-ui/records/blockers-people.md +24 -0
- package/modules/lifecycle/db-claims.js +32 -4
- package/modules/lifecycle/db-goals.js +18 -5
- package/modules/lifecycle/db-tasks.js +15 -6
- package/modules/lifecycle/done-when.js +23 -1
- package/modules/lifecycle/lifecycle.js +5 -0
- package/modules/lifecycle/migrations/lifecycle_017_notes.sql +46 -0
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/notes.js +219 -0
- package/modules/lifecycle/page-notice.js +124 -0
- package/modules/lifecycle/page-tweak-artist.js +18 -1
- package/modules/lifecycle/publish-reconciler.js +67 -20
- package/modules/lifecycle/routes/claims.js +5 -2
- package/modules/lifecycle/routes/goals.js +3 -1
- package/modules/lifecycle/routes/notes.js +91 -0
- package/modules/lifecycle/routes/project-settings.js +43 -35
- package/modules/lifecycle/routes/task-route-helpers.js +21 -0
- package/modules/lifecycle/routes/task-write-routes.js +21 -3
- package/modules/lifecycle/routes/tasks.js +12 -12
- package/modules/lifecycle/task-needs-gate.js +70 -0
- package/modules/market-watch/CLAUDE.md +15 -0
- package/modules/market-watch/db.js +340 -0
- package/modules/market-watch/key.js +58 -0
- package/modules/market-watch/migrations/market_watch_001_tables.sql +249 -0
- package/modules/market-watch/migrations/market_watch_002_run.sql +46 -0
- package/modules/market-watch/module.json +26 -0
- package/modules/market-watch/pollers/hub-seed.js +54 -0
- package/modules/market-watch/pollers/weekly-run.js +100 -0
- package/modules/market-watch/research.js +350 -0
- package/modules/market-watch/run.js +245 -0
- package/modules/market-watch/seed/market-data.json +38 -0
- package/modules/market-watch/seed/partners-data.json +497 -0
- package/modules/market-watch/seed.js +252 -0
- package/modules/market-watch/store.js +230 -0
- package/modules/memory/routes/memory.js +14 -2
- package/modules/memory/routes/search.js +8 -2
- package/modules/platform-identity/account-logins.js +192 -0
- package/modules/platform-identity/migrations/platform_identity_034_account_logins.sql +96 -0
- package/modules/platform-identity/platform-identity.js +5 -4
- package/modules/platform-identity/routes/my-projects.js +6 -0
- package/modules/platform-identity/routes/profile.js +18 -0
- package/modules/platform-identity/routes/public-profile.js +10 -1
- package/modules/platform-identity/routes/sso.js +26 -1
- package/modules/platform-identity/tests/platform-identity.mjs +2 -1
- package/modules/provisioning/credential-preflight.js +13 -3
- package/modules/provisioning/pollers/app-liveness.js +25 -5
- package/modules/provisioning/routes/provisioning.js +1 -1
- package/modules/provisioning/starter-bundles.js +6 -2
- package/modules/public-landing/public/account.html +10 -2
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-space-nebula.jpg +0 -0
- package/modules/public-landing/public/assets/world/r3-space-nebula.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg.json +1 -1
- package/modules/public-landing/public/community.html +1 -1
- package/modules/public-landing/public/connections.html +2 -2
- package/modules/public-landing/public/guild.html +1 -1
- package/modules/public-landing/public/invitations.html +2 -2
- package/modules/public-landing/public/projects.html +5 -5
- package/modules/raise/views/nda.html +2 -0
- package/modules/specialities/db.js +32 -1
- package/modules/specialities/routes/specialities.js +10 -6
- package/modules/status-ui/public/index.html +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +220 -0
- package/scripts/bongos/autobongos-tweak-lane.js +46 -1
- package/scripts/bongos/backfill-subagent-rewards.js +2 -2
- package/scripts/bongos/claim.js +22 -1
- package/scripts/bongos/db-backup-nightly.sh +29 -0
- package/scripts/bongos/deploy-divergence.js +7 -0
- package/scripts/bongos/doc-cli-guard.js +6 -0
- package/scripts/bongos/fitness.js +3 -2
- package/scripts/bongos/gen-diagrams.js +3 -3
- package/scripts/bongos/lib.sh +4 -5
- package/scripts/bongos/migration-namespace.js +13 -1
- package/scripts/bongos/plain-cards.js +4 -0
- package/scripts/bongos/provision-github-setup.js +3 -1
- package/scripts/bongos/provision-units.js +6 -1
- package/scripts/bongos/publish-manifest.js +3 -0
- package/scripts/bongos/release.js +2 -0
- package/scripts/bongos/run-unit-tests.js +268 -82
- package/scripts/bongos/seed-tasks.example.json +56 -0
- package/scripts/bongos/seed-tasks.js +222 -0
- package/scripts/bongos/seed-verify-criteria.js +1 -2
- package/scripts/bongos/ship-finish.js +2 -0
- package/scripts/bongos/ship-resume.js +2 -0
- package/scripts/bongos/strand-lease.js +250 -0
- package/scripts/bongos/strand-watch.js +26 -5
- package/scripts/bongos/version-literal-guard.js +2 -2
- package/scripts/discord/post.js +4 -3
- package/scripts/gds/seed-tasks.js +7 -0
- package/scripts/gds/{build-bongos.js → strand-lease.js} +3 -3
- package/scripts/hall-preview/server.js +21 -0
- package/src/bongos/auth-config.js +29 -1
- package/src/bongos/auth-discord.js +3 -3
- package/src/bongos/auth-github.js +3 -6
- package/src/bongos/auth-google.js +217 -0
- package/src/bongos/device-approval.js +140 -0
- package/src/bongos/llm-pricing.js +17 -1
- package/src/bongos/metrics.js +48 -2
- package/src/bongos/middleware/rate-limit.js +30 -11
- package/src/bongos/module-scope-map.js +12 -1
- package/src/bongos/optimizer.js +15 -1
- package/src/bongos/payout-approvals.js +342 -0
- package/src/bongos/payout-gates.js +364 -0
- package/src/bongos/prelaunch-gate.js +7 -9
- package/src/bongos/prelaunch-switch.js +20 -0
- package/src/bongos/route-rank-check.js +3 -0
- package/src/bongos/routes/auth-google.js +246 -0
- package/src/bongos/routes/auth.js +38 -1
- package/src/bongos/routes/backup.js +33 -9
- package/src/bongos/routes/builders.js +57 -30
- package/src/bongos/routes/github.js +9 -0
- package/src/bongos/routes/me.js +18 -0
- package/src/bongos/routes/my-payouts.js +133 -0
- package/src/bongos/routes/payout-approvals.js +312 -0
- package/src/bongos/routes/payout-gates.js +179 -0
- package/src/bongos/routes.js +15 -1
- package/src/bongos/secret-box.js +18 -3
- package/src/bongos/serve-internal.js +47 -19
- package/src/bongos/sign-in-pages.js +138 -0
- package/src/bongos/task-needs.js +146 -0
- package/src/bongos-downloads.js +18 -1
- package/src/instance-config.js +28 -0
- package/src/module-api.js +14 -1
- package/tests/account_logins.mjs +326 -0
- package/tests/account_logins_db.mjs +161 -0
- package/tests/ai_studio_billing.mjs +253 -0
- package/tests/ai_studio_generate.mjs +282 -0
- package/tests/ai_studio_keys.mjs +159 -0
- package/tests/ai_studio_requests.mjs +319 -0
- package/tests/auth_backchannel_logout_route.mjs +201 -0
- package/tests/auto_satisfy_criteria.mjs +10 -1
- package/tests/autobongos_tweak_lane.mjs +144 -0
- package/tests/autobongos_tweak_pin_identity.mjs +137 -0
- package/tests/backfill_subagent_rewards.mjs +17 -1
- package/tests/backup_trigger_lock.mjs +119 -0
- package/tests/blockers_people.mjs +198 -0
- package/tests/bounded_rate_maps.mjs +184 -0
- package/tests/claimable_blocker_status.mjs +1 -1
- package/tests/claims_batch_routes.mjs +421 -0
- package/tests/config_read_cache.mjs +171 -0
- package/tests/copy_desk_page_editor.mjs +12 -0
- package/tests/cost_write_guard.mjs +200 -0
- package/tests/craft_achievements.mjs +33 -10
- package/tests/criterion_goal_attach.mjs +1 -1
- package/tests/db_backup_nightly.mjs +73 -1
- package/tests/deploy_divergence_line.mjs +7 -0
- package/tests/effective_visibility_predicate.mjs +12 -0
- package/tests/fitness.mjs +19 -0
- package/tests/fixtures/seed-tasks/bongos-v1-idea-routing.json +256 -0
- package/tests/fixtures/seed-tasks/bv1-profiles.json +592 -0
- package/tests/fixtures/seed-tasks/bv2-ideator-surface.json +373 -0
- package/tests/fixtures/seed-tasks/fake-api.mjs +80 -0
- package/tests/github_one_trip.mjs +34 -0
- package/tests/github_repo_visibility.mjs +20 -0
- package/tests/goal_achievement.mjs +30 -1
- package/tests/goal_achievement_reward.mjs +205 -0
- package/tests/goal_sort_staleness.mjs +2 -1
- package/tests/goal_tier.mjs +11 -1
- package/tests/google_fonts_nonblocking.mjs +84 -0
- package/tests/google_sign_in.mjs +401 -0
- package/tests/government_board_expiry.mjs +94 -0
- package/tests/government_protected_surfaces.mjs +1 -1
- package/tests/government_routes.mjs +1 -1
- package/tests/hall_ai_studio.mjs +347 -0
- package/tests/hall_page_gate_map.mjs +3 -0
- package/tests/hall_studio_home.mjs +61 -2
- package/tests/home_attention_cards.mjs +1 -1
- package/tests/human_signal.mjs +141 -0
- package/tests/install_script_reader.mjs +51 -0
- package/tests/integration_fixture_ids.mjs +82 -0
- package/tests/invite_expiry_db.mjs +13 -1
- package/tests/lib_sh_resolution.mjs +0 -4
- package/tests/market_watch_data.mjs +244 -0
- package/tests/market_watch_data_db.mjs +308 -0
- package/tests/market_watch_module.mjs +114 -0
- package/tests/market_watch_run.mjs +503 -0
- package/tests/metrics.mjs +124 -0
- package/tests/migration_namespace.mjs +14 -0
- package/tests/module_api.mjs +2 -0
- package/tests/module_loader.mjs +1 -1
- package/tests/notes.mjs +182 -0
- package/tests/own_scope_guard.mjs +5 -0
- package/tests/page_tweak_marker_sql.mjs +76 -0
- package/tests/page_tweak_no_block.mjs +135 -0
- package/tests/page_tweak_notice.mjs +103 -0
- package/tests/payout_approvals.mjs +253 -0
- package/tests/payout_approvals_db.mjs +106 -0
- package/tests/payout_gates.mjs +322 -0
- package/tests/payout_gates_db.mjs +113 -0
- package/tests/planning_session_skill.mjs +13 -3
- package/tests/project_settings_page.mjs +29 -5
- package/tests/project_settings_spend_cap_route.mjs +235 -0
- package/tests/promotion_sweep.mjs +29 -1
- package/tests/provision.mjs +3 -2
- package/tests/provisioning_app_status.mjs +18 -0
- package/tests/provisioning_credential_preflight.mjs +53 -0
- package/tests/provisioning_env_manifest_edit_delete.mjs +223 -0
- package/tests/publish_reconciler_overlap.mjs +60 -0
- package/tests/rank_change_broadcast_port.mjs +151 -0
- package/tests/seed_tasks.mjs +176 -0
- package/tests/seed_verify_criteria.mjs +2 -6
- package/tests/site_classified_once.mjs +142 -0
- package/tests/speciality_db_shape.mjs +23 -0
- package/tests/speciality_session_skills.mjs +48 -2
- package/tests/strand_lease.mjs +249 -0
- package/tests/strand_watch.mjs +10 -0
- package/tests/task_criteria_routes.mjs +275 -0
- package/tests/task_criteria_unlink_db.mjs +123 -0
- package/tests/task_needs.mjs +233 -0
- package/tests/task_needs_db.mjs +110 -0
- package/tests/unit_runner_serial.mjs +266 -43
- package/tests/version_literals.mjs +5 -7
- package/tests/welcome_boot.mjs +16 -5
- package/tests/welcome_boot_browser.mjs +36 -4
- package/docs/recipes/bongos-cli-release.md +0 -66
- package/scripts/bongos/backfill-disciplines.js +0 -144
- package/scripts/bongos/backfill-session-earnings.js +0 -123
- package/scripts/bongos/backfill-session-types.js +0 -87
- package/scripts/bongos/build-bongos.js +0 -94
- package/scripts/bongos/cleanup-orphans.js +0 -74
- package/scripts/bongos/seed-bongos-v1-idea-routing-tasks.js +0 -152
- package/scripts/bongos/seed-bv1-profiles-tasks.js +0 -135
- package/scripts/bongos/seed-bv2-ideator-surface-tasks.js +0 -390
- package/scripts/bongos/smoke-batch-claim.sh +0 -238
- package/scripts/bongos/smoke-batch-release.sh +0 -316
- package/scripts/bongos/smoke-claim-consistency.sh +0 -222
- package/scripts/bongos/smoke-dependencies.sh +0 -143
- package/scripts/gds/backfill-disciplines.js +0 -7
- package/scripts/gds/backfill-session-earnings.js +0 -7
- package/scripts/gds/backfill-session-types.js +0 -7
- package/scripts/gds/cleanup-orphans.js +0 -7
- package/scripts/gds/seed-bongos-v1-idea-routing-tasks.js +0 -6
- package/scripts/gds/seed-bv1-profiles-tasks.js +0 -6
- package/scripts/gds/seed-bv2-ideator-surface-tasks.js +0 -6
- package/scripts/gds/smoke-batch-claim.sh +0 -8
- package/scripts/gds/smoke-batch-release.sh +0 -8
- package/scripts/gds/smoke-claim-consistency.sh +0 -8
- package/scripts/gds/smoke-dependencies.sh +0 -8
- package/tests/smoke_dependencies_witness.mjs +0 -46
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Historical seed, already run: goal 1000095 (BONGOS-V2), from seed-bv2-ideator-surface-tasks.js; spec docs/specs/bongos-v2-ideator-surface.md. Kept as the equivalence fixture for tests/seed_tasks.mjs (task 1004979).",
|
|
3
|
+
"version_id": "BONGOS-V2",
|
|
4
|
+
"goal_id": 1000095,
|
|
5
|
+
"source": "planning-session",
|
|
6
|
+
"source_ref_prefix": "bv2-ideator-surface",
|
|
7
|
+
"tasks": [
|
|
8
|
+
{
|
|
9
|
+
"ref": "R07",
|
|
10
|
+
"title": "BV2.R01 — Spike: how provenance is recorded without widening the task hierarchy",
|
|
11
|
+
"description": "Output an ADR naming the edge shape, where it is written, and who writes it when work is split or rewritten.\n\n**The constraint that makes this a spike.** `modules/lifecycle/hierarchy.js` caps task parentage at ONE level (a parent may not itself have a parent; a task with children may not become a child) and refuses cross-version trees. Both are deliberate — ship rollup and claim safety lean on them. A descent of the shape idea → task → rewrite → split → removal is four levels and crosses a version boundary, so it cannot ride on that relation, and **widening it is explicitly out of scope** (spec decision 8).\n\nProvenance is a different relation from work decomposition. The expected home is `ideas_task_lineage`, which is already many-ideas-to-one-task and carries none of those invariants — but confirm that before assuming it.\n\n**Also settle:** who writes the edge. Parentage today is a manual explicit act, so an edge that depends on someone remembering will have holes. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
12
|
+
"kind": "spike",
|
|
13
|
+
"discipline": "engineer",
|
|
14
|
+
"priority": 5,
|
|
15
|
+
"criterion_ids": [
|
|
16
|
+
"ideator-tag-survives-the-fork"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"ref": "R14",
|
|
21
|
+
"title": "BV2.R02 — Spike: what counts as removed, and who says so",
|
|
22
|
+
"description": "Output an ADR settling the removal signal: its source, and whether it is derived or declared.\n\n**Nothing records this today.** `shipped` is terminal (`modules/lifecycle/rot.js` says so in as many words), there is no reverted status, and `tasks.merged_into_task_id` is CHECK-constrained to abandoned rows so it can only ever describe work that was cut before it landed. This signal is what lets brightness fall at all.\n\n**The owner's decision (spec decision 13) is the shape, not the detail:** the system PROPOSES and a person CONFIRMS. `modules/lifecycle/cascade.js` already implements ship-generates-work and ships with an EMPTY rule set, so the mechanism exists and only the rule is new. A derived-only signal dims the sky for refactors, renames and file moves; a declared-only one rots because nobody remembers.\n\nNearest existing signal to build the derivation on: `modules/economy/credits.js` `netNegativeBonusAmount`, which already reads `lines_removed > lines_added` from a ship's own diff — the only place the system counts deletion. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
23
|
+
"kind": "spike",
|
|
24
|
+
"discipline": "engineer",
|
|
25
|
+
"priority": 5,
|
|
26
|
+
"criterion_ids": [
|
|
27
|
+
"ideator-sky-tells-the-truth"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"ref": "R01",
|
|
32
|
+
"title": "BV2.R03 — The receipt persists",
|
|
33
|
+
"description": "A filing's outcome is stated and survives the next keystroke; the last several outcomes stay reachable on the surface that filed them.\n\n**This is the bug that started the session.** The hall already prints \"Filed as task N under <goal>\" on a routed capture (`modules/hall-ui/public/ideas.js`, the `#fq-result` line) — and then clears it the moment the composer is touched again. The signal exists; its half-life is about four seconds.\n\nDone-when: a person files a thought, keeps working, and can still see what became of it without navigating anywhere. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
34
|
+
"kind": "feature",
|
|
35
|
+
"discipline": "ui",
|
|
36
|
+
"priority": 4,
|
|
37
|
+
"criterion_ids": [
|
|
38
|
+
"ideator-nothing-is-lost"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"ref": "R02",
|
|
43
|
+
"title": "BV2.R04 — Gravity is visible before release",
|
|
44
|
+
"description": "The goal that will catch a thought is shown while composing, and letting it float is an explicit choice rather than an empty dropdown.\n\n**No new routing.** `modules/ideas/routing.js` `decideLanding` already decides this at capture time, and `GET /goals/suggestions` already feeds the composer's goal select. This task makes the decision legible at the moment it is made — the invisible dropdown is the direct cause of the confusion this whole scope came from (spec decision 2).\n\nDone-when: before releasing a thought, a person can see which goal will catch it, change it, or deliberately let it float. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
45
|
+
"kind": "feature",
|
|
46
|
+
"discipline": "ui",
|
|
47
|
+
"priority": 4,
|
|
48
|
+
"criterion_ids": [
|
|
49
|
+
"ideator-nothing-is-lost"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"ref": "R05",
|
|
54
|
+
"title": "BV2.R05 — The per-instance dials: the ignite floor and the decay rate",
|
|
55
|
+
"description": "Both configured rather than constant, sharing one home. Defaults preserve today's behaviour and halving respectively.\n\n**Two dials, one surface** (spec decisions 3 and 16). The ignite floor — who may convert a quick idea directly into claimable work — is today the `METIC_PLUS` constant in `modules/ideas/routing.js`. The decay rate — how fast an ideator's share falls per hop of the descent — is new in R11. The owner's ruling is that both belong to the project, not to the platform: a project of one wants every thought claimable, a project of fifty wants a nod.\n\nThey are scoped together deliberately so a second dial does not grow its own separate home. Do not widen either default as a side effect. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
56
|
+
"kind": "feature",
|
|
57
|
+
"discipline": "engineer",
|
|
58
|
+
"priority": 3,
|
|
59
|
+
"criterion_ids": [
|
|
60
|
+
"ideator-nothing-is-lost"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"ref": "R20",
|
|
65
|
+
"title": "BV2.R06 — The new five questions as a versioned template",
|
|
66
|
+
"description": "`base-v2` ships beside `base-v1`; ideas already filed keep scoring against the questions they were actually asked.\n\n**The owner's question set, in order:** what made you have the idea; scope as best as you can here; what should an engineer or artist know prior to building; how is this long term justified in the project; what does a successful implementation of the idea look like.\n\n**This is cheaper than it looks.** `modules/ideas/templates.js` already registers templates by key and every idea stores its `template_key`, so this is a second entry in the registry, NOT a migration. Renaming a label is free; only renaming a key would be a data migration, and this does not need to.\n\nOne field survives outright (what made you have the idea ≈ `why_had`) and one roughly (the long-term justification ≈ `fit`). Three are new and one is dropped. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
67
|
+
"kind": "feature",
|
|
68
|
+
"discipline": "engineer",
|
|
69
|
+
"priority": 4,
|
|
70
|
+
"criterion_ids": [
|
|
71
|
+
"ideator-full-idea-shapes-the-space"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"ref": "R08",
|
|
76
|
+
"title": "BV2.R07 — Provenance edges on the idea lineage ledger",
|
|
77
|
+
"description": "task→task descent edges recorded, to arbitrary depth and across version boundaries. The task hierarchy's one-level cap and its cross-version refusal are untouched.\n\nBuild to whatever R07's ADR settled. The write side that exists today is `modules/ideas/inbox.js` `linkIdeaTask`, an upsert on `(task_id, idea_id)`; `ideas_task_lineage` has been many-ideas-to-one-task since `ideas_006`.\n\n**The property that matters:** the tag must survive a rewrite, a split and a merge. If it only survives one hop the result is a link, not a story — and, after R11, someone does not get paid. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
78
|
+
"kind": "feature",
|
|
79
|
+
"discipline": "engineer",
|
|
80
|
+
"priority": 5,
|
|
81
|
+
"criterion_ids": [
|
|
82
|
+
"ideator-tag-survives-the-fork"
|
|
83
|
+
],
|
|
84
|
+
"deps": [
|
|
85
|
+
"R07"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"ref": "R04",
|
|
90
|
+
"title": "BV2.R08 — Fold the Full Idea form and the spark queue into the new surface",
|
|
91
|
+
"description": "Developing a thought is a step taken AFTER a one-line filing, never a gate before it. No dead links from `/idea/:id`, the command palette, or the nav.\n\n**What the ideas page actually is today:** three stacked sections — the spark queue (\"waiting to be developed\"), the quick composer, and the five-field Full Idea composer with its live completeness meter. All three need a home. The Board Room does NOT — it is reached from the left nav and is not behind this page.\n\n**The risk this task carries** (spec decision 18): a spark's whole value is that it costs nothing to have. Folding a five-question form onto the same surface is the most likely way to slow the one-line path. R06a exists to catch that; do not treat it as someone else's problem. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
92
|
+
"kind": "refactor",
|
|
93
|
+
"discipline": "ui",
|
|
94
|
+
"priority": 3,
|
|
95
|
+
"criterion_ids": [
|
|
96
|
+
"ideator-nothing-is-lost"
|
|
97
|
+
],
|
|
98
|
+
"deps": [
|
|
99
|
+
"R01"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"ref": "R11",
|
|
104
|
+
"title": "BV2.R09 — The ideator payout follows the descent, decayed by depth",
|
|
105
|
+
"description": "A descendant's ship pays the origin at the decayed rate. Idempotent. Existing first-hop behaviour preserved exactly.\n\n**The seam exists.** `modules/economy/credits.js` `awardIdeaTaskShipCredit` already resolves a shipped task back to its source idea and splits the payout — but only via the EARLIEST lineage link, one hop. A task split off later, or a child of a child, pays nobody.\n\n**Decay** (spec decision 16): full share at the direct task, halving at each hop, so an infinite descent is worth at most twice the direct task. The rate is a dial (R05), not a constant.\n\n**The boundary that stops farming** (spec decision 7): descendant WORK pays the origin; a separate idea that merely cites the origin does not. The descent displays more than the payout counts. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
106
|
+
"kind": "feature",
|
|
107
|
+
"discipline": "engineer",
|
|
108
|
+
"priority": 5,
|
|
109
|
+
"criterion_ids": [
|
|
110
|
+
"ideator-tag-survives-the-fork"
|
|
111
|
+
],
|
|
112
|
+
"deps": [
|
|
113
|
+
"R08"
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"ref": "R09",
|
|
118
|
+
"title": "BV2.R10 — The descent reads to arbitrary depth and across versions",
|
|
119
|
+
"description": "One query, not per-hop round trips.\n\n**Three findings inherited from abandoned task 1004054**, whose predecessor was built, graded and lost. They are free design requirements, not someone else's mess:\n\n1. **Test at the RENDERED layer, not just the data layer.** The prior attempt tested only the server-side trail builder, which is exactly why finding 2 went uncaught.\n2. **A merged idea's triage note lives on the MERGED row, not the target row.** The prior attempt read it from the target, so the headline scenario was the broken one.\n3. **Collapse the hops into one query or a CTE.** The prior attempt ran 4–6 serial round trips per read.\n\nToday's reader is `modules/ideas/inbox.js` `listIdeaTrailForBuilder`, which returns `lineage_tasks[]` — one hop only. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
120
|
+
"kind": "feature",
|
|
121
|
+
"discipline": "engineer",
|
|
122
|
+
"priority": 4,
|
|
123
|
+
"criterion_ids": [
|
|
124
|
+
"ideator-tag-survives-the-fork"
|
|
125
|
+
],
|
|
126
|
+
"deps": [
|
|
127
|
+
"R08"
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"ref": "R15",
|
|
132
|
+
"title": "BV2.R11 — The cascade rule that proposes a removal",
|
|
133
|
+
"description": "A ship that deletes an earlier shipped task's work files a QUESTION, not a verdict. `shipped` stays terminal and no credit is ever clawed back.\n\nBuild to R14's ADR. `modules/lifecycle/cascade.js` already implements ship-generates-work and declares an EMPTY rule set — this is the first rule, so expect the mechanism to be sound and unexercised.\n\n**Do not make this a verdict.** The owner chose propose-then-confirm precisely because refactors, renames and file moves all look like deletions to a diff. A rule that marks work removed on its own evidence will dim the sky for reasons that are not real. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
134
|
+
"kind": "feature",
|
|
135
|
+
"discipline": "engineer",
|
|
136
|
+
"priority": 4,
|
|
137
|
+
"criterion_ids": [
|
|
138
|
+
"ideator-sky-tells-the-truth"
|
|
139
|
+
],
|
|
140
|
+
"deps": [
|
|
141
|
+
"R14"
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"ref": "R21",
|
|
146
|
+
"title": "BV2.R12 — Substance floors for the new Full Idea fields",
|
|
147
|
+
"description": "Floors chosen deliberately for each new field, because the completeness score feeds credits.\n\n`modules/ideas/completeness.js` scores against the per-field `substanceMin` declared in the template; `base-v1` used a flat 40 characters for all five. The new fields are not interchangeable — \"scope as best as you can here\" and \"what made you have the idea\" do not deserve the same floor.\n\n**The never-invent-a-field rule gets stronger here, not weaker** (the Ideator pack). The new set is deliberately resistant to ghost-writing: nobody but the filer can answer what made them have the idea. A floor that pushes an agent to pad a thin answer destroys exactly that property. A short answer scores short — that is the scorer working. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
148
|
+
"kind": "feature",
|
|
149
|
+
"discipline": "engineer",
|
|
150
|
+
"priority": 3,
|
|
151
|
+
"criterion_ids": [
|
|
152
|
+
"ideator-full-idea-shapes-the-space"
|
|
153
|
+
],
|
|
154
|
+
"deps": [
|
|
155
|
+
"R20"
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"ref": "R22",
|
|
160
|
+
"title": "BV2.R13 — A Full Idea carries a recommendation: goal or criterion",
|
|
161
|
+
"description": "The recommendation is carried from filing through to the board item and shown to voters.\n\n**The ideator does not decide** (spec decision 9). They recommend; the board decides. The reasoning the owner accepted is that scope is the thing you learn last — the person who had the thought writes it, the people reading it judge how big it turned out to be.\n\nThe board item payload is `boardWindowPayload` in `modules/ideas/routes/inbox.js`; the section labels the Board Room renders are a hand-mirror of the template's field keys (`modules/hall-ui/public/board-room.js`, test-pinned) — so a template change and a board change move together. **The Board Room's own flow is unchanged.** Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
162
|
+
"kind": "feature",
|
|
163
|
+
"discipline": "engineer",
|
|
164
|
+
"priority": 4,
|
|
165
|
+
"criterion_ids": [
|
|
166
|
+
"ideator-full-idea-shapes-the-space"
|
|
167
|
+
],
|
|
168
|
+
"deps": [
|
|
169
|
+
"R20"
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"ref": "R12",
|
|
174
|
+
"title": "BV2.R14 — Size is the sum of those payouts",
|
|
175
|
+
"description": "Computable from the ledger for any idea. Compressed for drawing, with the true figure available.\n\n**Size is credits, not karma** (spec decision 5). `karma_log` carries no task key and no idea key — `migrations/040_builder_karma.sql` states that this is deliberate — so \"the karma a thought caused\" is not a quantity that can be computed. Credits are already idea-attributed at ship, so they can.\n\nSize never falls. That is what makes it independent of brightness, and what lets a thought whose work was torn out still render large. Sums are long-tailed, so compress for drawing and show the real number in the panel. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
176
|
+
"kind": "feature",
|
|
177
|
+
"discipline": "engineer",
|
|
178
|
+
"priority": 4,
|
|
179
|
+
"criterion_ids": [
|
|
180
|
+
"ideator-tag-survives-the-fork"
|
|
181
|
+
],
|
|
182
|
+
"deps": [
|
|
183
|
+
"R11"
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"ref": "R10",
|
|
188
|
+
"title": "BV2.R15 — The descent surface on the idea detail page",
|
|
189
|
+
"description": "One thought at the top, everything it caused falling away below — including the branches that went out.\n\n`/idea/:id` (`modules/hall-ui/public/idea.js`) is the canonical per-idea record and stays; this is its new section. It already renders a one-line \"became task N\" outcome, which this replaces with the full story.\n\nDone-when: a person can trace a thing shipped months later back to the line that caused it, through rewrites, splits and merges. See `docs/design/mocks/ideator-surface/Descent.dc.html` for the agreed shape — it is a shape, not a pixel contract. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
190
|
+
"kind": "feature",
|
|
191
|
+
"discipline": "ui",
|
|
192
|
+
"priority": 4,
|
|
193
|
+
"criterion_ids": [
|
|
194
|
+
"ideator-tag-survives-the-fork"
|
|
195
|
+
],
|
|
196
|
+
"deps": [
|
|
197
|
+
"R09"
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"ref": "R15a",
|
|
202
|
+
"title": "BV2.R16 — Confirming or dismissing a removal proposal",
|
|
203
|
+
"description": "On the existing awaiting-nod surface, at the same floor as the nod (Metic+, `task.promote`). No new permission and no new page.\n\nAn unanswered proposal **rots on the normal clock** (spec decision 14) rather than accumulating — `modules/lifecycle/rot.js` and the `GET /inbox/rotting` feed already exist and this rides them.\n\nThe surface is `modules/hall-ui/public/goal-inbox.js`, which already renders the awaiting-nod queue and the rot feed side by side. Confirming is what makes brightness fall; dismissing is what stops a refactor from dimming the sky. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
204
|
+
"kind": "feature",
|
|
205
|
+
"discipline": "engineer",
|
|
206
|
+
"priority": 4,
|
|
207
|
+
"criterion_ids": [
|
|
208
|
+
"ideator-sky-tells-the-truth"
|
|
209
|
+
],
|
|
210
|
+
"deps": [
|
|
211
|
+
"R15"
|
|
212
|
+
]
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"ref": "R23",
|
|
216
|
+
"title": "BV2.R17 — Ratify a Full Idea into a goal",
|
|
217
|
+
"description": "The goal half beside the existing criterion half. One transaction. The five answers land on the goal.\n\n**The criterion half already exists** and is the model to follow: `modules/ideas/inbox.js` `ratifyCriterionProposal` writes `done_when_criteria` + the idea status + karma + the audit row in a single transaction, and `POST /inbox/:id/ratify` fronts it.\n\n**Nothing today turns a filed thought into a goal** — goals come from a separate planning act — so this is the genuinely new path of the four in this criterion. `POST /goals` validates `scope_modules` against the live roster and escalates to Archon for a protected scope; a ratification path has to respect both. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
218
|
+
"kind": "feature",
|
|
219
|
+
"discipline": "engineer",
|
|
220
|
+
"priority": 3,
|
|
221
|
+
"criterion_ids": [
|
|
222
|
+
"ideator-full-idea-shapes-the-space"
|
|
223
|
+
],
|
|
224
|
+
"deps": [
|
|
225
|
+
"R22"
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"ref": "R12a",
|
|
230
|
+
"title": "BV2.R18 — Backfill lineage for existing ideas, without backfilling money",
|
|
231
|
+
"description": "Pre-existing ideas carry a size computed from the descent they actually have. No credits are paid retroactively.\n\n**And the consequence is surfaced, not hidden** (spec decision 17). For those ideas size is NOT a sum of payouts actually made, so it needs a historical marker or the number quietly lies about what it means. The owner chose this deliberately: it makes the sky meaningful on day one without spending credits again on work already paid for once.\n\nDone-when: every idea filed before this shipped has a size, and anywhere that size is shown makes clear which kind of number it is. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
232
|
+
"kind": "feature",
|
|
233
|
+
"discipline": "engineer",
|
|
234
|
+
"priority": 4,
|
|
235
|
+
"criterion_ids": [
|
|
236
|
+
"ideator-tag-survives-the-fork"
|
|
237
|
+
],
|
|
238
|
+
"deps": [
|
|
239
|
+
"R12"
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"ref": "R03",
|
|
244
|
+
"title": "BV2.R19 — The room",
|
|
245
|
+
"description": "A plain overview of one's own thinking: what was filed, what it became, its size, and whether it still burns.\n\n**This is the page a person opens daily**, and it does the real work — the sky is ambient, the room is useful. It is deliberately unglamorous: rows, a size, a state.\n\n**It is the author's own view. The profile keeps a compact public trail** (spec decision 19) — the two are not the same surface. `modules/hall-ui/public/profile.js` already renders an idea trail from `GET /inbox/by-builder/:id`; that stays as what someone else sees on you.\n\nSee `docs/design/mocks/ideator-surface/Room.dc.html` for the agreed shape. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
246
|
+
"kind": "feature",
|
|
247
|
+
"discipline": "ui",
|
|
248
|
+
"priority": 3,
|
|
249
|
+
"criterion_ids": [
|
|
250
|
+
"ideator-nothing-is-lost"
|
|
251
|
+
],
|
|
252
|
+
"deps": [
|
|
253
|
+
"R12"
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"ref": "R16",
|
|
258
|
+
"title": "BV2.R20 — Brightness — the live share of a descent",
|
|
259
|
+
"description": "Computable per idea, and it falls when a removal is confirmed.\n\n**Graded, not binary** (spec decision 20): a thought whose descent is five things with three still alive is partly lit, and goes out only when nothing survives. That is what makes a star visibly dim as the project removes pieces of what it caused.\n\n**Brightness never claws back credits.** The ledger is not rewritten when the project changes its mind — only the picture changes. Size, which R12 owns, does not move. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
260
|
+
"kind": "feature",
|
|
261
|
+
"discipline": "engineer",
|
|
262
|
+
"priority": 4,
|
|
263
|
+
"criterion_ids": [
|
|
264
|
+
"ideator-sky-tells-the-truth"
|
|
265
|
+
],
|
|
266
|
+
"deps": [
|
|
267
|
+
"R15a",
|
|
268
|
+
"R12"
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"ref": "R06",
|
|
273
|
+
"title": "BV2.R21 — Proof: a thought filed from the hall is traceable an hour later without asking anyone",
|
|
274
|
+
"description": "**The live demonstration that `ideator-nothing-is-lost` is met.**\n\nA person files a thought, leaves, comes back later, and finds what became of it unaided — no asking, no database, no one else's help. If they filed it aimed, they can see which goal caught it; if they let it float, they can see that nothing has claimed it yet.\n\nThis is the exact scenario that failed on 2026-09-19 and produced this whole scope: a thought was filed, it silently became a task under a goal, and the person who filed it could not find it five minutes later. The proof is that this can no longer happen. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
275
|
+
"kind": "verify",
|
|
276
|
+
"discipline": "engineer",
|
|
277
|
+
"priority": 3,
|
|
278
|
+
"criterion_ids": [
|
|
279
|
+
"ideator-nothing-is-lost"
|
|
280
|
+
],
|
|
281
|
+
"deps": [
|
|
282
|
+
"R01",
|
|
283
|
+
"R03"
|
|
284
|
+
]
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"ref": "R06a",
|
|
288
|
+
"title": "BV2.R22 — Proof: the one-line path did not get slower",
|
|
289
|
+
"description": "**The guard on spec decision 18**, which is the decision in this scope carrying the most risk.\n\nFiling a spark takes the same gestures and the same time it does today, with the Full Idea form present on the same surface. Measure it against today's path, not against an impression.\n\nA spark's whole value is that it costs nothing to have. If the fast path picks up an extra step, a scroll, or a decision, this scope will have built something beautiful that people stop using — and they will go back to firing one-liners into Discord. A green build is not the evidence here; a timed comparison is. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
290
|
+
"kind": "verify",
|
|
291
|
+
"discipline": "engineer",
|
|
292
|
+
"priority": 3,
|
|
293
|
+
"criterion_ids": [
|
|
294
|
+
"ideator-nothing-is-lost"
|
|
295
|
+
],
|
|
296
|
+
"deps": [
|
|
297
|
+
"R04"
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"ref": "R13",
|
|
302
|
+
"title": "BV2.R23 — Proof: a task split twice pays its origin at the decayed rate and appears in the descent",
|
|
303
|
+
"description": "**The live demonstration that `ideator-tag-survives-the-fork` is met.**\n\nA real thought becomes a real task; that task is split; one of its children is split again; each ships. The origin is paid at the decayed rate at every hop, and the whole chain reads as one descent.\n\nThis is the criterion everything else stands on. If the tag dies at the first fork, the descent is a link, the size is wrong, and the sky is decoration over a number that does not mean anything. Demonstrate it on real work, not a fixture. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
304
|
+
"kind": "verify",
|
|
305
|
+
"discipline": "engineer",
|
|
306
|
+
"priority": 3,
|
|
307
|
+
"criterion_ids": [
|
|
308
|
+
"ideator-tag-survives-the-fork"
|
|
309
|
+
],
|
|
310
|
+
"deps": [
|
|
311
|
+
"R10",
|
|
312
|
+
"R12"
|
|
313
|
+
]
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"ref": "R17",
|
|
317
|
+
"title": "BV2.R24 — The five states resolve from ledger facts",
|
|
318
|
+
"description": "star / protostar / satellite / remnant / cold body — each DERIVED, none a UI guess.\n\n- **star** — caused work that is still alive\n- **protostar** — filed, not yet ignited. Include the well-vs-open-space distinction: caught by a goal and awaiting a nod is a different state from claimed by nothing, and position is what carries it (spec decision, direction doc)\n- **satellite** — folded into another thought; its value counts there\n- **remnant** — it burned, then the project removed it; it keeps its size\n- **cold body** — declined at triage; it never ignited, so it has no size at all\n\nDone-when: every state is computed from the ledger, and no surface has to infer one. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
319
|
+
"kind": "feature",
|
|
320
|
+
"discipline": "engineer",
|
|
321
|
+
"priority": 4,
|
|
322
|
+
"criterion_ids": [
|
|
323
|
+
"ideator-sky-tells-the-truth"
|
|
324
|
+
],
|
|
325
|
+
"deps": [
|
|
326
|
+
"R16"
|
|
327
|
+
]
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"ref": "R24",
|
|
331
|
+
"title": "BV2.R25 — Proof: a Full Idea ratified as a goal produces a goal carrying its five answers",
|
|
332
|
+
"description": "**The live demonstration that `ideator-full-idea-shapes-the-space` is met.**\n\nA Full Idea is written through the new five questions, carries a recommendation, goes to the Board Room unchanged, is ratified as a GOAL, and the resulting goal carries those five answers natively — scope from \"scope as best as you can here\", done-when from \"what does a successful implementation look like\".\n\nRun it end to end through the real Board Room. The point of this criterion is that an ideator's best work becomes the structure other people's thinking lands in; a goal that arrives without its answers has not demonstrated that. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
333
|
+
"kind": "verify",
|
|
334
|
+
"discipline": "engineer",
|
|
335
|
+
"priority": 3,
|
|
336
|
+
"criterion_ids": [
|
|
337
|
+
"ideator-full-idea-shapes-the-space"
|
|
338
|
+
],
|
|
339
|
+
"deps": [
|
|
340
|
+
"R23"
|
|
341
|
+
]
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"ref": "R18",
|
|
345
|
+
"title": "BV2.R26 — The sky",
|
|
346
|
+
"description": "The ambient view over the five states: size compressed, brightness live, goals as gravity wells and their criteria as the rings inside them.\n\n**It arrives last on purpose.** By this point it draws numbers and states the rest of the work has already produced, which is what keeps it cheap. A galaxy is not trivial, and this scope only earns it because nothing else depends on it.\n\n**It is never the only path to anything it shows** (spec decision 11). Every fact in the sky is reachable from the room or the descent.\n\nSee `docs/design/mocks/ideator-surface/Sky.dc.html`, and `docs/design/ideator-surface-direction.md` for why the project's own design system — a black void and one accent that lights only what is live — already supplies this. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
347
|
+
"kind": "feature",
|
|
348
|
+
"discipline": "ui",
|
|
349
|
+
"priority": 3,
|
|
350
|
+
"criterion_ids": [
|
|
351
|
+
"ideator-sky-tells-the-truth"
|
|
352
|
+
],
|
|
353
|
+
"deps": [
|
|
354
|
+
"R17",
|
|
355
|
+
"R03"
|
|
356
|
+
]
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"ref": "R19",
|
|
360
|
+
"title": "BV2.R27 — Proof: a large dead thought renders large and dead",
|
|
361
|
+
"description": "**The live demonstration that `ideator-sky-tells-the-truth` is met**, and the single scenario this criterion exists for.\n\nA thought that caused substantial work which the project has since removed shows at **full size and zero brightness**, and is distinguishable at a glance from a thought that never caused anything at all.\n\nThat distinction is the whole reason size and brightness are two channels instead of one number. If those two render alike, the criterion is not met however green the build is. Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.",
|
|
362
|
+
"kind": "verify",
|
|
363
|
+
"discipline": "engineer",
|
|
364
|
+
"priority": 3,
|
|
365
|
+
"criterion_ids": [
|
|
366
|
+
"ideator-sky-tells-the-truth"
|
|
367
|
+
],
|
|
368
|
+
"deps": [
|
|
369
|
+
"R18"
|
|
370
|
+
]
|
|
371
|
+
}
|
|
372
|
+
]
|
|
373
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// tests/fixtures/seed-tasks/fake-api.mjs — an in-memory stand-in for the four Bongos routes a
|
|
2
|
+
// task seeder writes to (task 1004979). It records every request so tests/seed_tasks.mjs can
|
|
3
|
+
// compare what two seeders SENT, and models just enough of the server to answer them the way
|
|
4
|
+
// production does: string task ids, POST /tasks idempotent on (source, source_ref) with
|
|
5
|
+
// `idempotent: true` on a replay, and GET /goals/:id criteria rows carrying linked_tasks.
|
|
6
|
+
|
|
7
|
+
import http from 'node:http';
|
|
8
|
+
|
|
9
|
+
export async function startFakeApi({ firstId = 2000001, goalCriteria = {} } = {}) {
|
|
10
|
+
const requests = [];
|
|
11
|
+
const tasks = []; // rows in creation order
|
|
12
|
+
const links = new Map(); // goalId -> Map(criterion -> count)
|
|
13
|
+
for (const [gid, crits] of Object.entries(goalCriteria)) links.set(String(gid), new Map(Object.entries(crits)));
|
|
14
|
+
let nextId = firstId;
|
|
15
|
+
|
|
16
|
+
const server = http.createServer((req, res) => {
|
|
17
|
+
let raw = '';
|
|
18
|
+
req.on('data', (c) => { raw += c; });
|
|
19
|
+
req.on('end', () => {
|
|
20
|
+
const body = raw ? JSON.parse(raw) : null;
|
|
21
|
+
const url = req.url.replace(/\?.*$/, '');
|
|
22
|
+
requests.push({ method: req.method, path: url, body });
|
|
23
|
+
const send = (status, obj) => { res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(obj)); };
|
|
24
|
+
let m;
|
|
25
|
+
if (req.method === 'POST' && url === '/api/bongos/tasks') {
|
|
26
|
+
const prior = tasks.find((t) => t.source === body.source && t.source_ref === body.source_ref);
|
|
27
|
+
if (prior) return send(200, { task: { id: prior.id }, idempotent: true });
|
|
28
|
+
const row = { ...body, id: String(nextId++), deps: [], promoted: false };
|
|
29
|
+
tasks.push(row);
|
|
30
|
+
const g = links.get(String(body.goal_id)) || new Map();
|
|
31
|
+
for (const c of body.criterion_ids || []) g.set(c, (g.get(c) || 0) + 1);
|
|
32
|
+
links.set(String(body.goal_id), g);
|
|
33
|
+
return send(201, { task: { id: row.id, status: 'backlog' } });
|
|
34
|
+
}
|
|
35
|
+
if (req.method === 'POST' && (m = url.match(/^\/api\/bongos\/tasks\/(\d+)\/dependencies$/))) {
|
|
36
|
+
const row = tasks.find((t) => t.id === m[1]);
|
|
37
|
+
if (!row) return send(404, { error: { code: 'not_found' } });
|
|
38
|
+
row.deps.push(...body.depends_on_task_ids.map(String));
|
|
39
|
+
return send(200, { added: body.depends_on_task_ids.length });
|
|
40
|
+
}
|
|
41
|
+
if (req.method === 'POST' && (m = url.match(/^\/api\/bongos\/tasks\/(\d+)\/promote$/))) {
|
|
42
|
+
const row = tasks.find((t) => t.id === m[1]);
|
|
43
|
+
if (!row) return send(404, { error: { code: 'not_found' } });
|
|
44
|
+
row.promoted = true;
|
|
45
|
+
return send(200, { task: { id: row.id, status: 'ready' } });
|
|
46
|
+
}
|
|
47
|
+
if (req.method === 'PATCH' && /^\/api\/bongos\/(tasks|inbox)\/\d+$/.test(url)) return send(200, { ok: true });
|
|
48
|
+
if (req.method === 'GET' && (m = url.match(/^\/api\/bongos\/goals\/(\d+)$/))) {
|
|
49
|
+
const g = links.get(m[1]) || new Map();
|
|
50
|
+
return send(200, { goal: { id: m[1] }, criteria: [...g].map(([criterion_id, linked_tasks]) => ({ criterion_id, linked_tasks })) });
|
|
51
|
+
}
|
|
52
|
+
return send(404, { error: { code: 'no_route', path: url } });
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
await new Promise((r) => server.listen(0, '127.0.0.1', r));
|
|
56
|
+
const base = `http://127.0.0.1:${server.address().port}`;
|
|
57
|
+
return {
|
|
58
|
+
base, requests, tasks,
|
|
59
|
+
writes: () => requests.filter((r) => r.method !== 'GET'),
|
|
60
|
+
close: () => new Promise((r) => server.close(r)),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The transport seed-tasks.js's seedTasks() takes, pointed at a fake server.
|
|
65
|
+
export function fetchApi(base) {
|
|
66
|
+
return async (method, p, body) => {
|
|
67
|
+
const res = await fetch(`${base}/api/bongos${p}`, {
|
|
68
|
+
method, headers: { 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined,
|
|
69
|
+
});
|
|
70
|
+
const text = await res.text();
|
|
71
|
+
return { status: res.status, ok: res.ok, json: text ? JSON.parse(text) : {} };
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Order-insensitive object keys, order-sensitive arrays: the canonical text of a write log.
|
|
76
|
+
export function canonical(v) {
|
|
77
|
+
if (Array.isArray(v)) return `[${v.map(canonical).join(',')}]`;
|
|
78
|
+
if (v && typeof v === 'object') return `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`).join(',')}}`;
|
|
79
|
+
return JSON.stringify(v);
|
|
80
|
+
}
|
|
@@ -106,6 +106,40 @@ test('the app creds come back out of the web.env the runner itself writes', () =
|
|
|
106
106
|
assert.equal(c.clientId, 'Iv23liABCDEF123456');
|
|
107
107
|
assert.equal(c.pem.trim(), PEM.trim(), 'the one-line pem is restored to real newlines');
|
|
108
108
|
assert.deepEqual(RUN.appCredsFromEnv(''), { appId: null, pem: null, clientId: null });
|
|
109
|
+
const unquoted = ENV.replace(/^GITHUB_APP_PRIVATE_KEY="(.*)"$/m, 'GITHUB_APP_PRIVATE_KEY=$1');
|
|
110
|
+
assert.notEqual(unquoted, ENV);
|
|
111
|
+
assert.equal(RUN.appCredsFromEnv(unquoted).pem.trim(), PEM.trim(), 'a web.env written before task 1005009 still reads');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// One KEY=VALUE line as systemd's EnvironmentFile hands it to the process (env-file.c):
|
|
115
|
+
// unquoted, a backslash escapes the next character and is dropped; inside double quotes
|
|
116
|
+
// it survives unless it escapes one of " \ ` $. The runner's own parser above is not the
|
|
117
|
+
// reader that matters — the SERVER gets the key from systemd (task 1005009).
|
|
118
|
+
function systemdEnvValue(body, key) {
|
|
119
|
+
const line = body.split('\n').find((l) => l.startsWith(`${key}=`));
|
|
120
|
+
const raw = line.slice(key.length + 1);
|
|
121
|
+
let out = '';
|
|
122
|
+
if (raw.startsWith('"')) {
|
|
123
|
+
for (let i = 1; i < raw.length && raw[i] !== '"'; i++) {
|
|
124
|
+
if (raw[i] === '\\' && i + 1 < raw.length) out += '"\\`$'.includes(raw[++i]) ? raw[i] : `\\${raw[i]}`;
|
|
125
|
+
else out += raw[i];
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
for (let i = 0; i < raw.length; i++) out += raw[i] === '\\' && i + 1 < raw.length ? raw[++i] : raw[i];
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
test('the SERVER can sign with the key systemd hands it from the web.env we write (task 1005009)', () => {
|
|
134
|
+
const GH = require(path.join(ROOT, 'modules', 'lifecycle', 'github-push.js'));
|
|
135
|
+
const sign = (pem) => GH.mintAppJwt({ appId: 5189633, privateKeyPem: pem, nowSec: 1_800_000_000 });
|
|
136
|
+
const jwt = sign(systemdEnvValue(ENV, 'GITHUB_APP_PRIVATE_KEY'));
|
|
137
|
+
const [h, b, s] = jwt.split('.');
|
|
138
|
+
assert.equal(crypto.createVerify('RSA-SHA256').update(`${h}.${b}`).verify(publicKey, Buffer.from(s, 'base64url')), true);
|
|
139
|
+
// The shape every hosted project carried until this task: systemd turns each \n into n.
|
|
140
|
+
const unquoted = ENV.replace(/^GITHUB_APP_PRIVATE_KEY="(.*)"$/m, 'GITHUB_APP_PRIVATE_KEY=$1');
|
|
141
|
+
assert.match(systemdEnvValue(unquoted, 'GITHUB_APP_PRIVATE_KEY'), /^-----BEGIN RSA PRIVATE KEY-----nM/);
|
|
142
|
+
assert.throws(() => sign(systemdEnvValue(unquoted, 'GITHUB_APP_PRIVATE_KEY')), 'the unquoted line is the bug this pins');
|
|
109
143
|
});
|
|
110
144
|
|
|
111
145
|
test('the app JWT is RS256, signed by the app key, issued by the app id, under ten minutes', () => {
|
|
@@ -27,6 +27,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
27
27
|
|
|
28
28
|
// A valid key BEFORE requiring the modules, so secret-box reads as configured.
|
|
29
29
|
process.env.BUILDER_SECRET_KEY = 'ab'.repeat(32);
|
|
30
|
+
// Pre-launch OFF unless a case turns it on (task 1004998): a password in the
|
|
31
|
+
// runner's env must not silently turn every flip case into a refusal.
|
|
32
|
+
delete process.env.PRELAUNCH_PASSWORD; delete process.env.STEALTH_PASSWORD;
|
|
30
33
|
|
|
31
34
|
const require = createRequire(import.meta.url);
|
|
32
35
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -150,6 +153,23 @@ test('POST visibility=public: flips via makeRepoPublic and forwards GitHub\'s an
|
|
|
150
153
|
} finally { s.restore(); }
|
|
151
154
|
});
|
|
152
155
|
|
|
156
|
+
test('POST visibility=public in PRE-LAUNCH: refused 409 public_repo_prelaunch, nothing flipped (task 1004998)', async () => {
|
|
157
|
+
const s = stub({ makePublic: () => ({ private: false }) });
|
|
158
|
+
const saved = process.env.PRELAUNCH_PASSWORD;
|
|
159
|
+
process.env.PRELAUNCH_PASSWORD = 'shh';
|
|
160
|
+
try {
|
|
161
|
+
const res = fakeRes();
|
|
162
|
+
await postHandler()({ builder: { id: 1 }, body: { repo: 'jaxri/mercury', visibility: 'public' } }, res);
|
|
163
|
+
assert.equal(res._status, 409);
|
|
164
|
+
assert.equal(res._body.error.code, 'public_repo_prelaunch');
|
|
165
|
+
assert.match(res._body.error.message, /pre-launch/);
|
|
166
|
+
assert.equal(s.calls.flip.length, 0, 'GitHub is never asked to flip');
|
|
167
|
+
} finally {
|
|
168
|
+
if (saved === undefined) delete process.env.PRELAUNCH_PASSWORD; else process.env.PRELAUNCH_PASSWORD = saved;
|
|
169
|
+
s.restore();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
153
173
|
test('POST visibility=public: GitHub\'s 403 becomes not_repo_admin, said plainly', async () => {
|
|
154
174
|
const s = stub({ makePublic: () => { throw new GithubApiError('no', { status: 403, code: 'not_repo_admin' }); } });
|
|
155
175
|
try {
|
|
@@ -60,6 +60,34 @@ function goalHandler({ goal, counts }) {
|
|
|
60
60
|
// =========================================================================
|
|
61
61
|
// achieveGoalIfComplete — the happy path: last criterion confirmed → achieved
|
|
62
62
|
// =========================================================================
|
|
63
|
+
// Task 1001693: the manual door books the goal-achievement karma too, but only
|
|
64
|
+
// for the caller whose conditional flip actually moved the row.
|
|
65
|
+
const seams = require('../src/module-seams.js');
|
|
66
|
+
const reward = require('../modules/economy/reward.js');
|
|
67
|
+
if (!seams.hasProvider('reward')) seams.registerProvider('reward', reward);
|
|
68
|
+
async function countingAward(fn) {
|
|
69
|
+
const original = reward.awardGoalAchievementKarma;
|
|
70
|
+
const calls = [];
|
|
71
|
+
reward.awardGoalAchievementKarma = async (_exec, { goalId }) => { calls.push(Number(goalId)); return { awarded: 1 }; };
|
|
72
|
+
try { await fn(); } finally { reward.awardGoalAchievementKarma = original; }
|
|
73
|
+
return calls;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await test('achieveGoalIfComplete: the caller that flips the goal books its karma once', async () => {
|
|
77
|
+
const pool = fakePool(goalHandler({ goal: GOAL(), counts: { total: 1, unsatisfied: 0 } }));
|
|
78
|
+
const calls = await countingAward(() => achieveGoalIfComplete({ goalId: 7 }, { pool }));
|
|
79
|
+
assert.deepEqual(calls, [7]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await test('achieveGoalIfComplete: a caller that loses the flip race books nothing, and still reports achieved', async () => {
|
|
83
|
+
const base = goalHandler({ goal: GOAL(), counts: { total: 1, unsatisfied: 0 } });
|
|
84
|
+
const pool = fakePool((s) => (/^UPDATE goals/.test(s) ? { rows: [] } : base(s)));
|
|
85
|
+
let out;
|
|
86
|
+
const calls = await countingAward(async () => { out = await achieveGoalIfComplete({ goalId: 7 }, { pool }); });
|
|
87
|
+
assert.deepEqual(calls, []);
|
|
88
|
+
assert.equal(out.achieved, true);
|
|
89
|
+
});
|
|
90
|
+
|
|
63
91
|
await test('achieveGoalIfComplete: open goal, all criteria satisfied → flips to achieved', async () => {
|
|
64
92
|
const pool = fakePool(goalHandler({ goal: GOAL(), counts: { total: 3, unsatisfied: 0 } }));
|
|
65
93
|
const out = await achieveGoalIfComplete({ goalId: 7 }, { pool });
|
|
@@ -67,7 +95,8 @@ await test('achieveGoalIfComplete: open goal, all criteria satisfied → flips t
|
|
|
67
95
|
assert.equal(out.goal.status, 'achieved', 'returns the updated row');
|
|
68
96
|
const upd = find(pool.queries, /^UPDATE goals/);
|
|
69
97
|
assert.ok(upd, 'the status flip must be persisted');
|
|
70
|
-
|
|
98
|
+
// Conditional on still reading `open` (task 1001693), so only one caller wins the flip.
|
|
99
|
+
assert.deepEqual(upd.params, [7, 'achieved', 'open'], 'UPDATE binds (goalId, "achieved", from "open")');
|
|
71
100
|
});
|
|
72
101
|
|
|
73
102
|
// =========================================================================
|