@bongos/core 1.18.0 → 1.18.18
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 +1740 -640
- package/.claude/skills/builder-box/SKILL.md +6 -0
- package/.claude/skills/builder-sequence/SKILL.md +97 -0
- package/.claude/skills/builder-ship/SKILL.md +8 -2
- package/.claude/skills/builder-start/SKILL.md +11 -0
- package/.claude/skills/builder-sync/SKILL.md +41 -0
- package/.claude/skills/fix-task/SKILL.md +64 -0
- package/.claude/skills/goal-create/SKILL.md +158 -0
- package/.claude/skills/goal-review/SKILL.md +3 -1
- package/.claude/skills/idea-triage/SKILL.md +14 -8
- package/.claude/skills/merge-mode/SKILL.md +2 -2
- package/.claude/skills/new-project/SKILL.md +1 -0
- package/.claude/skills/planning-session/SKILL.md +45 -18
- package/.env.local.example +4 -3
- package/README.md +5 -3
- package/bin/bongos.js +1 -0
- package/clients/bongos-client/README.md +2 -2
- package/clients/bongos-client/bongos-client.global.js +82 -18
- package/clients/bongos-client/index.cjs +82 -18
- package/clients/bongos-client/index.d.ts +131 -29
- package/clients/bongos-client/index.mjs +82 -18
- package/config/branding.neutral.json +11 -2
- package/config/governance.neutral.json +21 -0
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +24 -0
- package/docs/adr/0090-metic-task-authoring.md +1 -1
- package/docs/adr/0142-project-context-is-prose-only.md +41 -0
- package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
- package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
- package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
- package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
- package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
- package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
- package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
- package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
- package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
- package/docs/adr/0152-metic-task-abandonment.md +67 -0
- package/docs/adr/0153-sequential-chain-runner.md +51 -0
- package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
- package/docs/adr/README.md +15 -2
- package/docs/api/openapi.json +2648 -483
- package/docs/api-reference.md +73 -13
- package/docs/architecture.md +4 -2
- package/docs/branding-contract.md +3 -1
- package/docs/canonical-permissions.md +21 -8
- package/docs/file-map.md +15 -7
- package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
- package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
- package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
- package/docs/onboarding/diagrams/assertions.json +4 -7
- package/docs/onboarding/drachmae-and-karma.md +3 -3
- package/docs/onboarding/primer.md +17 -16
- package/docs/onboarding/slash-commands.md +124 -0
- package/docs/recipes/builder-box-lifecycle.md +23 -0
- package/docs/recipes/devbox-app-release.md +14 -4
- package/docs/recipes/instance-first-run.md +16 -0
- package/docs/recipes/local-dev.md +81 -0
- package/docs/recipes/ops-gotchas.md +19 -1
- package/docs/recipes/overnight-builder.md +1 -1
- package/docs/recipes/packaging-the-core.md +13 -0
- package/docs/recipes/parallel-goal-waves.md +174 -0
- package/docs/recipes/ship-and-regrade.md +2 -2
- package/docs/recipes/standalone-live-docs.md +48 -0
- package/docs/recipes/upgrading-the-core.md +5 -2
- package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
- package/migrations/003_pms.sql +6 -7
- package/migrations/006_pms_v2.sql +3 -31
- package/migrations/039_newcomer_friendly_column.sql +5 -0
- package/migrations/102_remove_box_first_provision_approval.sql +4 -0
- package/migrations/core_192_github_oauth_tokens.sql +33 -0
- package/migrations/core_193_task_needs_rebase.sql +28 -0
- package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
- package/migrations/core_195_idea_triage_provenance.sql +57 -0
- package/migrations/core_196_session_reward_topup.sql +69 -0
- package/modules/autonomy/pollers/routine-timers.js +9 -8
- package/modules/builder-settings/wandering-prefs.js +61 -18
- package/modules/dev-box/app/README.md +73 -0
- package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
- package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
- package/modules/dev-box/app/assets/tray/win-off.png +0 -0
- package/modules/dev-box/app/assets/tray/win-on.png +0 -0
- package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
- package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
- package/modules/dev-box/app/build-resources/icon.png +0 -0
- package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
- package/modules/dev-box/app/electron-builder.yml +95 -0
- package/modules/dev-box/app/package-lock.json +5291 -0
- package/modules/dev-box/app/package.json +30 -0
- package/modules/dev-box/app/renderer/app.js +319 -0
- package/modules/dev-box/app/renderer/index.html +45 -0
- package/modules/dev-box/app/renderer/styles.css +246 -0
- package/modules/dev-box/app/renderer/theme.generated.css +16 -0
- package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
- package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
- package/modules/dev-box/app/scripts/gen-brand.js +138 -0
- package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
- package/modules/dev-box/app/scripts/gen-icons.js +286 -0
- package/modules/dev-box/app/scripts/vendor-client.js +23 -0
- package/modules/dev-box/app/src/box-controller.js +287 -0
- package/modules/dev-box/app/src/brand.generated.json +13 -0
- package/modules/dev-box/app/src/brand.js +16 -0
- package/modules/dev-box/app/src/config.js +19 -0
- package/modules/dev-box/app/src/gds-client.js +90 -0
- package/modules/dev-box/app/src/main.js +610 -0
- package/modules/dev-box/app/src/pairing.js +57 -0
- package/modules/dev-box/app/src/preload.js +29 -0
- package/modules/dev-box/app/src/session-store.js +58 -0
- package/modules/dev-box/app/src/ssh-setup.js +569 -0
- package/modules/dev-box/app/src/task-picker.js +70 -0
- package/modules/dev-box/app/src/tray.js +99 -0
- package/modules/dev-box/app/src/updater.js +55 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
- package/modules/dev-box/app/test/unit.mjs +526 -0
- package/modules/dev-box/box-access.js +121 -4
- package/modules/dev-box/boxes.js +20 -0
- package/modules/dev-box/module.json +1 -1
- package/modules/dev-box/routes/box.js +87 -3
- package/modules/economy/credits.js +116 -25
- package/modules/economy/reward.js +8 -0
- package/modules/economy/streak.js +2 -1
- package/modules/governance/CLAUDE.md +15 -0
- package/modules/governance/catalog.js +419 -0
- package/modules/governance/config.js +174 -0
- package/modules/governance/db.js +125 -0
- package/modules/governance/migrations/governance_001_tables.sql +81 -0
- package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
- package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
- package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
- package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
- package/modules/governance/module.json +10 -0
- package/modules/governance/protected-surfaces.js +88 -0
- package/modules/governance/protected-surfaces.json +80 -0
- package/modules/governance/rank-roles.js +83 -0
- package/modules/governance/resolver.js +221 -0
- package/modules/governance/routes/governance.js +89 -0
- package/modules/governance/routes/role-sync.js +49 -0
- package/modules/governance/session-scopes.js +127 -0
- package/modules/governance/session-scopes.json +38 -0
- package/modules/hall-ui/public/atlas.css +117 -0
- package/modules/hall-ui/public/atlas.html +3 -114
- package/modules/hall-ui/public/brand-apply.js +34 -6
- package/modules/hall-ui/public/builders.js +187 -219
- package/modules/hall-ui/public/diagrams.html +2 -0
- package/modules/hall-ui/public/dom-utils.js +27 -0
- package/modules/hall-ui/public/drachmae.html +2 -0
- package/modules/hall-ui/public/drachmae.js +54 -1
- package/modules/hall-ui/public/gate.css +98 -0
- package/modules/hall-ui/public/gate.html +3 -95
- package/modules/hall-ui/public/goal-inbox.js +4 -1
- package/modules/hall-ui/public/goals-page.js +626 -114
- package/modules/hall-ui/public/goals.css +79 -1
- package/modules/hall-ui/public/goals.html +38 -11
- package/modules/hall-ui/public/hall-widgets.js +62 -52
- package/modules/hall-ui/public/harbor.css +123 -0
- package/modules/hall-ui/public/harbor.html +3 -120
- package/modules/hall-ui/public/index.html +11 -4
- package/modules/hall-ui/public/instances-panel.js +4 -1
- package/modules/hall-ui/public/modules.css +63 -0
- package/modules/hall-ui/public/modules.html +3 -60
- package/modules/hall-ui/public/my-projects.html +78 -0
- package/modules/hall-ui/public/my-projects.js +142 -0
- package/modules/hall-ui/public/new-project-wizard.js +7 -3
- package/modules/hall-ui/public/not-ready.html +2 -0
- package/modules/hall-ui/public/pair.css +73 -0
- package/modules/hall-ui/public/pair.html +3 -70
- package/modules/hall-ui/public/palette.js +302 -0
- package/modules/hall-ui/public/primer.css +13 -0
- package/modules/hall-ui/public/primer.html +2 -0
- package/modules/hall-ui/public/profile.html +2 -0
- package/modules/hall-ui/public/ranks.html +2 -0
- package/modules/hall-ui/public/roadmap.css +179 -0
- package/modules/hall-ui/public/roadmap.html +9 -172
- package/modules/hall-ui/public/roadmap.js +329 -316
- package/modules/hall-ui/public/scouting.html +45 -0
- package/modules/hall-ui/public/scouting.js +73 -0
- package/modules/hall-ui/public/sessions.css +16 -0
- package/modules/hall-ui/public/sessions.html +3 -13
- package/modules/hall-ui/public/settings.html +2 -0
- package/modules/hall-ui/public/settings.js +4 -4
- package/modules/hall-ui/public/shell.js +143 -18
- package/modules/hall-ui/public/style.css +231 -39
- package/modules/hall-ui/public/task-detail.js +347 -26
- package/modules/hall-ui/public/task.html +6 -1
- package/modules/hall-ui/public/watch.html +2 -0
- package/modules/hall-ui/public/work.html +8 -6
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/ideas/inbox.js +61 -11
- package/modules/ideas/routes/inbox.js +23 -0
- package/modules/lifecycle/claim-eligibility.js +72 -0
- package/modules/lifecycle/db.js +519 -139
- package/modules/lifecycle/done-when.js +18 -3
- package/modules/lifecycle/github-push.js +19 -2
- package/modules/lifecycle/goal-advisory.js +114 -0
- package/modules/lifecycle/goal-authz.js +62 -0
- package/modules/lifecycle/lifecycle.js +9 -0
- package/modules/lifecycle/publish-reconciler.js +109 -6
- package/modules/lifecycle/routes/claims.js +68 -23
- package/modules/lifecycle/routes/done-when.js +110 -15
- package/modules/lifecycle/routes/goals.js +8 -38
- package/modules/lifecycle/routes/tasks.js +38 -6
- package/modules/lifecycle/ship-card.js +6 -0
- package/modules/onboarding/onboarding-state.js +1 -1
- package/modules/platform-identity/CLAUDE.md +15 -0
- package/modules/platform-identity/hub-device.js +147 -0
- package/modules/platform-identity/hub-keys.js +151 -0
- package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
- package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
- package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
- package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
- package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
- package/modules/platform-identity/module.json +18 -0
- package/modules/platform-identity/platform-identity.js +531 -0
- package/modules/platform-identity/pollers/code-sweep.js +43 -0
- package/modules/platform-identity/routes/my-projects.js +140 -0
- package/modules/platform-identity/routes/projects.js +107 -0
- package/modules/platform-identity/routes/scouting.js +35 -0
- package/modules/platform-identity/routes/sso.js +402 -0
- package/modules/platform-identity/tests/platform-identity.mjs +388 -0
- package/modules/platform-identity/ui/.gitkeep +0 -0
- package/modules/security/routes/reports.js +10 -0
- package/modules/sessions/db.js +166 -21
- package/modules/sessions/module.json +1 -1
- package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
- package/modules/sessions/routes/sessions.js +33 -0
- package/modules/status-ui/public/explore.html +54 -0
- package/modules/status-ui/public/explore.js +67 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/anim/idle/frame_00.webp +0 -0
- package/public-landing/assets/anim/idle/frame_01.webp +0 -0
- package/public-landing/assets/anim/idle/frame_02.webp +0 -0
- package/public-landing/assets/anim/idle/frame_03.webp +0 -0
- package/public-landing/assets/anim/idle/frame_04.webp +0 -0
- package/public-landing/assets/anim/idle/frame_05.webp +0 -0
- package/public-landing/assets/anim/idle/frame_06.webp +0 -0
- package/public-landing/assets/anim/idle/frame_07.webp +0 -0
- package/public-landing/assets/anim/idle/frame_08.webp +0 -0
- package/public-landing/assets/anim/idle/frame_09.webp +0 -0
- package/public-landing/assets/anim/idle/frame_10.webp +0 -0
- package/public-landing/assets/anim/idle/frame_11.webp +0 -0
- package/public-landing/assets/anim/idle/frame_12.webp +0 -0
- package/public-landing/assets/anim/idle/frame_13.webp +0 -0
- package/public-landing/assets/anim/idle/frame_14.webp +0 -0
- package/public-landing/assets/anim/idle/frame_15.webp +0 -0
- package/public-landing/assets/anim/idle/frame_16.webp +0 -0
- package/public-landing/assets/anim/idle/frame_17.webp +0 -0
- package/public-landing/assets/anim/idle/frame_18.webp +0 -0
- package/public-landing/assets/anim/idle/frame_19.webp +0 -0
- package/public-landing/assets/anim/idle/frame_20.webp +0 -0
- package/public-landing/assets/anim/idle/frame_21.webp +0 -0
- package/public-landing/assets/anim/idle/frame_22.webp +0 -0
- package/public-landing/assets/anim/idle/frame_23.webp +0 -0
- package/public-landing/assets/anim/idle/frame_24.webp +0 -0
- package/public-landing/assets/anim/idle/frame_25.webp +0 -0
- package/public-landing/assets/anim/idle/frame_26.webp +0 -0
- package/public-landing/assets/anim/idle/frame_27.webp +0 -0
- package/public-landing/assets/anim/idle/frame_28.webp +0 -0
- package/public-landing/assets/anim/idle/frame_29.webp +0 -0
- package/public-landing/assets/anim/idle/frame_30.webp +0 -0
- package/public-landing/assets/anim/idle/frame_31.webp +0 -0
- package/public-landing/assets/anim/idle/frame_32.webp +0 -0
- package/public-landing/assets/anim/idle/frame_33.webp +0 -0
- package/public-landing/assets/anim/idle/frame_34.webp +0 -0
- package/public-landing/assets/anim/idle/frame_35.webp +0 -0
- package/public-landing/assets/anim/idle/frame_36.webp +0 -0
- package/public-landing/assets/anim/idle/frame_37.webp +0 -0
- package/public-landing/assets/anim/idle/frame_38.webp +0 -0
- package/public-landing/assets/anim/idle/frame_39.webp +0 -0
- package/public-landing/assets/anim/idle/frame_40.webp +0 -0
- package/public-landing/assets/anim/idle/frame_41.webp +0 -0
- package/public-landing/assets/anim/idle/frame_42.webp +0 -0
- package/public-landing/assets/anim/idle/frame_43.webp +0 -0
- package/public-landing/create.html +252 -62
- package/public-landing/gate/README.md +137 -0
- package/public-landing/gate/dispmaps/bar.png +0 -0
- package/public-landing/gate/dispmaps/flat.png +0 -0
- package/public-landing/gate/dispmaps/key.png +0 -0
- package/public-landing/gate/index.html +587 -0
- package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
- package/public-landing/gate/previews/off-the-boats.jpg +0 -0
- package/public-landing/gate/space.css +799 -0
- package/public-landing/gate/space.js +1826 -0
- package/public-landing/gate/sprites/enter.png +0 -0
- package/public-landing/gate/sprites/search.png +0 -0
- package/public-landing/gate/sprites/terminal-blank.png +0 -0
- package/public-landing/gate/sprites/tile.png +0 -0
- package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
- package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
- package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
- package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
- package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
- package/public-landing/world/assets/plates/scene-establishing.png +0 -0
- package/scripts/gds/abandon.js +6 -4
- package/scripts/gds/api.js +10 -5
- package/scripts/gds/api.ps1 +26 -0
- package/scripts/gds/api.sh +6 -0
- package/scripts/gds/audit-ships.js +7 -1
- package/scripts/gds/backfill-sessions.js +78 -18
- package/scripts/gds/backup.js +23 -1
- package/scripts/gds/box-connect-lib.js +124 -31
- package/scripts/gds/box-sync.js +66 -0
- package/scripts/gds/box.js +225 -53
- package/scripts/gds/cf-tunnel.js +145 -6
- package/scripts/gds/claim.js +34 -3
- package/scripts/gds/cli-lib.js +161 -21
- package/scripts/gds/code.js +6 -2
- package/scripts/gds/deploy-divergence.js +209 -0
- package/scripts/gds/dev-lib.js +309 -0
- package/scripts/gds/dev.js +322 -0
- package/scripts/gds/doctor.js +90 -61
- package/scripts/gds/fetch-art-key.js +1 -1
- package/scripts/gds/fetch-sound-prefs.js +2 -2
- package/scripts/gds/fitness.js +228 -12
- package/scripts/gds/gds-db-backup.service +16 -1
- package/scripts/gds/gds-db-offsite.service +9 -1
- package/scripts/gds/gen-api-client.js +1 -1
- package/scripts/gds/gen-api-docs.js +36 -14
- package/scripts/gds/gen-diagrams.js +45 -28
- package/scripts/gds/gen-file-map.js +2 -1
- package/scripts/gds/gen-freshness.js +32 -0
- package/scripts/gds/gen-repo-map.js +3 -2
- package/scripts/gds/gen-session-index.js +14 -5
- package/scripts/gds/hooks/pre-push.js +2 -2
- package/scripts/gds/init.js +60 -3
- package/scripts/gds/install-git-hooks.js +1 -1
- package/scripts/gds/key-card.js +15 -6
- package/scripts/gds/key-status.js +1 -1
- package/scripts/gds/linkify-refs.js +35 -9
- package/scripts/gds/lint-migrations.js +102 -0
- package/scripts/gds/login.js +58 -11
- package/scripts/gds/mirror-redact.js +5 -0
- package/scripts/gds/package-core.js +263 -4
- package/scripts/gds/paste-token.js +7 -1
- package/scripts/gds/preflight.js +126 -0
- package/scripts/gds/provision.js +268 -19
- package/scripts/gds/publish-manifest.js +22 -1
- package/scripts/gds/reauth.js +7 -3
- package/scripts/gds/regen-instance-docs.js +47 -0
- package/scripts/gds/run-unit-tests.js +42 -16
- package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
- package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
- package/scripts/gds/sequence.js +425 -0
- package/scripts/gds/session-cost.js +10 -1
- package/scripts/gds/setup.js +72 -5
- package/scripts/gds/shell.js +5 -3
- package/scripts/gds/ship.js +433 -59
- package/scripts/gds/smoke-gds.js +139 -8
- package/scripts/gds/start.js +171 -22
- package/scripts/gds/status.js +1 -1
- package/scripts/gds/triage.js +10 -9
- package/scripts/gds/upgrade.js +271 -7
- package/scripts/migrate.sh +10 -0
- package/scripts/status-mirror-sync.js +114 -84
- package/src/bongos/api-errors.js +1 -0
- package/src/bongos/auth.js +809 -107
- package/src/bongos/db-kernel.js +27 -0
- package/src/bongos/db.js +105 -0
- package/src/bongos/github-repos.js +93 -0
- package/src/bongos/module-scope-map.js +27 -1
- package/src/bongos/permission-path-check.js +136 -102
- package/src/bongos/repo-info.js +47 -11
- package/src/bongos/route-rank-check.js +429 -46
- package/src/bongos/routes/_helpers.js +12 -1
- package/src/bongos/routes/auth.js +312 -13
- package/src/bongos/routes/backup.js +58 -2
- package/src/bongos/routes/github.js +90 -0
- package/src/bongos/routes/instance.js +15 -3
- package/src/bongos/routes/me.js +35 -1
- package/src/bongos/routes.js +2 -0
- package/src/bongos/serve-internal.js +84 -15
- package/src/branding.js +49 -2
- package/src/instance-config.js +46 -0
- package/src/module-api.js +140 -1
- package/src/platform-server.js +86 -9
- package/tests/abandon_task.mjs +59 -3
- package/tests/api_cli_flags.mjs +53 -0
- package/tests/auth_page_gate.mjs +13 -0
- package/tests/backfill_sessions.mjs +127 -1
- package/tests/backup_cli.mjs +54 -0
- package/tests/bongos_box_connect.mjs +77 -0
- package/tests/box_access.mjs +130 -1
- package/tests/box_credential.mjs +125 -0
- package/tests/box_host_keys.mjs +24 -0
- package/tests/box_infra_resolve.mjs +149 -0
- package/tests/box_scope_session.mjs +6 -2
- package/tests/box_sync.mjs +55 -0
- package/tests/box_task_scope.mjs +99 -0
- package/tests/branding.mjs +41 -23
- package/tests/cf_ruleset_preflight.mjs +142 -0
- package/tests/cf_tunnel.mjs +37 -0
- package/tests/claim_gate_rebase.mjs +98 -0
- package/tests/clawback_confirmed_backlog.mjs +103 -0
- package/tests/cli_lib_formatters.mjs +20 -0
- package/tests/confirm_attribution.mjs +30 -46
- package/tests/credit_grant.mjs +198 -10
- package/tests/criterion_authoring_gate.mjs +272 -0
- package/tests/criterion_progress.mjs +33 -1
- package/tests/dev_lib.mjs +253 -0
- package/tests/devbox_artifact_gate.mjs +75 -0
- package/tests/devbox_dmg_backdrop.mjs +137 -0
- package/tests/devbox_icons.mjs +304 -0
- package/tests/devbox_installer_brand.mjs +232 -0
- package/tests/devbox_key_register.mjs +234 -0
- package/tests/docs_openapi_route.mjs +110 -0
- package/tests/doctor_hooks.mjs +141 -0
- package/tests/extract_json.mjs +140 -0
- package/tests/fitness.mjs +89 -0
- package/tests/gen_freshness.mjs +96 -0
- package/tests/github_repos_helpers.mjs +91 -0
- package/tests/github_token_store.mjs +121 -0
- package/tests/goal_advisory.mjs +251 -0
- package/tests/goal_criterion_confirm_ui.mjs +269 -0
- package/tests/goal_routes.mjs +59 -4
- package/tests/goal_tier.mjs +12 -3
- package/tests/governance_catalog.mjs +129 -0
- package/tests/governance_claim_eligibility.mjs +262 -0
- package/tests/governance_config.mjs +187 -0
- package/tests/governance_ownership_scope.mjs +410 -0
- package/tests/governance_page_gates.mjs +324 -0
- package/tests/governance_parity.mjs +336 -0
- package/tests/governance_principals.mjs +232 -0
- package/tests/governance_protected_surfaces.mjs +208 -0
- package/tests/governance_require_permission.mjs +223 -0
- package/tests/governance_resolver.mjs +84 -0
- package/tests/governance_role_sync.mjs +231 -0
- package/tests/governance_seed.mjs +127 -0
- package/tests/governance_session_scope.mjs +434 -0
- package/tests/governance_vanilla_boot.mjs +203 -0
- package/tests/grade_attribution.mjs +7 -7
- package/tests/hall_audit.mjs +223 -0
- package/tests/hall_nav.mjs +191 -0
- package/tests/hall_palette.mjs +206 -0
- package/tests/hall_signout.mjs +124 -0
- package/tests/hook_user_agents.mjs +10 -0
- package/tests/idea_triage.mjs +162 -0
- package/tests/idp_device_exchange.mjs +198 -0
- package/tests/idp_federation.mjs +100 -0
- package/tests/init.mjs +51 -0
- package/tests/instance_config.mjs +65 -0
- package/tests/instance_mismatch_guard.mjs +23 -6
- package/tests/instance_release_config.mjs +104 -0
- package/tests/linkify_refs.mjs +15 -2
- package/tests/migrations_forward_safe.mjs +86 -0
- package/tests/module_api.mjs +1 -1
- package/tests/module_contributions.mjs +55 -0
- package/tests/module_loader.mjs +10 -5
- package/tests/module_scope_active_claims.mjs +94 -0
- package/tests/needs_rebase.mjs +92 -0
- package/tests/one_active_claim_per_session.mjs +76 -0
- package/tests/override_attribution.mjs +10 -8
- package/tests/override_request_ownership.mjs +8 -4
- package/tests/package_core.mjs +246 -1
- package/tests/parse_cookie_decode.mjs +42 -0
- package/tests/pay_on_land.mjs +129 -0
- package/tests/platform_boot.mjs +100 -1
- package/tests/post_login_landing.mjs +42 -0
- package/tests/preflight_checks.mjs +72 -0
- package/tests/provision.mjs +289 -7
- package/tests/pruned_lockfile_integrity.mjs +79 -0
- package/tests/publish_branch_route.mjs +53 -0
- package/tests/publish_reconciler.mjs +187 -2
- package/tests/repo_info_binding.mjs +248 -0
- package/tests/reward_gate.mjs +11 -2
- package/tests/reward_mode_cost_plus_only.mjs +116 -0
- package/tests/reward_mode_surfacing.mjs +168 -0
- package/tests/route_permission_check.mjs +301 -0
- package/tests/route_rank_check.mjs +254 -12
- package/tests/route_validation.mjs +30 -0
- package/tests/sequence.mjs +267 -0
- package/tests/session_digest.mjs +44 -0
- package/tests/session_index.mjs +18 -6
- package/tests/session_records.mjs +39 -1
- package/tests/session_reward_on_land.mjs +381 -0
- package/tests/session_reward_reconcile.mjs +229 -0
- package/tests/ship_api_client_pathspec.mjs +100 -0
- package/tests/ship_base_flag.mjs +93 -0
- package/tests/ship_ci_deploy.mjs +140 -3
- package/tests/ship_upstream_deletion.mjs +57 -0
- package/tests/smoke_target.mjs +233 -0
- package/tests/start_env_guard.mjs +87 -0
- package/tests/start_rebase_warning.mjs +103 -0
- package/tests/status_mirror_sync.mjs +122 -0
- package/tests/stop_refcheck.mjs +19 -1
- package/tests/task_goal_link.mjs +15 -6
- package/tests/upgrade.mjs +393 -5
- package/tests/web_admission_status.mjs +162 -0
- package/tests/write_validation_gaps.mjs +110 -0
- package/.gitignore +0 -91
- package/docs/adr/0139-platform-history-migration-to-cloudbongos.md +0 -94
- package/docs/adr/0140-cloudbongos-standalone-reprovision.md +0 -43
- package/scripts/hall-preview/fixtures/access-requests.json +0 -20
- package/scripts/hall-preview/fixtures/achievements.json +0 -76
- package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
- package/scripts/hall-preview/fixtures/audit-log.json +0 -68
- package/scripts/hall-preview/fixtures/blockers__detail.json +0 -12
- package/scripts/hall-preview/fixtures/box__me.json +0 -5
- package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -69
- package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
- package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
- package/scripts/hall-preview/fixtures/builders__roster.json +0 -48
- package/scripts/hall-preview/fixtures/goals.json +0 -502
- package/scripts/hall-preview/fixtures/goals__25.json +0 -96
- package/scripts/hall-preview/fixtures/goals__26.json +0 -67
- package/scripts/hall-preview/fixtures/goals__3.json +0 -103
- package/scripts/hall-preview/fixtures/goals__35.json +0 -49
- package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
- package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
- package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
- package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
- package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -32
- package/scripts/hall-preview/fixtures/inbox.json +0 -330
- package/scripts/hall-preview/fixtures/inbox__detail.json +0 -15
- package/scripts/hall-preview/fixtures/instance.json +0 -124
- package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
- package/scripts/hall-preview/fixtures/me.json +0 -476
- package/scripts/hall-preview/fixtures/me__art-key__own.json +0 -6
- package/scripts/hall-preview/fixtures/me__event-sound-prefs.json +0 -209
- package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
- package/scripts/hall-preview/fixtures/me__render.json +0 -5
- package/scripts/hall-preview/fixtures/me__skill-prefs.json +0 -143
- package/scripts/hall-preview/fixtures/me__sound-prefs.json +0 -67
- package/scripts/hall-preview/fixtures/me__wandering.json +0 -43
- package/scripts/hall-preview/fixtures/override-requests.json +0 -15
- package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
- package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
- package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
- package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
- package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
- package/scripts/hall-preview/fixtures/public__grades.json +0 -50
- package/scripts/hall-preview/fixtures/public__progress.json +0 -124
- package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
- package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
- package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
- package/scripts/hall-preview/fixtures/security__docs.json +0 -10
- package/scripts/hall-preview/fixtures/security__reports.json +0 -31
- package/scripts/hall-preview/fixtures/sessions__search.json +0 -9
- package/scripts/hall-preview/fixtures/tasks.json +0 -2420
- package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
- package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
- package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
- package/scripts/hall-preview/fixtures/versions.json +0 -89
|
@@ -96,6 +96,11 @@ const STARTER_SPARSE_PATHS = [
|
|
|
96
96
|
// stamped, and the "Connect to thy dev box" onboarding step never ticks. No
|
|
97
97
|
// secrets live in infra/ (ADR 0022).
|
|
98
98
|
'infra',
|
|
99
|
+
// tests/ — so a starter box can RUN the suite for the code it holds (BV1.R99,
|
|
100
|
+
// goal 1000052; same rationale as BASE_SPARSE_PATHS). Onboarding is "bare
|
|
101
|
+
// machine → first ship", and a newcomer proving their first change needs the
|
|
102
|
+
// top-level tests/ (tests/*.mjs + helpers.mjs), which sits beside src, not in it.
|
|
103
|
+
'tests',
|
|
99
104
|
];
|
|
100
105
|
|
|
101
106
|
// States in which a box is (or is becoming) a running droplet that can actually
|
|
@@ -117,21 +122,99 @@ function cloneSpecForScope(scope) {
|
|
|
117
122
|
return { scope: 'starter', mode: 'sparse', sparsePaths: [...STARTER_SPARSE_PATHS] };
|
|
118
123
|
}
|
|
119
124
|
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// PURE: the TASK-scoped surface (ADR 0148 / goal 1000051) — layered on BASE
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
// BASE_SPARSE_PATHS — the "pre-existing Bongos instance" every box carries no
|
|
130
|
+
// matter what the builder claimed: enough for Claude to hold methodology context
|
|
131
|
+
// and run the /builder-* CLI, but NONE of the product/game surface. It is the
|
|
132
|
+
// NON-PRODUCT subset of STARTER_SPARSE_PATHS — .devcontainer, .claude, scripts,
|
|
133
|
+
// src/bongos, docs, infra. (Top-level files like CLAUDE.md/server.js + the src/*.js
|
|
134
|
+
// root files come AUTOMATICALLY via cone-mode ancestor inclusion, because src/bongos
|
|
135
|
+
// is listed — see the STARTER note above.) A task's own module code (including the
|
|
136
|
+
// product/game when a game task is claimed) layers on top via sparsePathsForScopeKeys.
|
|
137
|
+
// This is the ADR 0148 move: scope the box to the code the claimed task needs, not
|
|
138
|
+
// the whole repo.
|
|
139
|
+
//
|
|
140
|
+
// BEHAVIOR vs ADR 0052 (sandbox-for-everyone): BASE deliberately drops the game
|
|
141
|
+
// (src/world, src/rooms, public) + art + modules/status-ui — a task-scoped box only
|
|
142
|
+
// carries the live sandbox when the claimed work is scoped to the `game` module. The
|
|
143
|
+
// pre-ADR-0148 "every box runs the sandbox" survives on the RANK fallback path
|
|
144
|
+
// (decideSourceAccess with NO scopeKeys), so a vanilla instance without the lifecycle
|
|
145
|
+
// port is unchanged.
|
|
146
|
+
const BASE_SPARSE_PATHS = [
|
|
147
|
+
'.devcontainer', '.claude', 'scripts', 'src/bongos', 'docs', 'infra',
|
|
148
|
+
// tests/ — so a pulled module's build/test commands actually RUN on the box
|
|
149
|
+
// (BV1.R99, goal 1000052). Module tests live in the TOP-LEVEL tests/ dir
|
|
150
|
+
// (tests/box_*.mjs + the tests/helpers.mjs harness), NOT inside modules/<key>/,
|
|
151
|
+
// so a box holding the module source but not tests/ cannot verify a change.
|
|
152
|
+
// A cone-mode directory; it carries no product runtime, only test code.
|
|
153
|
+
'tests',
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
// sparsePathsForScopeKeys — PURE. Given the module KEYS a builder's active claims
|
|
157
|
+
// put in scope (from the lifecycle port `moduleScopeForActiveClaims`, R92) and the
|
|
158
|
+
// doorway `moduleScopeMap` (PASSED IN by the route — box-access imports no core
|
|
159
|
+
// internal), compute the git cone-mode sparse-checkout set: BASE ∪ the DIRECTORY
|
|
160
|
+
// globs of every scoped module.
|
|
161
|
+
//
|
|
162
|
+
// Cone-mode constraint (load-bearing, ADR 0148): `git sparse-checkout set` accepts
|
|
163
|
+
// ONLY directories — a file path aborts the whole set. A module's scope_modules →
|
|
164
|
+
// globs come in two shapes: directory globs (trailing '/', e.g. 'modules/dev-box/')
|
|
165
|
+
// and file-list globs (exact paths, e.g. the kernel key's 'src/bongos/db.js'). We
|
|
166
|
+
// KEEP the directory globs (normalized to no trailing slash, matching the BASE
|
|
167
|
+
// style) and DROP the file globs — every file glob in the map sits under a BASE
|
|
168
|
+
// directory (src/bongos, scripts) or is a top-level/ancestor file cone mode pulls
|
|
169
|
+
// automatically, so base already materializes it. (A future non-cone sparse move
|
|
170
|
+
// could pull individual files; out of scope — cone mode stays.) Deterministic:
|
|
171
|
+
// BASE in declared order, then the module dirs sorted + deduped.
|
|
172
|
+
function sparsePathsForScopeKeys(scopeKeys, moduleScopeMap) {
|
|
173
|
+
const base = [...BASE_SPARSE_PATHS];
|
|
174
|
+
const seen = new Set(base);
|
|
175
|
+
const extra = [];
|
|
176
|
+
for (const key of Array.isArray(scopeKeys) ? scopeKeys : []) {
|
|
177
|
+
const entry = moduleScopeMap && typeof moduleScopeMap.scopeForModule === 'function'
|
|
178
|
+
? moduleScopeMap.scopeForModule(key)
|
|
179
|
+
: null;
|
|
180
|
+
if (!entry || !Array.isArray(entry.globs)) continue;
|
|
181
|
+
for (const glob of entry.globs) {
|
|
182
|
+
if (typeof glob !== 'string' || !glob.endsWith('/')) continue; // dirs only (cone mode)
|
|
183
|
+
const dir = glob.replace(/\/+$/, '');
|
|
184
|
+
if (!seen.has(dir)) { seen.add(dir); extra.push(dir); }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
extra.sort();
|
|
188
|
+
return [...base, ...extra];
|
|
189
|
+
}
|
|
190
|
+
|
|
120
191
|
// ---------------------------------------------------------------------------
|
|
121
192
|
// PURE: the per-fetch access decision (consumed by GET /box/source-access)
|
|
122
193
|
// ---------------------------------------------------------------------------
|
|
123
194
|
|
|
124
195
|
// Decide what source access a builder gets RIGHT NOW. The route reads the live
|
|
125
196
|
// rank+status from the DB and passes them in; this stays pure + unit-tested so the
|
|
126
|
-
// route and any future caller enforce the SAME rule.
|
|
127
|
-
//
|
|
128
|
-
//
|
|
197
|
+
// route and any future caller enforce the SAME rule.
|
|
198
|
+
//
|
|
199
|
+
// Rank still governs ALLOW/DENY + the provision floor (an inactive/below-floor
|
|
200
|
+
// builder is still 403, unchanged). What rank NO LONGER decides — once a claim
|
|
201
|
+
// drives it — is the module BREADTH (ADR 0148 / goal 1000051). The optional
|
|
202
|
+
// `scopeKeys` (from the lifecycle port `moduleScopeForActiveClaims`, R92, resolved
|
|
203
|
+
// + passed by the route in R94) selects the surface, three ways:
|
|
204
|
+
// - scopeKeys is a NON-EMPTY array → TASK scope: BASE ∪ the claims' module dirs
|
|
205
|
+
// (via sparsePathsForScopeKeys + the passed-in `moduleScopeMap`).
|
|
206
|
+
// - scopeKeys is an EMPTY array → BASE-only (a live box with no active claim /
|
|
207
|
+
// no scoped goal — the pre-existing Bongos instance, nothing extra).
|
|
208
|
+
// - scopeKeys is ABSENT (undefined) → the pre-ADR-0148 RANK fallback (full for
|
|
209
|
+
// Metic+, else the starter sparse surface). This is the path a vanilla instance
|
|
210
|
+
// with no lifecycle port takes, and it preserves the old behavior EXACTLY — so
|
|
211
|
+
// shipping R93 before R94 wires the route changes nothing in production.
|
|
129
212
|
//
|
|
130
213
|
// Returns one of:
|
|
131
214
|
// { allowed:false, reason:'BUILDER_INACTIVE' }
|
|
132
215
|
// { allowed:false, reason:'INSUFFICIENT_RANK', required, actual }
|
|
133
216
|
// { allowed:true, scope, spec }
|
|
134
|
-
function decideSourceAccess({ rank, status, floor = DEFAULT_PROVISION_FLOOR } = {}) {
|
|
217
|
+
function decideSourceAccess({ rank, status, floor = DEFAULT_PROVISION_FLOOR, scopeKeys, moduleScopeMap } = {}) {
|
|
135
218
|
if (status && status !== 'active') {
|
|
136
219
|
return { allowed: false, reason: 'BUILDER_INACTIVE' };
|
|
137
220
|
}
|
|
@@ -143,10 +226,41 @@ function decideSourceAccess({ rank, status, floor = DEFAULT_PROVISION_FLOOR } =
|
|
|
143
226
|
actual: rank ?? null,
|
|
144
227
|
};
|
|
145
228
|
}
|
|
229
|
+
// Claim-driven task scope (present → the box asks for exactly its work's code).
|
|
230
|
+
if (Array.isArray(scopeKeys)) {
|
|
231
|
+
const sparsePaths = sparsePathsForScopeKeys(scopeKeys, moduleScopeMap);
|
|
232
|
+
const scope = scopeKeys.length > 0 ? 'task' : 'base';
|
|
233
|
+
return { allowed: true, scope, spec: { scope, mode: 'sparse', sparsePaths } };
|
|
234
|
+
}
|
|
235
|
+
// No claim signal (no lifecycle port on this instance): the pre-ADR-0148 rank fallback.
|
|
146
236
|
const scope = boxScopeForRank(rank);
|
|
147
237
|
return { allowed: true, scope, spec: cloneSpecForScope(scope) };
|
|
148
238
|
}
|
|
149
239
|
|
|
240
|
+
// scopeMetadata — PURE. The credential-FREE projection of a source-access
|
|
241
|
+
// decision, for the read-only GET /box/scope companion (BV1.R100 / goal 1000051):
|
|
242
|
+
// the laptop app (and R104's proof) needs to KNOW what a box holds — which module
|
|
243
|
+
// dirs, which sparse set — WITHOUT being issued the pull credential. It is computed
|
|
244
|
+
// from the SAME decideSourceAccess result the real fetch uses, so the answer can
|
|
245
|
+
// never drift from the actual checkout.
|
|
246
|
+
//
|
|
247
|
+
// `modules` reports the resolved module KEYS a task-scope pulls (the raw scopeKeys
|
|
248
|
+
// from moduleScopeForActiveClaims, deduped + sorted for a stable display) — or null
|
|
249
|
+
// when the decision came from the RANK fallback (scopeKeys absent), where breadth is
|
|
250
|
+
// rank-driven, not module-driven, so there is no per-module list to name.
|
|
251
|
+
function scopeMetadata({ decision, scopeKeys } = {}) {
|
|
252
|
+
if (!decision || !decision.allowed) return null;
|
|
253
|
+
const modules = Array.isArray(scopeKeys)
|
|
254
|
+
? [...new Set(scopeKeys.filter((k) => typeof k === 'string'))].sort()
|
|
255
|
+
: null;
|
|
256
|
+
return {
|
|
257
|
+
scope: decision.scope,
|
|
258
|
+
mode: decision.spec ? decision.spec.mode : null,
|
|
259
|
+
modules,
|
|
260
|
+
sparse_paths: decision.spec ? decision.spec.sparsePaths : null,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
150
264
|
// ---------------------------------------------------------------------------
|
|
151
265
|
// PURE: the clawback / reconcile decision (consumed by `box.js reconcile`)
|
|
152
266
|
// ---------------------------------------------------------------------------
|
|
@@ -262,9 +376,12 @@ async function flagBoxReconcileForBuilder(db, builderId, { rank, status, floor =
|
|
|
262
376
|
module.exports = {
|
|
263
377
|
// pure — surface catalog
|
|
264
378
|
STARTER_SPARSE_PATHS,
|
|
379
|
+
BASE_SPARSE_PATHS, // ADR 0148 / goal 1000051 — the non-product base a task-scoped box layers on
|
|
265
380
|
SOURCE_HOLDING_STATES,
|
|
266
381
|
boxCanHoldSource,
|
|
267
382
|
cloneSpecForScope,
|
|
383
|
+
sparsePathsForScopeKeys, // ADR 0148 — BASE ∪ the claims' module dirs (cone-mode)
|
|
384
|
+
scopeMetadata, // BV1.R100 — credential-free projection for GET /box/scope
|
|
268
385
|
// pure — decisions
|
|
269
386
|
decideSourceAccess,
|
|
270
387
|
reconcileSourceAccess,
|
package/modules/dev-box/boxes.js
CHANGED
|
@@ -783,6 +783,24 @@ function computeCodeStaleness({ state, provisionedAt, codeReportedAt, codeCommit
|
|
|
783
783
|
return { stale: false, reason: 'current' };
|
|
784
784
|
}
|
|
785
785
|
|
|
786
|
+
// PURE: are the box's REPORTED SSH host keys older than the droplet now running?
|
|
787
|
+
// A rebuild mints fresh host keys, and until the new box re-reports (~10 min) the
|
|
788
|
+
// row can still carry its predecessor's identity — pinning that is the "Host denied
|
|
789
|
+
// (verification failed)" lockout. A report that predates `active_since` therefore
|
|
790
|
+
// describes a droplet that no longer exists; so does a missing timestamp on either
|
|
791
|
+
// side. Callers must fall back to a live keyscan rather than pin a dead identity.
|
|
792
|
+
function hostKeysAreStale({ hostKeysAt, activeSince } = {}) {
|
|
793
|
+
const toMs = (x) => {
|
|
794
|
+
if (x == null) return null;
|
|
795
|
+
const t = x instanceof Date ? x.getTime() : new Date(x).getTime();
|
|
796
|
+
return Number.isFinite(t) ? t : null;
|
|
797
|
+
};
|
|
798
|
+
const reported = toMs(hostKeysAt);
|
|
799
|
+
const activated = toMs(activeSince);
|
|
800
|
+
if (reported == null || activated == null) return true;
|
|
801
|
+
return reported < activated;
|
|
802
|
+
}
|
|
803
|
+
|
|
786
804
|
module.exports = {
|
|
787
805
|
// pure — rank
|
|
788
806
|
RANK_ORDER,
|
|
@@ -812,6 +830,8 @@ module.exports = {
|
|
|
812
830
|
computeCodeStaleness,
|
|
813
831
|
CODE_REPORT_GRACE_MS,
|
|
814
832
|
CODE_STALE_LAG_MS,
|
|
833
|
+
// pure — host-key staleness (task 1002509)
|
|
834
|
+
hostKeysAreStale,
|
|
815
835
|
// pure — droplet⇄row drift (task 1289)
|
|
816
836
|
DROPLET_HOLDING_STATES,
|
|
817
837
|
loginFromDroplet,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"key": "dev-box",
|
|
3
3
|
"title": "Dev boxes",
|
|
4
4
|
"description": "Per-builder cloud dev environments (DigitalOcean + Cloudflare tunnels), sandbox preview, server-mediated branch publish.",
|
|
5
|
-
"coreVersion": "^1.
|
|
5
|
+
"coreVersion": "^1.12.0",
|
|
6
6
|
"default": false,
|
|
7
7
|
"contributes": {
|
|
8
8
|
"routes": ["box"],
|
|
@@ -152,6 +152,10 @@ function publicBox(row) {
|
|
|
152
152
|
hostname: row.hostname,
|
|
153
153
|
last_activity_at: row.last_activity_at,
|
|
154
154
|
provisioned_at: row.provisioned_at,
|
|
155
|
+
// When the CURRENT droplet came up. provisioned_at is preserved across rebuilds,
|
|
156
|
+
// so this is the only timestamp a client can compare host_keys_at against to
|
|
157
|
+
// tell a fresh identity report from a dead box's (boxes.hostKeysAreStale).
|
|
158
|
+
active_since: row.active_since || null,
|
|
155
159
|
parked_at: row.parked_at,
|
|
156
160
|
compute_cost_usd: Number(row.compute_cost_usd || 0),
|
|
157
161
|
claude_active: row.claude_active ?? false,
|
|
@@ -429,7 +433,26 @@ module.exports = function buildBoxRouter() {
|
|
|
429
433
|
const builder = await gdsDb.getBuilderById(req.builder.id);
|
|
430
434
|
const rank = builder ? builder.rank : null;
|
|
431
435
|
const status = builder ? builder.status : 'inactive';
|
|
432
|
-
|
|
436
|
+
|
|
437
|
+
// R94 (ADR 0148 §4 / goal 1000051): scope the box to the CODE THE BUILDER'S
|
|
438
|
+
// CLAIMED TASK NEEDS. Resolve the lifecycle port (OPTIONAL — resolveOptional
|
|
439
|
+
// returns null on a vanilla instance without it) and read the set-union of the
|
|
440
|
+
// builder's active claims' module scope. scopeKeys drives the sparse BREADTH:
|
|
441
|
+
// - array present → TASK scope (BASE ∪ the claims' module dirs; empty → BASE-only)
|
|
442
|
+
// - undefined → decideSourceAccess falls back to the RANK scope (the
|
|
443
|
+
// pre-ADR-0148 behavior, so a port-less instance is unchanged).
|
|
444
|
+
// NEVER fail the source fetch on a scope-read hiccup — degrade to rank scope.
|
|
445
|
+
const lifecycle = api.resolveOptional('lifecycle');
|
|
446
|
+
let scopeKeys;
|
|
447
|
+
if (lifecycle && typeof lifecycle.moduleScopeForActiveClaims === 'function') {
|
|
448
|
+
try {
|
|
449
|
+
scopeKeys = await lifecycle.moduleScopeForActiveClaims(req.builder.id);
|
|
450
|
+
} catch (scopeErr) {
|
|
451
|
+
console.error('[gds] source-access scope read (non-fatal, → rank scope)', scopeErr && scopeErr.message ? scopeErr.message : scopeErr);
|
|
452
|
+
scopeKeys = undefined;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const decision = boxAccess.decideSourceAccess({ rank, status, scopeKeys, moduleScopeMap: api.moduleScopeMap });
|
|
433
456
|
const box = await boxes.getBoxByBuilderId(pool, req.builder.id);
|
|
434
457
|
const auditDeny = (reason, extra) =>
|
|
435
458
|
box && boxAccess.recordSourceEvent(pool, {
|
|
@@ -452,7 +475,16 @@ module.exports = function buildBoxRouter() {
|
|
|
452
475
|
}
|
|
453
476
|
|
|
454
477
|
// Mint the scope-appropriate credential (operator-supplied; may be unconfigured).
|
|
455
|
-
|
|
478
|
+
// ADR 0148 C4 leaves the TOKEN MODEL untouched: the credential (which repo +
|
|
479
|
+
// which token) stays RANK-based, exactly as before task-scoping. decision.scope
|
|
480
|
+
// may now be 'task'/'base' (the sparse BREADTH the box ASKS for), but the
|
|
481
|
+
// credential must still be the rank one — issue() only special-cases 'full', so
|
|
482
|
+
// passing 'task'/'base' would hand even a Metic+ the STARTER repo/token, which on
|
|
483
|
+
// a separate-starter-repo instance can't reach the task's module. Task-scope is
|
|
484
|
+
// the CLIENT-SIDE sparse ask; a hard per-claim credential ceiling is the deferred
|
|
485
|
+
// hardening (ADR 0148 Consequences).
|
|
486
|
+
const credScope = boxes.boxScopeForRank(rank); // 'starter' | 'full' — unchanged posture
|
|
487
|
+
const cred = boxCredential.defaultProvider.issue(credScope, { nowMs: Date.now() });
|
|
456
488
|
|
|
457
489
|
// Audit the grant — scope + configured flag only, NEVER the token. Only a
|
|
458
490
|
// REAL (configured) grant is logged: an unconfigured fetch issued no
|
|
@@ -465,7 +497,7 @@ module.exports = function buildBoxRouter() {
|
|
|
465
497
|
if (box && cred.configured) {
|
|
466
498
|
await boxAccess.recordSourceEvent(pool, {
|
|
467
499
|
box, builderId: req.builder.id, event: 'source_grant',
|
|
468
|
-
detail: `scope=${decision.scope} mode=${decision.spec.mode}`,
|
|
500
|
+
detail: `scope=${decision.scope} cred=${credScope} mode=${decision.spec.mode}`,
|
|
469
501
|
actor: 'api',
|
|
470
502
|
}).catch(() => {});
|
|
471
503
|
}
|
|
@@ -490,6 +522,58 @@ module.exports = function buildBoxRouter() {
|
|
|
490
522
|
}
|
|
491
523
|
});
|
|
492
524
|
|
|
525
|
+
// GET /box/scope — the credential-FREE companion to /box/source-access
|
|
526
|
+
// (BV1.R100 / goal 1000051). Answers "what code does my box hold RIGHT NOW?" —
|
|
527
|
+
// the resolved sparse set + the module keys of the builder's active claims —
|
|
528
|
+
// WITHOUT issuing the pull credential. The laptop app reads it to confirm
|
|
529
|
+
// "your box now has <module> for task N" after a pick+claim; R104's proof reads
|
|
530
|
+
// it to VERIFY the scope is minimal (excludes other modules). Same live rank+
|
|
531
|
+
// status + lifecycle-port scope logic as source-access, so the answer can't
|
|
532
|
+
// drift from the actual checkout — it just omits the secret.
|
|
533
|
+
//
|
|
534
|
+
// Deliberately NOT gated on a live box (boxCanHoldSource): the SCOPE is metadata,
|
|
535
|
+
// so a builder can ask "what WOULD my box hold for my current claims" the instant
|
|
536
|
+
// they claim, before the box has even synced. The ALLOW/DENY still comes from the
|
|
537
|
+
// live rank+status (a demoted/inactive builder gets the same 403 as source-access).
|
|
538
|
+
// allowBoxScope so it works both from the laptop app (full session) and on the box
|
|
539
|
+
// (box-scoped session) — safe here precisely because no credential is returned.
|
|
540
|
+
router.get('/box/scope', auth.allowBoxScope, auth.requireBuilder, async (req, res) => {
|
|
541
|
+
try {
|
|
542
|
+
// ONE consistent rank+status snapshot (the /box/source-access TOCTOU note).
|
|
543
|
+
const builder = await gdsDb.getBuilderById(req.builder.id);
|
|
544
|
+
const rank = builder ? builder.rank : null;
|
|
545
|
+
const status = builder ? builder.status : 'inactive';
|
|
546
|
+
|
|
547
|
+
// Resolve the claim-driven module scope exactly as source-access does
|
|
548
|
+
// (optional lifecycle port; degrade to rank scope on any hiccup).
|
|
549
|
+
const lifecycle = api.resolveOptional('lifecycle');
|
|
550
|
+
let scopeKeys;
|
|
551
|
+
if (lifecycle && typeof lifecycle.moduleScopeForActiveClaims === 'function') {
|
|
552
|
+
try {
|
|
553
|
+
scopeKeys = await lifecycle.moduleScopeForActiveClaims(req.builder.id);
|
|
554
|
+
} catch (scopeErr) {
|
|
555
|
+
console.error('[gds] /box/scope scope read (non-fatal, → rank scope)', scopeErr && scopeErr.message ? scopeErr.message : scopeErr);
|
|
556
|
+
scopeKeys = undefined;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const decision = boxAccess.decideSourceAccess({ rank, status, scopeKeys, moduleScopeMap: api.moduleScopeMap });
|
|
560
|
+
if (!decision.allowed) {
|
|
561
|
+
return res.fail('SOURCE_ACCESS_REVOKED', 403, {
|
|
562
|
+
reason: decision.reason,
|
|
563
|
+
required: decision.required ?? null,
|
|
564
|
+
actual: decision.actual ?? null,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
const meta = boxAccess.scopeMetadata({ decision, scopeKeys });
|
|
568
|
+
// task_scope true only when a claim actually drove the breadth (scopeKeys is
|
|
569
|
+
// an array). false → the rank fallback (no active claim / no lifecycle port).
|
|
570
|
+
return res.json({ ...meta, task_scope: Array.isArray(scopeKeys) });
|
|
571
|
+
} catch (err) {
|
|
572
|
+
console.error('[gds] GET /box/scope', err);
|
|
573
|
+
res.fail('box_scope_failed', { status: 500, message: 'internal error' });
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
|
|
493
577
|
// POST /box/heartbeat — the box reports it is alive, resetting the idle clock.
|
|
494
578
|
// rank: any authenticated builder (own resource write). Optional body:
|
|
495
579
|
// { claude_active: bool } — when provided, records whether a `claude` process
|
|
@@ -135,14 +135,45 @@ const IDEA_BONUS_FRACTION = 0.25; // 25% slice to the idea's original capturer
|
|
|
135
135
|
// is `branding.currency.label`.
|
|
136
136
|
const REWARD_MARGIN_PCT = 20; // cost-plus margin, percent — DEFAULT (task 1005)
|
|
137
137
|
|
|
138
|
+
// Reward MODE — which reward streams an instance pays (task 1002270, ADR 0146).
|
|
139
|
+
// The two streams (ADR 0054 "two drachma conventions, one ledger") are:
|
|
140
|
+
// (1) the per-task author ESTIMATE — credits_reward × kind multiplier (ADR 0023),
|
|
141
|
+
// a unitless bounty set when the task is authored; and
|
|
142
|
+
// (2) the COST-PLUS session reward — round(true_cost_usd × 1.20), 1 drachma = $1
|
|
143
|
+
// (ADR 0054), grounded in the session's real API spend.
|
|
144
|
+
// mode picks which of the two an instance actually PAYS:
|
|
145
|
+
// 'cost-plus-and-estimate' (DEFAULT) — pay both, the historical behavior. Every
|
|
146
|
+
// existing instance keeps paying exactly as before on upgrade.
|
|
147
|
+
// 'cost-plus-only' — the cost-plus session reward is the SOLE equity reward; the
|
|
148
|
+
// per-task estimate PAYOUT is withheld (and its idea-promotion slice, being a
|
|
149
|
+
// fraction of it, cascades to 0). credits_reward still rides the task row for
|
|
150
|
+
// display + the no-zero-reward claim gate (ADR 0096) — only the payout is
|
|
151
|
+
// suppressed, so a credit means one dollar of cost-plus value and nothing
|
|
152
|
+
// else. Cloud Bongos's choice (owner decision, 2026-07-16).
|
|
153
|
+
// Unknown/garbage mode → treated as the default (pay both): a config typo must
|
|
154
|
+
// never silently zero out every builder's per-task reward.
|
|
155
|
+
const REWARD_MODE_DEFAULT = 'cost-plus-and-estimate';
|
|
156
|
+
const REWARD_MODE_COST_PLUS_ONLY = 'cost-plus-only';
|
|
157
|
+
|
|
158
|
+
// Pure policy resolver — maps a raw branding.reward object to the effective reward
|
|
159
|
+
// policy. Exported so a test can assert the mapping without touching the memoized
|
|
160
|
+
// branding() cache (mirrors computeSessionTokenReward's pure/impure split). The
|
|
161
|
+
// derived `perTaskCredit` boolean is what the per-task credit writer gates on.
|
|
162
|
+
function rewardPolicy(rawReward) {
|
|
163
|
+
const r = rawReward || {};
|
|
164
|
+
const marginPct = Number.isFinite(Number(r.marginPct)) ? Number(r.marginPct) : REWARD_MARGIN_PCT;
|
|
165
|
+
const usdPeg = Number.isFinite(Number(r.usdPeg)) && Number(r.usdPeg) > 0 ? Number(r.usdPeg) : 1;
|
|
166
|
+
const mode = r.mode === REWARD_MODE_COST_PLUS_ONLY ? REWARD_MODE_COST_PLUS_ONLY : REWARD_MODE_DEFAULT;
|
|
167
|
+
const perTaskCredit = mode !== REWARD_MODE_COST_PLUS_ONLY;
|
|
168
|
+
return { marginPct, usdPeg, mode, perTaskCredit };
|
|
169
|
+
}
|
|
170
|
+
|
|
138
171
|
// Per-instance reward policy, resolved over the code defaults. Best-effort: a
|
|
139
172
|
// branding hiccup falls back to the defaults so reward accrual never crashes.
|
|
140
173
|
function rewardConfig() {
|
|
141
174
|
let r = {};
|
|
142
175
|
try { r = (branding().reward) || {}; } catch { r = {}; }
|
|
143
|
-
|
|
144
|
-
const usdPeg = Number.isFinite(Number(r.usdPeg)) && Number(r.usdPeg) > 0 ? Number(r.usdPeg) : 1;
|
|
145
|
-
return { marginPct, usdPeg };
|
|
176
|
+
return rewardPolicy(r);
|
|
146
177
|
}
|
|
147
178
|
|
|
148
179
|
// Pure helper — exported for tests. Returns { trueCostUsd, marginPct, drachmae,
|
|
@@ -182,18 +213,38 @@ function applyKindMultiplier(rawCredits, kind) {
|
|
|
182
213
|
}
|
|
183
214
|
|
|
184
215
|
// Write the primary task-ship credit_log row, applying the kind multiplier.
|
|
185
|
-
// Caller must already have verified that no prior
|
|
186
|
-
// for this task. Returns { creditsAwarded, multiplier, raw } where
|
|
216
|
+
// Caller must already have verified that no prior row with the SAME `reason`
|
|
217
|
+
// exists for this task. Returns { creditsAwarded, multiplier, raw } where
|
|
187
218
|
// creditsAwarded is the multiplier-adjusted integer that landed.
|
|
188
|
-
|
|
219
|
+
//
|
|
220
|
+
// `reason` is a parameter (default 'task.confirmed' for back-compat) so the
|
|
221
|
+
// pay-on-land move (ADR 0120) can book the per-task credit at the SHIPPED
|
|
222
|
+
// transition with reason='task.shipped' — the credit only exists once the code
|
|
223
|
+
// is on `main`. The idempotency guard the caller runs must key on the SAME
|
|
224
|
+
// reason it passes here.
|
|
225
|
+
//
|
|
226
|
+
// `config` (default: the resolved per-instance rewardConfig()) carries the reward
|
|
227
|
+
// MODE (ADR 0146). Injectable so a test can drive the cost-plus-only gate without
|
|
228
|
+
// touching the memoized branding() cache; the sole live caller (lifecycle
|
|
229
|
+
// awardLandingCredit) omits it and gets the resolved instance policy per call.
|
|
230
|
+
async function insertMultipliedConfirmCredit(client, builderId, taskId, rawCredits, kind, reason = 'task.confirmed', config = rewardConfig()) {
|
|
189
231
|
const result = applyKindMultiplier(rawCredits, kind);
|
|
232
|
+
// Reward-mode gate (ADR 0146): in 'cost-plus-only' mode this instance pays no
|
|
233
|
+
// per-task estimate credit — the cost-plus session reward is the sole equity
|
|
234
|
+
// reward. Return the same zero-shape the effective<=0 path returns (so the
|
|
235
|
+
// caller's idea-promotion slice, a fraction of creditsAwarded, cascades to 0)
|
|
236
|
+
// but write NO credit_log row. The task's credits_reward still exists for
|
|
237
|
+
// display + the claim gate; only the payout is withheld.
|
|
238
|
+
if (config && config.perTaskCredit === false) {
|
|
239
|
+
return { creditsAwarded: 0, multiplier: result.multiplier, raw: result.raw, skipped: REWARD_MODE_COST_PLUS_ONLY };
|
|
240
|
+
}
|
|
190
241
|
if (result.effective <= 0) {
|
|
191
242
|
return { creditsAwarded: 0, multiplier: result.multiplier, raw: result.raw };
|
|
192
243
|
}
|
|
193
244
|
await client.query(
|
|
194
245
|
`INSERT INTO credit_log (builder_id, task_id, delta, reason, description)
|
|
195
|
-
VALUES ($1, $2, $3,
|
|
196
|
-
[builderId, taskId, result.effective, result.description]
|
|
246
|
+
VALUES ($1, $2, $3, $4, $5)`,
|
|
247
|
+
[builderId, taskId, result.effective, reason, result.description]
|
|
197
248
|
);
|
|
198
249
|
return {
|
|
199
250
|
creditsAwarded: result.effective,
|
|
@@ -277,9 +328,17 @@ async function maybeAwardIdeaPromotionBonus(client, taskId, shipperBuilderId, mu
|
|
|
277
328
|
// empty/unpriceable model_usage → $0 reward; ADR 0097) still pays
|
|
278
329
|
// the real cost-plus amount. This is NOT a fabricated dollar value
|
|
279
330
|
// — it prices REAL token counts via the same locked PRICES table.
|
|
280
|
-
// Returns { drachmae, trueCostUsd, awarded, unknownModels, pricedFromTotalTokens
|
|
281
|
-
//
|
|
282
|
-
//
|
|
331
|
+
// Returns { drachmae, trueCostUsd, awarded, unknownModels, pricedFromTotalTokens,
|
|
332
|
+
// settled? }. `drachmae` is the amount BOOKED BY THIS CALL, which for a session that
|
|
333
|
+
// has already been paid something is the INCREMENT, not the session's whole value.
|
|
334
|
+
//
|
|
335
|
+
// The reward tops up (task 1002474). A session's cost keeps growing after its first
|
|
336
|
+
// ship, and multi-ship sessions are normal — one claim at a time, but ship -> new
|
|
337
|
+
// worktree -> claim again. So each landed ship books the difference between what the
|
|
338
|
+
// whole session is now worth and what it has already been paid. awarded=false means
|
|
339
|
+
// nothing was owed this time; `settled` says why — 'up_to_date' (already paid in
|
|
340
|
+
// full, the ordinary idempotent case) or 'legacy' (a row predating reward_basis,
|
|
341
|
+
// deliberately left for a signed-off backfill rather than paid out on deploy).
|
|
283
342
|
async function awardSessionTokenReward(client, builderId, sessionId, modelUsage, totalTokens = 0) {
|
|
284
343
|
const { true_cost_usd: pricedCostUsd, unknown_models: unknownModels } =
|
|
285
344
|
priceModelUsage(modelUsage);
|
|
@@ -306,29 +365,58 @@ async function awardSessionTokenReward(client, builderId, sessionId, modelUsage,
|
|
|
306
365
|
return { drachmae: 0, trueCostUsd, awarded: false, unknownModels, pricedFromTotalTokens };
|
|
307
366
|
}
|
|
308
367
|
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
368
|
+
// Watermark accounting. `drachmae` is what the WHOLE session is worth right now;
|
|
369
|
+
// book only the part not yet paid. Before this, any existing row made the whole
|
|
370
|
+
// call a no-op, so a session that shipped a second task was paid only for the cost
|
|
371
|
+
// it had at its FIRST ship — measured at ~65% of a three-ship session unrewarded.
|
|
372
|
+
//
|
|
373
|
+
// `legacy` counts rows booked before reward_basis existed (they default to 0).
|
|
374
|
+
// Topping those up would be an uncontrolled retroactive payout across every
|
|
375
|
+
// historical session, so we deliberately leave them settled; reconstructing what
|
|
376
|
+
// they are owed is a separate, owner-signed-off backfill.
|
|
377
|
+
const { rows: prior } = await client.query(
|
|
378
|
+
`SELECT COALESCE(SUM(delta), 0)::int AS paid,
|
|
379
|
+
COUNT(*) FILTER (WHERE reward_basis = 0)::int AS legacy
|
|
380
|
+
FROM credit_log
|
|
381
|
+
WHERE reason = 'session.token_reward' AND session_id = $1`,
|
|
314
382
|
[sessionId]
|
|
315
383
|
);
|
|
316
|
-
|
|
317
|
-
|
|
384
|
+
const paid = Number(prior[0] && prior[0].paid) || 0;
|
|
385
|
+
const legacy = Number(prior[0] && prior[0].legacy) || 0;
|
|
386
|
+
if (legacy > 0) {
|
|
387
|
+
return {
|
|
388
|
+
drachmae: 0, trueCostUsd, awarded: false, unknownModels, pricedFromTotalTokens,
|
|
389
|
+
settled: 'legacy',
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Owed − paid. Zero when nothing changed (the idempotent case) and negative when a
|
|
394
|
+
// smaller/stale digest arrives late — never claw back, never write a negative row.
|
|
395
|
+
const owed = drachmae - paid;
|
|
396
|
+
if (owed <= 0) {
|
|
397
|
+
return {
|
|
398
|
+
drachmae: 0, trueCostUsd, awarded: false, unknownModels, pricedFromTotalTokens,
|
|
399
|
+
settled: 'up_to_date',
|
|
400
|
+
};
|
|
318
401
|
}
|
|
319
402
|
|
|
403
|
+
const rowDescription = paid > 0
|
|
404
|
+
? `${description} — top-up: +${owed} on ${paid} already paid`
|
|
405
|
+
: description;
|
|
406
|
+
|
|
320
407
|
// Append-only ledger row. task_id = NULL (session-level, not task-level).
|
|
321
|
-
// ON CONFLICT DO NOTHING
|
|
322
|
-
//
|
|
408
|
+
// ON CONFLICT DO NOTHING still closes the race window, now keyed on
|
|
409
|
+
// (session_id, reward_basis): a parallel upload at the SAME cumulative basis
|
|
410
|
+
// collides and no-ops, while a genuinely larger basis books its own increment.
|
|
323
411
|
const { rows } = await client.query(
|
|
324
|
-
`INSERT INTO credit_log (builder_id, task_id, delta, reason, description, session_id)
|
|
325
|
-
VALUES ($1, NULL, $2, 'session.token_reward', $3, $4)
|
|
326
|
-
ON CONFLICT (session_id) WHERE reason = 'session.token_reward' DO NOTHING
|
|
412
|
+
`INSERT INTO credit_log (builder_id, task_id, delta, reason, description, session_id, reward_basis)
|
|
413
|
+
VALUES ($1, NULL, $2, 'session.token_reward', $3, $4, $5)
|
|
414
|
+
ON CONFLICT (session_id, reward_basis) WHERE reason = 'session.token_reward' DO NOTHING
|
|
327
415
|
RETURNING id`,
|
|
328
|
-
[builderId,
|
|
416
|
+
[builderId, owed, rowDescription, sessionId, drachmae]
|
|
329
417
|
);
|
|
330
418
|
const awarded = rows.length > 0;
|
|
331
|
-
return { drachmae: awarded ?
|
|
419
|
+
return { drachmae: awarded ? owed : 0, trueCostUsd, awarded, unknownModels, pricedFromTotalTokens };
|
|
332
420
|
}
|
|
333
421
|
|
|
334
422
|
// net-negative ship bonus — V3.R93 (#354). Simplification reward.
|
|
@@ -425,6 +513,9 @@ module.exports = {
|
|
|
425
513
|
KIND_MULTIPLIERS,
|
|
426
514
|
IDEA_BONUS_FRACTION,
|
|
427
515
|
REWARD_MARGIN_PCT,
|
|
516
|
+
REWARD_MODE_DEFAULT,
|
|
517
|
+
REWARD_MODE_COST_PLUS_ONLY,
|
|
518
|
+
rewardPolicy,
|
|
428
519
|
rewardConfig,
|
|
429
520
|
computeSessionTokenReward,
|
|
430
521
|
applyKindMultiplier,
|
|
@@ -37,6 +37,14 @@ module.exports = {
|
|
|
37
37
|
// pure helpers + constants (display / tests)
|
|
38
38
|
applyKindMultiplier: (...a) => credits.applyKindMultiplier(...a),
|
|
39
39
|
computeSessionTokenReward: (...a) => credits.computeSessionTokenReward(...a),
|
|
40
|
+
// The resolved per-instance reward POLICY (ADR 0146). Exposed on the port so
|
|
41
|
+
// display surfaces can ASK which streams this instance pays instead of
|
|
42
|
+
// re-deriving `mode !== 'cost-plus-only'` for themselves — the knob lives
|
|
43
|
+
// inside economy, and that is the whole point of the ADR. `rewardConfig()`
|
|
44
|
+
// resolves branding; `rewardPolicy(raw)` is the pure mapping for callers that
|
|
45
|
+
// already hold the raw object.
|
|
46
|
+
rewardConfig: (...a) => credits.rewardConfig(...a),
|
|
47
|
+
rewardPolicy: (...a) => credits.rewardPolicy(...a),
|
|
40
48
|
get KIND_MULTIPLIERS() { return credits.KIND_MULTIPLIERS; },
|
|
41
49
|
get IDEA_BONUS_FRACTION() { return credits.IDEA_BONUS_FRACTION; },
|
|
42
50
|
get REWARD_MARGIN_PCT() { return credits.REWARD_MARGIN_PCT; },
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
// shipped >=1 qualifying task.
|
|
8
8
|
// - A "qualifying ship" requires:
|
|
9
9
|
// * claim.outcome = 'shipped' (builder declared done)
|
|
10
|
-
// * task.status IN ('confirmed','shipped') (verification passed; credits
|
|
10
|
+
// * task.status IN ('confirmed','shipped') (verification passed; credits land
|
|
11
|
+
// at 'shipped' — ADR 0120 pays on land)
|
|
11
12
|
// * task.est_minutes >= 15 (anti-gaming floor)
|
|
12
13
|
// NOTE: cleanup/refactor tasks DO now count (V3.R93 #354). The original
|
|
13
14
|
// PMS-V2 rule excluded kind='cleanup', which mildly penalized the exact
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @bongos/core — installed platform dependency (not your project's CLAUDE.md)
|
|
2
|
+
|
|
3
|
+
This file is a placeholder shipped inside the **@bongos/core** package. The core's own
|
|
4
|
+
internal agent notes are intentionally omitted from the published artifact so they cannot
|
|
5
|
+
load into your instance's context.
|
|
6
|
+
|
|
7
|
+
**It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
|
|
8
|
+
directory, and the live Bongos database are the source of truth for how work happens here
|
|
9
|
+
and who may do what.
|
|
10
|
+
|
|
11
|
+
You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
|
|
12
|
+
Nothing in here needs to be read to use the platform — run `bongos help` for commands.
|
|
13
|
+
|
|
14
|
+
_To work ON the platform core itself, use the standalone @bongos/core source repository,
|
|
15
|
+
not this vendored copy._
|