@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
|
@@ -53,6 +53,7 @@ const REFUSALS = {
|
|
|
53
53
|
not_permitted: 'your rank does not hold autonomy.run, so your runner may not work on this project',
|
|
54
54
|
builder_paused: 'the owner paused your runner',
|
|
55
55
|
no_builder_scope: 'you have not picked any goals for your runner — choose them in Settings',
|
|
56
|
+
pin_move: 'the worker would be told to move a live core pin, and going live always waits for a person',
|
|
56
57
|
};
|
|
57
58
|
|
|
58
59
|
// The stop codes effectiveFence may set, and the refusal each one becomes.
|
|
@@ -361,4 +362,66 @@ function decideTask({ task, goals = [], protectedHits = [] } = {}) {
|
|
|
361
362
|
return { go: true, code: null, reason: null };
|
|
362
363
|
}
|
|
363
364
|
|
|
364
|
-
|
|
365
|
+
// PIN MOVES — "land only, ask before going live" (task 1004974).
|
|
366
|
+
//
|
|
367
|
+
// An unattended worker may land work; moving a hosted project's live core pin
|
|
368
|
+
// always waits for a person. refusePinMove is asked over everything the runner
|
|
369
|
+
// HANDS a worker — its prompt and the command lines it is told to run — and
|
|
370
|
+
// refuses when any of them names a pin-moving door. Each pattern is one door:
|
|
371
|
+
//
|
|
372
|
+
// core-upgrade / core_upgrade POST /provisioning/instances/:id/core-upgrade, its
|
|
373
|
+
// provision-core-upgrade.js runner and ledger
|
|
374
|
+
// update-channel the rule that decides which moves the runner makes
|
|
375
|
+
// core.pin.move the atom every other pin door sits behind
|
|
376
|
+
// /core-update, software-update the platform's own update request routes
|
|
377
|
+
// render-deploy/* the Render deploy act and its app settings
|
|
378
|
+
// npm-release/* publishing a core release, which a patch-channel
|
|
379
|
+
// project's pin then follows on its own
|
|
380
|
+
// bongos upgrade the CLI that performs the move on a host
|
|
381
|
+
// "pin move" / "move the pin" the act named in words
|
|
382
|
+
//
|
|
383
|
+
// A NAMED LIST, KEPT HONEST BY A DERIVED TEST. A denylist admits every door added
|
|
384
|
+
// after it. tests/autobongos_tweak_lane.mjs therefore enumerates every MUTATING
|
|
385
|
+
// route gated on core.pin.move or the owner-or-pin gate, straight from the route
|
|
386
|
+
// files, and fails when one is not matched here. A new pin door reds the build
|
|
387
|
+
// until it is named.
|
|
388
|
+
//
|
|
389
|
+
// THIS IS THE RUNNER NOT ASKING, NOT THE WALL. The worker carries its builder's
|
|
390
|
+
// own CLI session, and the server cannot tell an unattended worker from that
|
|
391
|
+
// builder at a terminal, so a worker that wanders past its prompt (or paraphrases
|
|
392
|
+
// past these patterns) meets only the route's own gate, which today ADMITS the
|
|
393
|
+
// owner's and an Archon's session. That server-side wall is out of this task's
|
|
394
|
+
// scope by the owner's instruction. It is tracked as private security report
|
|
395
|
+
// 1000030 and pinned by the todo tests in tests/autobongos_tweak_pin_identity.mjs.
|
|
396
|
+
// This check guarantees one narrower thing: the runner is never the one that asked.
|
|
397
|
+
const PIN_MOVE_PATTERNS = Object.freeze([
|
|
398
|
+
{ door: 'core-upgrade', re: /core[-_ ]?upgrade/i },
|
|
399
|
+
{ door: 'update-channel', re: /update[-_ ]channel/i },
|
|
400
|
+
{ door: 'npm-release', re: /npm-release\//i },
|
|
401
|
+
{ door: 'core.pin.move', re: /core\.pin\.move/i },
|
|
402
|
+
{ door: 'core-update', re: /\/core-update\b|software-update/i },
|
|
403
|
+
{ door: 'render-deploy', re: /render-deploy\//i },
|
|
404
|
+
{ door: 'bongos upgrade', re: /\bbongos\s+upgrade\b/i },
|
|
405
|
+
{ door: 'pin move', re: /\bpin[-_ ]mov(?:e|es|ing)\b|\bmov(?:e|es|ing)\s+(?:the\s+|a\s+|its\s+)?(?:live\s+|core\s+)*pin\b/i },
|
|
406
|
+
]);
|
|
407
|
+
|
|
408
|
+
// Which doors does this text name? Pure; [] for anything that is not a string.
|
|
409
|
+
function pinMoveDoors(text) {
|
|
410
|
+
if (typeof text !== 'string' || !text) return [];
|
|
411
|
+
return PIN_MOVE_PATTERNS.filter((p) => p.re.test(text)).map((p) => p.door);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// refusePinMove — may this prompt and this list of command lines go to a worker?
|
|
415
|
+
// FAILS CLOSED: a prompt that is not a string, or a script list that is not an
|
|
416
|
+
// array of strings, refuses, because a hand-off the fence cannot read is one it
|
|
417
|
+
// cannot vouch for.
|
|
418
|
+
function refusePinMove({ prompt, scripts } = {}) {
|
|
419
|
+
if (typeof prompt !== 'string' || !Array.isArray(scripts) || scripts.some((s) => typeof s !== 'string')) {
|
|
420
|
+
return { go: false, code: 'pin_move', reason: `${REFUSALS.pin_move} (the prompt or script list could not be read)`, doors: [] };
|
|
421
|
+
}
|
|
422
|
+
const doors = [...new Set([prompt, ...scripts].flatMap(pinMoveDoors))];
|
|
423
|
+
if (doors.length) return { go: false, code: 'pin_move', reason: `${REFUSALS.pin_move} (${doors.join(', ')})`, doors };
|
|
424
|
+
return { go: true, code: null, reason: null, doors: [] };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
module.exports = { REFUSALS, normaliseFence, prioritise, allowedGoals, effectiveFence, decideRun, decideTask, needsHostAccess, PIN_MOVE_PATTERNS, pinMoveDoors, refusePinMove };
|
|
@@ -270,7 +270,31 @@ function stuckPageTweakNeeds(ctx) {
|
|
|
270
270
|
});
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
|
|
273
|
+
// ---- need: payout_holds (task 1004914, goal 1000092 N10; ADR 0385 D8) -------
|
|
274
|
+
//
|
|
275
|
+
// Pay you are owed is on hold — a sanctions check, an age rule, or a person's
|
|
276
|
+
// freeze. The owner's framework (cited by Article only) forbids withholding
|
|
277
|
+
// earned pay silently, so every open hold is a need here until it is lifted: it
|
|
278
|
+
// reaches the hall's Standing card and every session's nudge, the same shape as
|
|
279
|
+
// a waiting board vote. ctx.openPayoutHolds is { count, first_id }, read by core
|
|
280
|
+
// /me from src/bongos/payout-gates.js; absent means none.
|
|
281
|
+
function payoutHoldsNeed(ctx) {
|
|
282
|
+
const holds = ctx.openPayoutHolds;
|
|
283
|
+
const n = Number(holds && holds.count) || 0;
|
|
284
|
+
if (n <= 0) return null;
|
|
285
|
+
return {
|
|
286
|
+
id: 'payout_holds',
|
|
287
|
+
count: n,
|
|
288
|
+
title: n === 1 ? 'A payout to you is on hold' : 'Payouts to you are on hold',
|
|
289
|
+
what: 'Money you are owed cannot be paid yet. The notice says why and what clears it.',
|
|
290
|
+
state: 'action_needed',
|
|
291
|
+
severity: 'action',
|
|
292
|
+
message: 'Nothing you earned is lost while it is held. Your payout status lists each hold, why it was made and what clears it; if you think one is wrong, you can appeal and a person reviews it with you.',
|
|
293
|
+
action: null,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const NEEDS = [artKeyNeed, helpRequestsNeed, taskRecommendationsNeed, boardVotesNeed, mingleIntroductionsNeed, stuckPageTweakNeeds, payoutHoldsNeed];
|
|
274
298
|
|
|
275
299
|
// Priority WITHIN a state bucket (task 1003738). computeNeeds used to sort by
|
|
276
300
|
// state alone with a stable sort, so the NEEDS array order silently decided
|
|
@@ -296,6 +320,8 @@ const NEEDS = [artKeyNeed, helpRequestsNeed, taskRecommendationsNeed, boardVotes
|
|
|
296
320
|
const NEED_PRIORITY = {
|
|
297
321
|
founding_welcome: -1,
|
|
298
322
|
board_votes: 0,
|
|
323
|
+
// Money you are owed and cannot get outranks your own setup chores (task 1004914).
|
|
324
|
+
payout_holds: 0.5,
|
|
299
325
|
help_requests: 1,
|
|
300
326
|
art_key: 2,
|
|
301
327
|
task_recommendations: 3,
|
|
@@ -998,6 +998,11 @@ module.exports = function buildCopyDeskRouter() {
|
|
|
998
998
|
if (!readingPage) return;
|
|
999
999
|
const round = openRoundOf(await lifecycle.listPageTweakTasks({ pageId }), pageId);
|
|
1000
1000
|
const out = pageStatus.composeEditor({ page, readingPage, round, builderId: req.builder.id });
|
|
1001
|
+
// task 1004948: engineering work open on this page's files — information only,
|
|
1002
|
+
// so an artist knows to expect code changes. A missing port or failed read is [].
|
|
1003
|
+
out.engineering_active = typeof lifecycle.engineeringActiveOnPage === 'function'
|
|
1004
|
+
? await lifecycle.engineeringActiveOnPage(Array.isArray(page.files) ? page.files : [])
|
|
1005
|
+
: [];
|
|
1001
1006
|
res.set('Cache-Control', 'no-store');
|
|
1002
1007
|
res.json(out);
|
|
1003
1008
|
} catch (err) {
|
|
@@ -512,25 +512,37 @@ async function reconcileCraftAchievements(client, builderId, opts = {}) {
|
|
|
512
512
|
// backfillCraftAchievements — the part of the backfill a migration cannot do.
|
|
513
513
|
// migrations/core_270 backfills six badges from the ledger in SQL, but "A Whole
|
|
514
514
|
// Surface" needs the page inventory, which is a file the database never sees. So
|
|
515
|
-
// the lifecycle's periodic sweep (publish-reconciler.js) runs this
|
|
516
|
-
//
|
|
517
|
-
// reconcile, in id order, bounded.
|
|
518
|
-
//
|
|
519
|
-
|
|
515
|
+
// the lifecycle's periodic sweep (publish-reconciler.js) runs this hourly: every
|
|
516
|
+
// builder the craft ledger has paid SINCE `sinceId` (a credit_log id; 0 = all),
|
|
517
|
+
// through the craft-only reconcile, in id order, bounded. INCREMENTAL (task
|
|
518
|
+
// 1004965): it returns a `watermark`, the highest credit_log id it covered, and
|
|
519
|
+
// the caller passes it back next time, so a caught-up run reads only rows newer
|
|
520
|
+
// than the watermark (the primary key serves `id > $5`) and finds none. A run
|
|
521
|
+
// that hit the limit has not covered everything, so it keeps the old watermark
|
|
522
|
+
// rather than skip the builders past the limit. (A row committed late under a
|
|
523
|
+
// lower id than the watermark is not lost: every craft payment already runs
|
|
524
|
+
// reconcileAfterCraftCredit for its payee — this is the drain behind that hook,
|
|
525
|
+
// and a process restart rescans from 0.) Idempotent, so a rescan unlocks
|
|
526
|
+
// nothing twice. Returns { checked, unlocked, watermark }.
|
|
527
|
+
async function backfillCraftAchievements({ client = null, limit = 500, sinceId = 0 } = {}) {
|
|
520
528
|
const q = client || pool;
|
|
529
|
+
const since = Number(sinceId) > 0 ? Number(sinceId) : 0;
|
|
521
530
|
const { rows } = await q.query(
|
|
522
|
-
`SELECT
|
|
523
|
-
WHERE delta > 0 AND (reason LIKE $1 OR reason LIKE $2 OR reason LIKE $3 OR reason LIKE $4)
|
|
531
|
+
`SELECT builder_id, MAX(id) AS last_id FROM credit_log
|
|
532
|
+
WHERE id > $5 AND delta > 0 AND (reason LIKE $1 OR reason LIKE $2 OR reason LIKE $3 OR reason LIKE $4)
|
|
533
|
+
GROUP BY builder_id
|
|
524
534
|
ORDER BY builder_id
|
|
525
|
-
LIMIT $
|
|
526
|
-
[IDEA_SHIP_LIKE, IDEA_FULL_LIKE, IDEA_BOARD_LIKE, PAGE_LIKE, limit]
|
|
535
|
+
LIMIT $6`,
|
|
536
|
+
[IDEA_SHIP_LIKE, IDEA_FULL_LIKE, IDEA_BOARD_LIKE, PAGE_LIKE, since, limit]
|
|
527
537
|
);
|
|
528
538
|
let unlocked = 0;
|
|
529
539
|
for (const r of rows) {
|
|
530
540
|
const got = await reconcileAfterCraftCredit(q, r.builder_id);
|
|
531
541
|
unlocked += got.length;
|
|
532
542
|
}
|
|
533
|
-
|
|
543
|
+
const covered = rows.length < limit;
|
|
544
|
+
const watermark = covered ? rows.reduce((m, r) => Math.max(m, Number(r.last_id) || 0), since) : since;
|
|
545
|
+
return { checked: rows.length, unlocked, watermark };
|
|
534
546
|
}
|
|
535
547
|
|
|
536
548
|
// reconcileAfterCraftCredit — the craft writers' hook (task 1004438). An idea
|
|
@@ -57,6 +57,15 @@ const COST_WRITE_SURFACES = [
|
|
|
57
57
|
file: 'modules/agents/spawn.js',
|
|
58
58
|
builder_id: 'whoever REQUESTED the fire, or null for an event fire, which nobody asked for. Not the PAYER — that is payer_kind on the agents_runs row, a different question.',
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
// task 1004937 (ADR 0394 §3): the weekly Market Watch run mirrors each researched
|
|
62
|
+
// item's cost here, in-process, after market_watch_run_items.cost_usd (the
|
|
63
|
+
// accounting its monthly cap sums) is final. Keyed source_ref=item-<item id> under
|
|
64
|
+
// source=market-watch:<run id>, so a resumed run cannot double-post an item.
|
|
65
|
+
surface: 'modules/market-watch/run.js (weekly run) → reward port → db.logCost() (source=market-watch:<run id>)',
|
|
66
|
+
file: 'modules/market-watch/run.js',
|
|
67
|
+
builder_id: 'null — the project pays on its own Anthropic key, and nobody asked for a scheduled run.',
|
|
68
|
+
},
|
|
60
69
|
{
|
|
61
70
|
surface: 'migrations / direct DB',
|
|
62
71
|
file: '—',
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// modules/economy/cost-write-guard.js — who may book what through POST /cost
|
|
2
|
+
// (task 1004959, security report 1000029).
|
|
3
|
+
//
|
|
4
|
+
// WHY. The cost route took `source`, `task_id` and `amount_usd` on the caller's
|
|
5
|
+
// word. Two ledger readers trust those fields: the autonomous spend brake
|
|
6
|
+
// (`autonomousApiSpendMtd` sums api rows tagged `agent:`/`cron:`, against a
|
|
7
|
+
// ceiling of about $30) and the Treasury's per-goal pots (rows joined to a task's
|
|
8
|
+
// goal). So any builder could stop every agent for the month with one row, or
|
|
9
|
+
// charge spend to someone else's goal. Payouts were never exposed: cost-plus is
|
|
10
|
+
// priced from session uploads, not from cost_log.
|
|
11
|
+
//
|
|
12
|
+
// The rules, each one keyed to a real writer so no legitimate caller breaks:
|
|
13
|
+
// - `agent:` rows are written in-process by modules/agents/spawn.js and never
|
|
14
|
+
// over HTTP, so the route refuses them outright. `market-watch:` rows are the
|
|
15
|
+
// same kind: the weekly Market Watch run writes them in-process (task 1004937).
|
|
16
|
+
// - `cron:` rows come from session-cost.js on a scheduled run, which is set up
|
|
17
|
+
// by trusted builders, so the route takes them from Metic+ only.
|
|
18
|
+
// - a `task_id` must be a task the caller has claimed (now or before). The
|
|
19
|
+
// grader and the cost CLI both log against the caller's own claim. An Archon
|
|
20
|
+
// may name any task, for backfills.
|
|
21
|
+
// - one row is capped at MAX_ROW_USD, far above any real session (a long
|
|
22
|
+
// marathon session costs a few hundred dollars).
|
|
23
|
+
//
|
|
24
|
+
// Pure apart from the injected `callerHasClaimed` lookup, so every refusal is
|
|
25
|
+
// unit-tested without a database.
|
|
26
|
+
|
|
27
|
+
const MAX_ROW_USD = 5000;
|
|
28
|
+
|
|
29
|
+
// Written in-process only: `agent:` by modules/agents/spawn.js, `market-watch:` by the
|
|
30
|
+
// weekly Market Watch run (modules/market-watch/run.js, task 1004937). A row under
|
|
31
|
+
// either prefix that arrived over HTTP is a forgery of server spend.
|
|
32
|
+
const SERVER_ONLY_PREFIXES = Object.freeze(['agent:', 'market-watch:']);
|
|
33
|
+
const TRUSTED_ONLY_PREFIX = 'cron:';
|
|
34
|
+
|
|
35
|
+
// rankMeets(rank, floor) — the doorway's ladder check; `callerHasClaimed(taskId)`
|
|
36
|
+
// resolves true when the caller holds or held a claim on that task.
|
|
37
|
+
// Returns null when the write may go ahead, else { code, status, message }.
|
|
38
|
+
async function checkCostWrite({ source, taskId, rank, rankMeets, callerHasClaimed }) {
|
|
39
|
+
const src = typeof source === 'string' ? source.trim().toLowerCase() : '';
|
|
40
|
+
if (SERVER_ONLY_PREFIXES.some((p) => src.startsWith(p))) {
|
|
41
|
+
return { code: 'reserved_source', status: 403, message: 'this spend is recorded by the server itself, not through this route' };
|
|
42
|
+
}
|
|
43
|
+
if (src.startsWith(TRUSTED_ONLY_PREFIX) && !rankMeets(rank, ['metic'])) {
|
|
44
|
+
return { code: 'reserved_source', status: 403, message: 'scheduled-run spend can only be logged by a Metic or above' };
|
|
45
|
+
}
|
|
46
|
+
if (taskId != null && !rankMeets(rank, ['archon']) && !(await callerHasClaimed(taskId))) {
|
|
47
|
+
return { code: 'task_not_yours', status: 403, message: 'cost can only be logged against a task you have claimed' };
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { checkCostWrite, MAX_ROW_USD };
|
package/modules/economy/cost.js
CHANGED
|
@@ -89,7 +89,8 @@ async function autonomousApiSpendMtd() {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// The Treasury's three spending lines, month by month (task 1004527, the
|
|
92
|
-
// Governor City):
|
|
92
|
+
// Governor City): credits awarded to builders (credit_log — a contribution meter
|
|
93
|
+
// sized by session cost in USD, never money; ADR 0387), what the
|
|
93
94
|
// grader cost (cost_log source 'grader') and hosting (categories infra, compute,
|
|
94
95
|
// domain). The same ledgers GET /public/cost-summary already publishes, cut the
|
|
95
96
|
// one way the Treasury building reads them. Newest month first, bounded.
|
|
@@ -132,16 +132,18 @@ const IDEA_GOVERNANCE_PASS_FLOOR = 30; // owner: floor ~30 — see the note on
|
|
|
132
132
|
//
|
|
133
133
|
// A SHIPPED session earns drachmae proportional to its TRUE API token cost:
|
|
134
134
|
// drachmae = round(true_cost_usd * (1 + REWARD_MARGIN_PCT/100))
|
|
135
|
-
// i.e. cost + 20
|
|
136
|
-
//
|
|
135
|
+
// i.e. cost + 20%: the reward is SIZED BY the session's cost in USD, one drachma
|
|
136
|
+
// per dollar of cost-plus at the default scale (task 1138, the ADR 0054
|
|
137
|
+
// amendment). A drachma is not worth a dollar: credits are a contribution meter,
|
|
138
|
+
// never money, never redeemable and never equity (ADR 0385 decision 2, ADR 0387),
|
|
139
|
+
// and no dollar value is read off one. This reward was originally minted in
|
|
137
140
|
// integer CENTS (the formula carried a trailing ×100); that 100× factor was
|
|
138
|
-
// dropped
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// 2026-06-15 amendment) records the convention and why the cents form existed.
|
|
141
|
+
// dropped — de-inflating balances ~100× and bringing this path onto the same scale
|
|
142
|
+
// as the per-task credits_reward path above (still a unitless author estimate).
|
|
143
|
+
// Both accrue to builders.total_credits (the contribution meter); ADR 0054 (+ its
|
|
144
|
+
// 2026-06-15 amendment) records the scale and why the cents form existed.
|
|
143
145
|
//
|
|
144
|
-
// REWARD_MARGIN_PCT (and the 1 drachma
|
|
146
|
+
// REWARD_MARGIN_PCT (and the 1:1 cost-to-drachma scale, `usdPeg`) are the code-default reward
|
|
145
147
|
// POLICY. They stay a code-reviewed default — not a live UI knob — for the same
|
|
146
148
|
// reasons KIND_MULTIPLIERS does: a policy decision deserves a commit trail and
|
|
147
149
|
// the grader reads it deterministically. But the reward policy is also
|
|
@@ -175,16 +177,16 @@ function rewardConfig() {
|
|
|
175
177
|
// The two streams (ADR 0054 "two drachma conventions, one ledger") are:
|
|
176
178
|
// (1) the per-task author ESTIMATE — credits_reward × kind multiplier (ADR 0023),
|
|
177
179
|
// a unitless bounty set when the task is authored; and
|
|
178
|
-
// (2) the COST-PLUS session reward — round(true_cost_usd × 1.20),
|
|
179
|
-
// (ADR 0054)
|
|
180
|
+
// (2) the COST-PLUS session reward — round(true_cost_usd × 1.20), sized by the
|
|
181
|
+
// session's real API spend in USD (ADR 0054); still a credit, not money.
|
|
180
182
|
// mode picks which of the two an instance actually PAYS:
|
|
181
183
|
// 'cost-plus-and-estimate' (DEFAULT) — pay both, the historical behavior. Every
|
|
182
184
|
// existing instance keeps paying exactly as before on upgrade.
|
|
183
|
-
// 'cost-plus-only' — the cost-plus session reward is the SOLE
|
|
185
|
+
// 'cost-plus-only' — the cost-plus session reward is the SOLE credit reward; the
|
|
184
186
|
// per-task estimate PAYOUT is withheld (and its idea-promotion slice, being a
|
|
185
187
|
// fraction of it, cascades to 0). credits_reward still rides the task row for
|
|
186
188
|
// display + the no-zero-reward claim gate (ADR 0096) — only the payout is
|
|
187
|
-
// suppressed, so
|
|
189
|
+
// suppressed, so every credit is sized by cost-plus and nothing
|
|
188
190
|
// else. Cloud Bongos's choice (owner decision, 2026-07-16).
|
|
189
191
|
// Unknown/garbage mode → treated as the default (pay both): a config typo must
|
|
190
192
|
// never silently zero out every builder's per-task reward.
|
|
@@ -203,8 +205,9 @@ function rewardConfig() {
|
|
|
203
205
|
// absent/any-non-false → the default TRUE (a pre-ADR-0172 config keeps paying).
|
|
204
206
|
|
|
205
207
|
// Pure helper — exported for tests. Returns { trueCostUsd, marginPct, drachmae,
|
|
206
|
-
// description }. drachmae = round(cost × (1+margin) × usdPeg); at the 1:1
|
|
207
|
-
// drachma is
|
|
208
|
+
// description }. drachmae = round(cost × (1+margin) × usdPeg); at the 1:1 scale
|
|
209
|
+
// one drachma is earned per dollar of cost-plus (task 1138) — sized by USD, not
|
|
210
|
+
// worth USD (ADR 0387), e.g.
|
|
208
211
|
// "$15.00 true cost × 1.20 = 18 drachmae (token reward)"
|
|
209
212
|
// NOTE a sub-$0.42 session (at the 1:1 peg) rounds to 0 drachmae — intended:
|
|
210
213
|
// sub-dollar sessions earn nothing. Kept pure (code-default params); the call
|
|
@@ -256,7 +259,7 @@ function applyKindMultiplier(rawCredits, kind) {
|
|
|
256
259
|
async function insertMultipliedConfirmCredit(client, builderId, taskId, rawCredits, kind, reason = 'task.confirmed', config = rewardConfig()) {
|
|
257
260
|
const result = applyKindMultiplier(rawCredits, kind);
|
|
258
261
|
// Reward-mode gate (ADR 0146): in 'cost-plus-only' mode this instance pays no
|
|
259
|
-
// per-task estimate credit — the cost-plus session reward is the sole
|
|
262
|
+
// per-task estimate credit — the cost-plus session reward is the sole credit
|
|
260
263
|
// reward. Return the same zero-shape the effective<=0 path returns (so the
|
|
261
264
|
// caller's idea-promotion slice, a fraction of creditsAwarded, cascades to 0)
|
|
262
265
|
// but write NO credit_log row. The task's credits_reward still exists for
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/economy/goal-reward.js — the KARMA a goal's contributors earn when the
|
|
4
|
+
// goal is ACHIEVED (task 1001693 / BV1.P5.3, ADR 0101 Part 3).
|
|
5
|
+
//
|
|
6
|
+
// WHY KARMA AND NOT CREDITS. Credits are paid per task already (the estimate and
|
|
7
|
+
// the cost-plus session reward, ADR 0054), and a goal is a coordination construct,
|
|
8
|
+
// not a unit of billable work. A credit bonus on top would pay the same shipping
|
|
9
|
+
// twice out of a tight budget. Karma recognises the collaboration at no monetary
|
|
10
|
+
// cost. Whether ANY money attaches to an achievement, and the size of the karma
|
|
11
|
+
// pool below, are the owner's call (ADR 0101 Part 3 "owner-gated"): the numbers
|
|
12
|
+
// here are the budget-safe default, surfaced to the owner rather than presumed.
|
|
13
|
+
//
|
|
14
|
+
// CONTRIBUTION-WEIGHTED, NEVER MEMBERSHIP-FLAT. The pool is split by the number of
|
|
15
|
+
// tasks each builder SHIPPED under the goal (tasks.shipped_by, the attribution
|
|
16
|
+
// every shipped-work reader already keys on). Joining a goal earns nothing, so
|
|
17
|
+
// squatting on goals buys nothing. The goal's owner (goals.created_by) takes a
|
|
18
|
+
// small coordination share on top of whatever they shipped. The weight is a COUNT,
|
|
19
|
+
// deliberately not credits_reward: that is the task author's own estimate, so
|
|
20
|
+
// weighting by it would let whoever writes the tasks size their own share.
|
|
21
|
+
//
|
|
22
|
+
// ON ACHIEVEMENT, NEVER ON CREATION, AND ONCE PER GOAL EVER. The lifecycle calls
|
|
23
|
+
// this only from the statement that actually flipped the goal open→achieved, so
|
|
24
|
+
// exactly one caller reaches it per flip. The reason key `goal.achieved:<id>` is
|
|
25
|
+
// checked inside the one INSERT as well, so a goal that is reopened and achieved
|
|
26
|
+
// again pays nothing the second time: reopen/re-achieve cannot be farmed.
|
|
27
|
+
//
|
|
28
|
+
// A goal whose work was all abandoned shipped nothing and earns nothing.
|
|
29
|
+
//
|
|
30
|
+
// karma_log rows reach builders.karma through the migration-040 trigger; nothing
|
|
31
|
+
// here touches builders directly.
|
|
32
|
+
|
|
33
|
+
const GOAL_ACHIEVEMENT_KARMA_POOL = 10;
|
|
34
|
+
const GOAL_OWNER_SHARE = 0.1;
|
|
35
|
+
const GOAL_REASON_PREFIX = 'goal.achieved:';
|
|
36
|
+
|
|
37
|
+
// Largest-remainder apportionment: integer shares that always sum to `total`, ties
|
|
38
|
+
// broken by builder id so the same inputs always give the same split.
|
|
39
|
+
function apportion(total, weights) {
|
|
40
|
+
const sum = weights.reduce((t, w) => t + w.weight, 0);
|
|
41
|
+
if (!(total > 0) || !(sum > 0)) return weights.map((w) => ({ ...w, share: 0 }));
|
|
42
|
+
const raw = weights.map((w) => {
|
|
43
|
+
const exact = (total * w.weight) / sum;
|
|
44
|
+
return { ...w, share: Math.floor(exact), rem: exact - Math.floor(exact) };
|
|
45
|
+
});
|
|
46
|
+
let left = total - raw.reduce((t, r) => t + r.share, 0);
|
|
47
|
+
const order = [...raw].sort((a, b) => (b.rem - a.rem) || (String(a.builderId) < String(b.builderId) ? -1 : 1));
|
|
48
|
+
for (const r of order) {
|
|
49
|
+
if (left <= 0) break;
|
|
50
|
+
r.share += 1;
|
|
51
|
+
left -= 1;
|
|
52
|
+
}
|
|
53
|
+
return raw.map(({ rem, ...r }) => r);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// goalRewardSplit — PURE. Who earns what from one achieved goal.
|
|
57
|
+
// `contributions` is [{ builderId, shipped }]; returns [{ builderId, delta, role }],
|
|
58
|
+
// one row per builder, zero rows dropped. Conserves the pool exactly whenever at
|
|
59
|
+
// least one task shipped.
|
|
60
|
+
function goalRewardSplit({ contributions = [], ownerId = null,
|
|
61
|
+
pool = GOAL_ACHIEVEMENT_KARMA_POOL, ownerShare = GOAL_OWNER_SHARE } = {}) {
|
|
62
|
+
const shippers = contributions
|
|
63
|
+
.map((c) => ({ builderId: c.builderId, weight: Number(c.shipped) || 0 }))
|
|
64
|
+
.filter((c) => c.builderId != null && c.weight > 0);
|
|
65
|
+
if (shippers.length === 0 || !(pool > 0)) return [];
|
|
66
|
+
|
|
67
|
+
const ownerCut = ownerId == null ? 0 : Math.round(pool * ownerShare);
|
|
68
|
+
const byBuilder = new Map();
|
|
69
|
+
for (const s of apportion(pool - ownerCut, shippers)) {
|
|
70
|
+
if (s.share > 0) byBuilder.set(String(s.builderId), { builderId: s.builderId, delta: s.share, role: 'contributor' });
|
|
71
|
+
}
|
|
72
|
+
if (ownerCut > 0) {
|
|
73
|
+
const key = String(ownerId);
|
|
74
|
+
const prior = byBuilder.get(key);
|
|
75
|
+
byBuilder.set(key, prior
|
|
76
|
+
? { ...prior, delta: prior.delta + ownerCut, role: 'owner_contributor' }
|
|
77
|
+
: { builderId: ownerId, delta: ownerCut, role: 'owner' });
|
|
78
|
+
}
|
|
79
|
+
return [...byBuilder.values()];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// awardGoalAchievementKarma — book the split for one just-achieved goal on the
|
|
83
|
+
// caller's executor (the ship transaction, or the pool for the reconciler sweep).
|
|
84
|
+
// Returns { awarded, reason, payees }; `awarded` is the karma NEWLY booked, 0 on a
|
|
85
|
+
// re-achieve or a goal that shipped nothing.
|
|
86
|
+
//
|
|
87
|
+
// IT CAN NEVER FAIL THE ACHIEVEMENT. A failed statement inside the ship
|
|
88
|
+
// transaction would abort the whole ship, so on a transaction client the booking
|
|
89
|
+
// runs under a SAVEPOINT and rolls back to it on any error; on a pool, SAVEPOINT
|
|
90
|
+
// itself fails, nothing is open to poison, and the booking runs plainly. The
|
|
91
|
+
// reconcileAfterCraftCredit precedent in achievements.js, exactly. A failure
|
|
92
|
+
// returns reason 'failed' with the message, and books nothing.
|
|
93
|
+
async function awardGoalAchievementKarma(exec, opts = {}) {
|
|
94
|
+
let saved = false;
|
|
95
|
+
try {
|
|
96
|
+
await exec.query('SAVEPOINT goal_achievement_karma');
|
|
97
|
+
saved = true;
|
|
98
|
+
} catch (_) { /* not in a transaction: a pool */ }
|
|
99
|
+
try {
|
|
100
|
+
const out = await bookGoalKarma(exec, opts);
|
|
101
|
+
if (saved) await exec.query('RELEASE SAVEPOINT goal_achievement_karma');
|
|
102
|
+
return out;
|
|
103
|
+
} catch (e) {
|
|
104
|
+
if (saved) {
|
|
105
|
+
try { await exec.query('ROLLBACK TO SAVEPOINT goal_achievement_karma'); } catch (_e) { /* the caller's tx decides */ }
|
|
106
|
+
}
|
|
107
|
+
return { awarded: 0, reason: 'failed', error: e && e.message, payees: [] };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function bookGoalKarma(exec, { goalId } = {}) {
|
|
112
|
+
const id = Number(goalId);
|
|
113
|
+
if (!Number.isFinite(id)) return { awarded: 0, reason: 'bad_goal_id', payees: [] };
|
|
114
|
+
const reason = `${GOAL_REASON_PREFIX}${id}`;
|
|
115
|
+
const { rows: goalRows } = await exec.query('SELECT created_by FROM goals WHERE id = $1', [id]);
|
|
116
|
+
if (goalRows.length === 0) return { awarded: 0, reason: 'not_found', payees: [] };
|
|
117
|
+
const { rows } = await exec.query(
|
|
118
|
+
`SELECT shipped_by AS builder_id, count(*)::int AS shipped
|
|
119
|
+
FROM tasks
|
|
120
|
+
WHERE goal_id = $1 AND status = 'shipped' AND shipped_by IS NOT NULL
|
|
121
|
+
AND title NOT LIKE '\\_\\_smoke\\_\\_%' ESCAPE '\\'
|
|
122
|
+
GROUP BY shipped_by`,
|
|
123
|
+
[id]
|
|
124
|
+
);
|
|
125
|
+
const payees = goalRewardSplit({
|
|
126
|
+
contributions: rows.map((r) => ({ builderId: r.builder_id, shipped: r.shipped })),
|
|
127
|
+
ownerId: goalRows[0].created_by,
|
|
128
|
+
});
|
|
129
|
+
if (payees.length === 0) return { awarded: 0, reason: 'nothing_shipped', payees: [] };
|
|
130
|
+
// ONE statement for every payee, guarded on the goal's reason key: either the
|
|
131
|
+
// whole split is booked or none of it is.
|
|
132
|
+
//
|
|
133
|
+
// THE GUARD IS BUILDER-SCOPED so it is served by idx_karma_log_builder: karma_log
|
|
134
|
+
// has no index on `reason` and is the whole instance's append-only karma history,
|
|
135
|
+
// so a reason-only NOT EXISTS would scan all of it. Scoping to the builders who
|
|
136
|
+
// could hold an earlier row for this goal loses nothing: an earlier award paid
|
|
137
|
+
// the goal's owner (always a payee when the goal has one) and every builder who
|
|
138
|
+
// had shipped by then, and shipped tasks stay shipped, so a re-achieve's guard
|
|
139
|
+
// set always contains at least one earlier payee. The owner is added to the set
|
|
140
|
+
// explicitly for that reason.
|
|
141
|
+
const guardIds = [...new Set([...payees.map((p) => String(p.builderId)),
|
|
142
|
+
...(goalRows[0].created_by == null ? [] : [String(goalRows[0].created_by)])])];
|
|
143
|
+
const { rowCount } = await exec.query(
|
|
144
|
+
`INSERT INTO karma_log (builder_id, delta, reason, granted_by)
|
|
145
|
+
SELECT u.builder_id, u.delta, $3, NULL
|
|
146
|
+
FROM unnest($1::bigint[], $2::int[]) AS u(builder_id, delta)
|
|
147
|
+
WHERE NOT EXISTS (SELECT 1 FROM karma_log
|
|
148
|
+
WHERE builder_id = ANY($4::bigint[]) AND reason = $3)`,
|
|
149
|
+
[payees.map((p) => p.builderId), payees.map((p) => p.delta), reason, guardIds]
|
|
150
|
+
);
|
|
151
|
+
if (rowCount === 0) return { awarded: 0, reason: 'already_awarded', payees: [] };
|
|
152
|
+
return { awarded: payees.reduce((t, p) => t + p.delta, 0), reason: 'awarded', payees };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = {
|
|
156
|
+
GOAL_ACHIEVEMENT_KARMA_POOL,
|
|
157
|
+
GOAL_OWNER_SHARE,
|
|
158
|
+
GOAL_REASON_PREFIX,
|
|
159
|
+
goalRewardSplit,
|
|
160
|
+
awardGoalAchievementKarma,
|
|
161
|
+
};
|
|
@@ -23,6 +23,7 @@ const ideaSizeLib = require('./idea-size');
|
|
|
23
23
|
const ideaSizeHistorical = require('./idea-size-historical');
|
|
24
24
|
const ideaBrightnessLib = require('./idea-brightness');
|
|
25
25
|
const creditsByCraftLib = require('./credits-by-craft');
|
|
26
|
+
const goalReward = require('./goal-reward');
|
|
26
27
|
|
|
27
28
|
module.exports = {
|
|
28
29
|
// credit allocation — transaction-participant: caller passes its tx `client`
|
|
@@ -30,6 +31,9 @@ module.exports = {
|
|
|
30
31
|
maybeAwardIdeaPromotionBonus: (...a) => credits.maybeAwardIdeaPromotionBonus(...a),
|
|
31
32
|
// ADR 0172 IC2 — the ideator credit lane (stream 2): a passing Full Idea pays its author, once.
|
|
32
33
|
awardIdeaFullPassCredit: (...a) => credits.awardIdeaFullPassCredit(...a),
|
|
34
|
+
// BV1.P5.3 / ADR 0101 Part 3 — an achieved goal's karma, split by shipped work.
|
|
35
|
+
// Called by the lifecycle on the executor of the statement that flipped the goal.
|
|
36
|
+
awardGoalAchievementKarma: (...a) => goalReward.awardGoalAchievementKarma(...a),
|
|
33
37
|
// ADR 0172 IC4 — stream 1: pay the idea's author per shipped task, grade-scaled (replaces the 25% slice at ship).
|
|
34
38
|
awardIdeaTaskShipCredit: (...a) => credits.awardIdeaTaskShipCredit(...a),
|
|
35
39
|
// BV2.R09 / ADR 0312 — the same stream, for the origins the shipping task
|
|
@@ -20,20 +20,38 @@ const express = require('express');
|
|
|
20
20
|
// intra-module require — a module never requires a core internal.
|
|
21
21
|
const api = require('../../../src/module-api');
|
|
22
22
|
const cost = require('../cost');
|
|
23
|
+
const { checkCostWrite, MAX_ROW_USD } = require('../cost-write-guard');
|
|
23
24
|
const { validateOrRespond, LIMITS } = api;
|
|
24
25
|
|
|
25
26
|
const COST_CATEGORIES = ['api', 'compute', 'infra', 'domain', 'art', 'other'];
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
// Has this builder ever claimed this task? A released claim still counts: a
|
|
29
|
+
// session's cost lands after its ship frees the claim (task 1004959). Served by
|
|
30
|
+
// idx_claims_task_all (claims (task_id), core_263): a task carries a handful of
|
|
31
|
+
// claims ever, so the builder_id filter runs over those rows, never the table.
|
|
32
|
+
async function builderHasClaimed(taskId, builderId) {
|
|
33
|
+
const { rows } = await api.pool.query(
|
|
34
|
+
'SELECT 1 FROM claims WHERE task_id = $1 AND builder_id = $2 LIMIT 1',
|
|
35
|
+
[taskId, builderId],
|
|
36
|
+
);
|
|
37
|
+
return rows.length > 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// deps.logCost / deps.hasClaimed are test seams; production uses the real ones.
|
|
41
|
+
module.exports = function buildCostRouter(deps = {}) {
|
|
28
42
|
// The Treasury's floors and record in the Governor City (task 1004527).
|
|
29
43
|
require('../governor-docket').contributeEconomyToDocket();
|
|
30
44
|
const router = express.Router();
|
|
45
|
+
const logCost = deps.logCost || ((...a) => cost.logCost(...a));
|
|
46
|
+
const hasClaimed = deps.hasClaimed || builderHasClaimed;
|
|
31
47
|
|
|
32
48
|
// rank: any-builder — log cost on own claim (idempotent via source/source_ref).
|
|
49
|
+
// Who may book what (reserved sources, the caller's own task, the per-row cap)
|
|
50
|
+
// is cost-write-guard.js — security report 1000029.
|
|
33
51
|
router.post('/cost', api.requireBuilder, async (req, res) => {
|
|
34
52
|
if (validateOrRespond(req, res, {
|
|
35
53
|
task_id: { type: 'integer', min: 1 },
|
|
36
|
-
amount_usd: { required: true, type: 'number', min: 0, max:
|
|
54
|
+
amount_usd: { required: true, type: 'number', min: 0, max: MAX_ROW_USD },
|
|
37
55
|
// Don't enum-restrict category — the existing skills set categories that
|
|
38
56
|
// may legitimately drift; just cap the length. (CLAUDE.md mentions
|
|
39
57
|
// api|compute|infra|domain|art|other as the canonical set.)
|
|
@@ -45,10 +63,10 @@ module.exports = function buildCostRouter() {
|
|
|
45
63
|
// System 1 / task 1344: optional per-tier USD split (cost_log columns
|
|
46
64
|
// added by migration 123). session-cost.js sends these so spend can be
|
|
47
65
|
// attributed by tier (cache_read is the marathon sink). Omitted → NULL.
|
|
48
|
-
fresh_input_usd: { type: 'number', min: 0, max:
|
|
49
|
-
cache_read_usd: { type: 'number', min: 0, max:
|
|
50
|
-
cache_write_usd: { type: 'number', min: 0, max:
|
|
51
|
-
output_usd: { type: 'number', min: 0, max:
|
|
66
|
+
fresh_input_usd: { type: 'number', min: 0, max: MAX_ROW_USD },
|
|
67
|
+
cache_read_usd: { type: 'number', min: 0, max: MAX_ROW_USD },
|
|
68
|
+
cache_write_usd: { type: 'number', min: 0, max: MAX_ROW_USD },
|
|
69
|
+
output_usd: { type: 'number', min: 0, max: MAX_ROW_USD },
|
|
52
70
|
// V3.R11 (#91): builder_id is optional. When absent we default to the
|
|
53
71
|
// authed builder (the right answer 99% of the time). When provided it
|
|
54
72
|
// must match the authed builder — we don't let one builder attribute
|
|
@@ -85,7 +103,15 @@ module.exports = function buildCostRouter() {
|
|
|
85
103
|
recordedAtDate = parsed;
|
|
86
104
|
}
|
|
87
105
|
try {
|
|
88
|
-
const
|
|
106
|
+
const refused = await checkCostWrite({
|
|
107
|
+
source,
|
|
108
|
+
taskId: taskId ?? null,
|
|
109
|
+
rank: req.builder?.rank,
|
|
110
|
+
rankMeets: api.rankMeetsThreshold,
|
|
111
|
+
callerHasClaimed: (id) => hasClaimed(id, authedBuilderId),
|
|
112
|
+
});
|
|
113
|
+
if (refused) return res.fail(refused.code, { status: refused.status, message: refused.message });
|
|
114
|
+
const out = await logCost({
|
|
89
115
|
taskId: taskId ?? null,
|
|
90
116
|
amountUsd,
|
|
91
117
|
category,
|
|
@@ -238,6 +238,12 @@ const PERMISSIONS = [
|
|
|
238
238
|
// Review the store's proposed sub-categories (task 1004355, ADR 0342 D3): approve,
|
|
239
239
|
// rename then approve, or merge into an existing one (a merge moves its modules).
|
|
240
240
|
{ key: 'module.subcategory.review', system: false, floor: 'metic', guards: 'GET /store/subcategories/queue, POST /store/subcategories/:id/approve, POST /store/subcategories/:id/merge' },
|
|
241
|
+
// The project's authorised person for money (task 1004913, ADR 0385 D4): sign,
|
|
242
|
+
// narrow or revoke a standing payout rule, hold a noticed run, approve one payout,
|
|
243
|
+
// sign one equity grant or contract, and read the approval log. Archon by default;
|
|
244
|
+
// not a system key, so a project may hand it to a treasurer rank of its own. The
|
|
245
|
+
// routes additionally refuse anything but a person's own web session.
|
|
246
|
+
{ key: 'payout.approve', system: false, floor: 'archon', guards: 'GET /payouts/approvals, GET /payouts/rules, POST /payouts/rules[/:id/{narrow,revoke,runs,runs/:runKey/hold}], POST /payouts/{approve,grants,contracts}' },
|
|
241
247
|
// Take a module out of the store (task 1003797, ADR 0338 D2). Stops new installs,
|
|
242
248
|
// versions and downloads; never reaches an instance that already holds it.
|
|
243
249
|
{ key: 'module.delist', system: false, floor: 'metic', guards: 'POST /store/modules/:key/delist, POST /store/modules/:key/versions/:version/release (a version the promotion gate held on tester — task 1003804), POST /store/modules/:key/versions/:version/withdraw (take a version back — task 1004895)' },
|