@bongos/core 1.18.3 → 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 +1575 -285
- 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 +4 -2
- 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 +6 -1
- 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/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 +10 -0
- package/docs/api/openapi.json +843 -17
- package/docs/api-reference.md +23 -10
- 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 +8 -0
- 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/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/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/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/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/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/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 +51 -58
- 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 -27
- 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/login.js +16 -1
- package/scripts/gds/paste-token.js +7 -1
- package/scripts/gds/preflight.js +126 -0
- package/scripts/gds/provision.js +123 -12
- package/scripts/gds/publish-manifest.js +7 -1
- package/scripts/gds/reauth.js +7 -3
- package/scripts/gds/regen-instance-docs.js +47 -0
- package/scripts/gds/run-unit-tests.js +4 -2
- 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 +31 -5
- package/scripts/gds/shell.js +5 -3
- package/scripts/gds/ship.js +319 -29
- 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 +124 -2
- package/scripts/status-mirror-sync.js +114 -84
- package/src/bongos/api-errors.js +1 -0
- package/src/bongos/auth.js +447 -102
- package/src/bongos/db-kernel.js +27 -0
- package/src/bongos/db.js +39 -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 +219 -2
- package/src/bongos/routes/backup.js +58 -2
- package/src/bongos/routes/me.js +35 -1
- package/src/bongos/serve-internal.js +84 -15
- package/src/branding.js +32 -0
- package/src/instance-config.js +30 -0
- package/src/module-api.js +112 -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 +12 -0
- 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/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 +93 -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/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/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 +132 -7
- 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 +80 -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 +139 -0
- package/tests/web_admission_status.mjs +162 -0
- package/tests/write_validation_gaps.mjs +110 -0
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
// tests/confirm_attribution.mjs
|
|
2
2
|
//
|
|
3
|
-
// Regression guard for #661
|
|
4
|
-
// /tasks/:id/confirm) ANOTHER builder's completed task, BOTH the credit_log
|
|
5
|
-
// row AND tasks.shipped_by must land with the CLAIM HOLDER (the builder who
|
|
6
|
-
// did the work), never the confirming Archon.
|
|
3
|
+
// Regression guard for #661 + ADR 0120 (pay-on-land).
|
|
7
4
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// (#1) manual-confirmed, and
|
|
13
|
-
// resolves the most-recent claim holder up front and threads
|
|
14
|
-
//
|
|
15
|
-
//
|
|
5
|
+
// #661: when an Archon manual-confirms (POST /tasks/:id/confirm) ANOTHER
|
|
6
|
+
// builder's completed task, tasks.shipped_by must land with the CLAIM HOLDER
|
|
7
|
+
// (the builder who did the work), never the confirming Archon. Live repro
|
|
8
|
+
// 2026-06-04: Xenos (#25) shipped #660, the grader infra-errored so it stalled
|
|
9
|
+
// at `completed`, an Archon (#1) manual-confirmed, and shipped_by must still be
|
|
10
|
+
// #25. confirmTask resolves the most-recent claim holder up front and threads
|
|
11
|
+
// it into the tasks UPDATE; it falls back to the confirmer only when the task
|
|
12
|
+
// has no claim at all.
|
|
13
|
+
//
|
|
14
|
+
// ADR 0120: confirm NO LONGER awards the per-task credit. The credit + idea
|
|
15
|
+
// bonus now land at the confirmed→shipped flip (shipTask → awardLandingCredit,
|
|
16
|
+
// reason='task.shipped'); a task that strands at 'confirmed' pays nothing until
|
|
17
|
+
// its code is on `main`. So confirm must run ZERO credit_log INSERTs and return
|
|
18
|
+
// creditsAwarded === 0 — but it must STILL attribute shipped_by to the claim
|
|
19
|
+
// holder. The credit-at-ship half is pinned by tests/pay_on_land.mjs.
|
|
16
20
|
//
|
|
17
21
|
// confirmTask opens its own transaction via pool.connect(); the #538 `deps.pool`
|
|
18
22
|
// injection seam (honored ONLY when NODE_ENV==='test') lets us drive it with a
|
|
@@ -27,16 +31,13 @@ import { makeRunner, makeSqlAwareClient } from './helpers.mjs';
|
|
|
27
31
|
process.env.NODE_ENV = 'test';
|
|
28
32
|
|
|
29
33
|
const require = createRequire(import.meta.url);
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// achievements/credits objects (stubs would bite if used; confirmTask uses only
|
|
34
|
-
// the credit path).
|
|
34
|
+
// confirmTask resolves the `reward` kernel port for the achievement reconcile
|
|
35
|
+
// (the credit path moved to ship), so the port must be registered before
|
|
36
|
+
// driving it. Stub the achievement hooks to no-ops.
|
|
35
37
|
const achievements = require('../modules/economy/achievements.js');
|
|
36
38
|
achievements.evaluateAfterShip = async () => [];
|
|
37
39
|
achievements.evaluateAfterCreditAward = async () => [];
|
|
38
40
|
achievements.evaluateAfterClaim = async () => [];
|
|
39
|
-
// task 1679: confirmTask (the skip-grade path) now reconciles achievements.
|
|
40
41
|
achievements.reconcileBuilderAchievements = async () => [];
|
|
41
42
|
const seams = require('../src/module-seams.js');
|
|
42
43
|
if (!seams.hasProvider('reward')) seams.registerProvider('reward', require('../modules/economy/reward.js'));
|
|
@@ -48,10 +49,9 @@ const { test, summary } = makeRunner();
|
|
|
48
49
|
// SQL-aware fake: returns rows by matching the statement, records every call so
|
|
49
50
|
// the test can inspect the exact params each query received. Order-independent.
|
|
50
51
|
// claimHolderId === null → simulate a task with NO claims row (fallback path)
|
|
51
|
-
// priorCredit === true → a 'task.confirmed' row already exists (idempotent)
|
|
52
52
|
// taskCompleted === false → the tasks UPDATE matches nothing (wrong state)
|
|
53
|
-
function fakePool({ claimHolderId, task,
|
|
54
|
-
return makeSqlAwareClient((s
|
|
53
|
+
function fakePool({ claimHolderId, task, taskCompleted = true }) {
|
|
54
|
+
return makeSqlAwareClient((s) => {
|
|
55
55
|
if (/SELECT builder_id FROM claims/.test(s)) {
|
|
56
56
|
return { rows: claimHolderId == null ? [] : [{ builder_id: claimHolderId }] };
|
|
57
57
|
}
|
|
@@ -62,13 +62,7 @@ function fakePool({ claimHolderId, task, priorCredit = false, taskCompleted = tr
|
|
|
62
62
|
: [],
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
return { rows: priorCredit ? [{ '?column?': 1 }] : [] };
|
|
67
|
-
}
|
|
68
|
-
if (/SELECT 1 FROM credit_log .* 'idea_promotion_bonus'/.test(s)) return { rows: [] };
|
|
69
|
-
if (/FROM idea_inbox/.test(s)) return { rows: [] }; // no idea lineage
|
|
70
|
-
if (/INSERT INTO credit_log/.test(s)) return { rows: [] };
|
|
71
|
-
return { rows: [] }; // BEGIN / COMMIT / etc.
|
|
65
|
+
return { rows: [] }; // BEGIN / COMMIT / SAVEPOINT / etc.
|
|
72
66
|
});
|
|
73
67
|
}
|
|
74
68
|
|
|
@@ -82,19 +76,15 @@ const WORKER = 25; // the claim holder who actually did the work
|
|
|
82
76
|
const TASK = { id: 660, title: 'fixture task', credits_reward: 15, kind: 'feature' };
|
|
83
77
|
|
|
84
78
|
// =========================================================================
|
|
85
|
-
await test('confirm:
|
|
79
|
+
await test('confirm: pay-on-land — NO credit awarded at confirm, but shipped_by is the claim holder', async () => {
|
|
86
80
|
const pool = fakePool({ claimHolderId: WORKER, task: TASK });
|
|
87
81
|
const out = await confirmTask({ taskId: TASK.id, builderId: ARCHON }, { pool });
|
|
88
82
|
|
|
89
83
|
assert.equal(out.taskStatus, 'confirmed', 'task should flip to confirmed');
|
|
90
84
|
assert.equal(out.creditedBuilderId, WORKER, 'creditedBuilderId must be the claim holder');
|
|
91
|
-
assert.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
assert.ok(credit, 'a credit_log INSERT must run');
|
|
95
|
-
assert.equal(credit.params[0], WORKER, 'credit_log.builder_id must be the claim holder');
|
|
96
|
-
assert.notEqual(credit.params[0], ARCHON, 'credit_log.builder_id must NOT be the Archon');
|
|
97
|
-
assert.equal(credit.params[1], TASK.id, 'credit_log.task_id must be the task');
|
|
85
|
+
assert.equal(out.creditsAwarded, 0, 'ADR 0120: no per-task credit lands at confirm');
|
|
86
|
+
assert.equal(all(pool._client.queries, /INSERT INTO credit_log/).length, 0,
|
|
87
|
+
'confirm must run ZERO credit_log INSERTs — the reward waits for the land');
|
|
98
88
|
});
|
|
99
89
|
|
|
100
90
|
await test('confirm: tasks.shipped_by ($2 of the UPDATE) is attributed to the claim holder', async () => {
|
|
@@ -117,19 +107,13 @@ await test('confirm: the claim-holder lookup happens BEFORE the tasks UPDATE', a
|
|
|
117
107
|
assert.ok(idxClaim < idxUpdate, 'claim holder must be resolved before the tasks UPDATE binds shipped_by');
|
|
118
108
|
});
|
|
119
109
|
|
|
120
|
-
await test('confirm: NO claim on the task → falls back to the confirmer (never refuses a legit confirm)', async () => {
|
|
110
|
+
await test('confirm: NO claim on the task → shipped_by falls back to the confirmer (never refuses a legit confirm)', async () => {
|
|
121
111
|
const pool = fakePool({ claimHolderId: null, task: TASK });
|
|
122
112
|
const out = await confirmTask({ taskId: TASK.id, builderId: ARCHON }, { pool });
|
|
123
113
|
assert.equal(out.creditedBuilderId, ARCHON, 'with no claim, the confirmer is the only candidate');
|
|
124
|
-
const
|
|
125
|
-
assert.equal(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
await test('confirm: idempotent — a prior task.confirmed credit means no second credit insert', async () => {
|
|
129
|
-
const pool = fakePool({ claimHolderId: WORKER, task: TASK, priorCredit: true });
|
|
130
|
-
const out = await confirmTask({ taskId: TASK.id, builderId: ARCHON }, { pool });
|
|
131
|
-
assert.equal(out.creditsAwarded, 0, 'no fresh credit on a re-confirm');
|
|
132
|
-
assert.equal(all(pool._client.queries, /INSERT INTO credit_log/).length, 0, 'must not double-insert credit');
|
|
114
|
+
const taskUpdate = find(pool._client.queries, /^UPDATE tasks/);
|
|
115
|
+
assert.equal(taskUpdate.params[1], ARCHON, 'shipped_by binds to the confirmer when there is no claim');
|
|
116
|
+
assert.equal(all(pool._client.queries, /INSERT INTO credit_log/).length, 0, 'still no credit at confirm');
|
|
133
117
|
});
|
|
134
118
|
|
|
135
119
|
await test('confirm: transaction commits on the happy path (no rollback)', async () => {
|
package/tests/credit_grant.mjs
CHANGED
|
@@ -31,6 +31,9 @@ const {
|
|
|
31
31
|
computeSessionTokenReward,
|
|
32
32
|
awardSessionTokenReward,
|
|
33
33
|
REWARD_MARGIN_PCT,
|
|
34
|
+
REWARD_MODE_DEFAULT,
|
|
35
|
+
REWARD_MODE_COST_PLUS_ONLY,
|
|
36
|
+
rewardPolicy,
|
|
34
37
|
} = require('../modules/economy/credits.js');
|
|
35
38
|
|
|
36
39
|
const { test, summary } = makeRunner();
|
|
@@ -139,9 +142,17 @@ await test('insertMultipliedConfirmCredit: feature pays exactly raw', async () =
|
|
|
139
142
|
assert.equal(res.raw, 40);
|
|
140
143
|
assert.equal(client.queries.length, 1);
|
|
141
144
|
assert.match(client.queries[0].sql, /INSERT INTO credit_log/);
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
+
// ADR 0120: reason is now a BOUND PARAM (default 'task.confirmed'), not a SQL
|
|
146
|
+
// literal, so the pay-on-land move can book 'task.shipped' at the land.
|
|
147
|
+
// params: [builderId, taskId, delta, reason, description]
|
|
148
|
+
assert.deepEqual(client.queries[0].params, [1, 100, 40, 'task.confirmed', '40c × 1.0 = 40c (feature)']);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await test('insertMultipliedConfirmCredit: reason param books task.shipped (ADR 0120 pay-on-land)', async () => {
|
|
152
|
+
const client = fakeClient();
|
|
153
|
+
await insertMultipliedConfirmCredit(client, 1, 100, 40, 'feature', 'task.shipped');
|
|
154
|
+
assert.equal(client.queries[0].params[3], 'task.shipped', 'reason param must reach credit_log.reason');
|
|
155
|
+
assert.equal(client.queries[0].params[4], '40c × 1.0 = 40c (feature)', 'description follows reason');
|
|
145
156
|
});
|
|
146
157
|
|
|
147
158
|
await test('insertMultipliedConfirmCredit: learning-capture pays 1.5×', async () => {
|
|
@@ -149,7 +160,7 @@ await test('insertMultipliedConfirmCredit: learning-capture pays 1.5×', async (
|
|
|
149
160
|
const res = await insertMultipliedConfirmCredit(client, 1, 100, 40, 'learning-capture');
|
|
150
161
|
assert.equal(res.creditsAwarded, 60);
|
|
151
162
|
assert.equal(client.queries[0].params[2], 60);
|
|
152
|
-
assert.equal(client.queries[0].params[
|
|
163
|
+
assert.equal(client.queries[0].params[4], '40c × 1.5 = 60c (learning-capture)');
|
|
153
164
|
});
|
|
154
165
|
|
|
155
166
|
await test('insertMultipliedConfirmCredit: cleanup pays 0.8×', async () => {
|
|
@@ -273,9 +284,14 @@ await test('computeSessionTokenReward: $1.65 × 1.20 = 2 drachmae ($1=1 drachma)
|
|
|
273
284
|
assert.equal(computeSessionTokenReward(100).drachmae, 120, '$100 × 1.20 = 120 drachmae ≈ dollars');
|
|
274
285
|
});
|
|
275
286
|
|
|
287
|
+
// The watermark SELECT's row shape (task 1002474). `paid` = drachmae already booked
|
|
288
|
+
// for this session; `legacy` = rows predating the top-up column (reward_basis = 0).
|
|
289
|
+
const ledger = (paid = 0, legacy = 0) => [{ paid, legacy }];
|
|
290
|
+
const NO_PRIOR = ledger(0, 0);
|
|
291
|
+
|
|
276
292
|
await test('awardSessionTokenReward: prices opus tokens server-side + inserts ONE credit_log row', async () => {
|
|
277
293
|
const client = fakeClient();
|
|
278
|
-
client.enqueue(
|
|
294
|
+
client.enqueue(NO_PRIOR); // watermark SELECT → nothing paid yet
|
|
279
295
|
client.enqueue([{ id: 1 }]); // INSERT ... RETURNING id
|
|
280
296
|
const res = await awardSessionTokenReward(client, 7, 'sess-abc', {
|
|
281
297
|
'claude-opus-4-8': { input: 110_000 }, // 110k × $5/M = $0.55 (Opus 4.8, task 1343)
|
|
@@ -286,14 +302,16 @@ await test('awardSessionTokenReward: prices opus tokens server-side + inserts ON
|
|
|
286
302
|
const insert = client.queries[1];
|
|
287
303
|
assert.match(insert.sql, /INSERT INTO credit_log/);
|
|
288
304
|
assert.match(insert.sql, /session\.token_reward/);
|
|
305
|
+
// params gained reward_basis (the cumulative owed total) as the last positional.
|
|
289
306
|
assert.deepEqual(insert.params, [
|
|
290
|
-
7, 1, '$0.55 true cost × 1.20 = 1 drachmae (token reward)', 'sess-abc',
|
|
307
|
+
7, 1, '$0.55 true cost × 1.20 = 1 drachmae (token reward)', 'sess-abc', 1,
|
|
291
308
|
]);
|
|
292
309
|
});
|
|
293
310
|
|
|
294
|
-
await test('awardSessionTokenReward: idempotent —
|
|
311
|
+
await test('awardSessionTokenReward: idempotent — nothing further owed → no INSERT, no double payout', async () => {
|
|
295
312
|
const client = fakeClient();
|
|
296
|
-
|
|
313
|
+
// The session already collected everything it is owed at this cost level.
|
|
314
|
+
client.enqueue(ledger(1, 0));
|
|
297
315
|
const res = await awardSessionTokenReward(client, 7, 'sess-abc', {
|
|
298
316
|
'claude-opus-4-8': { input: 110_000 },
|
|
299
317
|
});
|
|
@@ -304,7 +322,7 @@ await test('awardSessionTokenReward: idempotent — prior row → no INSERT, no
|
|
|
304
322
|
|
|
305
323
|
await test('awardSessionTokenReward: ON CONFLICT race → INSERT returns no row → clean no-op', async () => {
|
|
306
324
|
const client = fakeClient();
|
|
307
|
-
client.enqueue(
|
|
325
|
+
client.enqueue(NO_PRIOR);
|
|
308
326
|
client.enqueue([]); // INSERT swallowed by ON CONFLICT DO NOTHING (RETURNING empty)
|
|
309
327
|
const res = await awardSessionTokenReward(client, 7, 'sess-x', {
|
|
310
328
|
'claude-opus-4-8': { input: 110_000 },
|
|
@@ -315,7 +333,7 @@ await test('awardSessionTokenReward: ON CONFLICT race → INSERT returns no row
|
|
|
315
333
|
|
|
316
334
|
await test('awardSessionTokenReward: unknown model still pays (opus rate) + flags unknownModels', async () => {
|
|
317
335
|
const client = fakeClient();
|
|
318
|
-
client.enqueue(
|
|
336
|
+
client.enqueue(NO_PRIOR);
|
|
319
337
|
client.enqueue([{ id: 2 }]);
|
|
320
338
|
const res = await awardSessionTokenReward(client, 7, 'sess-u', { 'gpt-4o': { input: 1_000_000 } });
|
|
321
339
|
assert.equal(res.awarded, true);
|
|
@@ -323,6 +341,100 @@ await test('awardSessionTokenReward: unknown model still pays (opus rate) + flag
|
|
|
323
341
|
assert.ok(res.unknownModels.includes('gpt-4o'));
|
|
324
342
|
});
|
|
325
343
|
|
|
344
|
+
// =========================================================================
|
|
345
|
+
// task 1002474 — a multi-ship session must be paid for ALL of its cost.
|
|
346
|
+
//
|
|
347
|
+
// The reward was a hard no-op once ANY row existed for the session_id, so a
|
|
348
|
+
// session that shipped a second task was paid only for the cost it had accrued
|
|
349
|
+
// at its FIRST ship. Measured: three ships from one session priced at $14.30 /
|
|
350
|
+
// $27.76 / $40.32 paid 17 drachmae total, where the full session was worth 48 —
|
|
351
|
+
// about 65% of real shipped work unrewarded. Multi-ship sessions are normal
|
|
352
|
+
// (one claim at a time, but ship → new worktree → claim again).
|
|
353
|
+
//
|
|
354
|
+
// The cure is watermark accounting: book the DIFFERENCE between what the whole
|
|
355
|
+
// session is now worth and what it has already been paid.
|
|
356
|
+
// =========================================================================
|
|
357
|
+
|
|
358
|
+
await test('1002474 a later, more expensive ship books only the INCREMENT', async () => {
|
|
359
|
+
const client = fakeClient();
|
|
360
|
+
client.enqueue(ledger(6, 0)); // already paid 6 for this session
|
|
361
|
+
client.enqueue([{ id: 9 }]);
|
|
362
|
+
// $20 of opus input = 4M tokens × $5/M → 20 × 1.20 = 24 owed in total.
|
|
363
|
+
const res = await awardSessionTokenReward(client, 7, 'sess-multi', {
|
|
364
|
+
'claude-opus-4-8': { input: 4_000_000 },
|
|
365
|
+
});
|
|
366
|
+
assert.equal(res.awarded, true);
|
|
367
|
+
assert.equal(res.drachmae, 18, '24 owed − 6 already paid = 18, not a second full 24');
|
|
368
|
+
const insert = client.queries[1];
|
|
369
|
+
assert.equal(insert.params[1], 18, 'the ledger row carries the INCREMENT');
|
|
370
|
+
assert.equal(insert.params[4], 24, 'reward_basis is the CUMULATIVE owed total');
|
|
371
|
+
assert.match(insert.params[2], /top-up/i, 'the audit string must show this is a top-up');
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
await test('1002474 the running total never exceeds the full-session value', async () => {
|
|
375
|
+
// Three ships of one session, exactly the shape that was measured.
|
|
376
|
+
const usageFor = (dollars) => ({ 'claude-opus-4-8': { input: (dollars / 5) * 1_000_000 } });
|
|
377
|
+
let paid = 0;
|
|
378
|
+
for (const dollars of [14.30, 27.76, 40.32]) {
|
|
379
|
+
const client = fakeClient();
|
|
380
|
+
client.enqueue(ledger(paid, 0));
|
|
381
|
+
client.enqueue([{ id: 1 }]);
|
|
382
|
+
const res = await awardSessionTokenReward(client, 7, 'sess-3ship', usageFor(dollars));
|
|
383
|
+
paid += res.drachmae;
|
|
384
|
+
}
|
|
385
|
+
assert.equal(paid, Math.round(40.32 * 1.2), 'the session ends up paid its full cost-plus value');
|
|
386
|
+
assert.equal(paid, 48);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
await test('1002474 a cheaper re-upload never claws back or pays again', async () => {
|
|
390
|
+
const client = fakeClient();
|
|
391
|
+
client.enqueue(ledger(48, 0)); // already fully paid at the $40.32 level
|
|
392
|
+
const res = await awardSessionTokenReward(client, 7, 'sess-3ship', {
|
|
393
|
+
'claude-opus-4-8': { input: 1_000_000 }, // a stale/smaller digest → owes 6
|
|
394
|
+
});
|
|
395
|
+
assert.equal(res.awarded, false, 'owed − paid is negative → nothing happens');
|
|
396
|
+
assert.equal(res.drachmae, 0, 'never a negative credit_log row');
|
|
397
|
+
assert.equal(client.queries.length, 1, 'no INSERT at all');
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
await test('1002474 a LEGACY session (pre-migration row) is left alone for the backfill to decide', async () => {
|
|
401
|
+
const client = fakeClient();
|
|
402
|
+
// reward_basis = 0 marks a row booked before this change. Topping it up would be
|
|
403
|
+
// an uncontrolled retroactive payout; the backfill (task 1002448) owns that call.
|
|
404
|
+
client.enqueue(ledger(17, 1));
|
|
405
|
+
const res = await awardSessionTokenReward(client, 7, 'sess-legacy', {
|
|
406
|
+
'claude-opus-4-8': { input: 8_000_000 }, // would otherwise owe far more
|
|
407
|
+
});
|
|
408
|
+
assert.equal(res.awarded, false, 'no surprise retroactive payout on deploy');
|
|
409
|
+
assert.equal(res.drachmae, 0);
|
|
410
|
+
assert.equal(res.settled, 'legacy', 'and it says WHY, so this is diagnosable');
|
|
411
|
+
assert.equal(client.queries.length, 1, 'no INSERT');
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
await test('1002474 a single-ship session pays exactly what it paid before (no regression)', async () => {
|
|
415
|
+
const client = fakeClient();
|
|
416
|
+
client.enqueue(NO_PRIOR);
|
|
417
|
+
client.enqueue([{ id: 3 }]);
|
|
418
|
+
const res = await awardSessionTokenReward(client, 7, 'sess-single', {
|
|
419
|
+
'claude-opus-4-8': { input: 4_000_000 }, // $20 → 24
|
|
420
|
+
});
|
|
421
|
+
assert.equal(res.awarded, true);
|
|
422
|
+
assert.equal(res.drachmae, 24);
|
|
423
|
+
assert.equal(client.queries[1].params[1], 24, 'first payment is the whole amount, unchanged');
|
|
424
|
+
assert.doesNotMatch(client.queries[1].params[2], /top-up/i, 'a first payment is not labelled a top-up');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
await test('1002474 the INSERT still carries a DB-level double-pay backstop', async () => {
|
|
428
|
+
const client = fakeClient();
|
|
429
|
+
client.enqueue(NO_PRIOR);
|
|
430
|
+
client.enqueue([{ id: 4 }]);
|
|
431
|
+
await awardSessionTokenReward(client, 7, 'sess-guard', { 'claude-opus-4-8': { input: 4_000_000 } });
|
|
432
|
+
const insert = client.queries[1];
|
|
433
|
+
assert.match(insert.sql, /ON CONFLICT/, 'the race guard must survive the rewrite');
|
|
434
|
+
assert.match(insert.sql, /DO NOTHING/);
|
|
435
|
+
assert.match(insert.sql, /reward_basis/, 'and it must be keyed on (session_id, reward_basis)');
|
|
436
|
+
});
|
|
437
|
+
|
|
326
438
|
// task 1676 — the cost-plus zero-out fix for the dev-box/CI agent ship path.
|
|
327
439
|
// Symptom (ADR 0097): those sessions uploaded empty/unpriceable model_usage, so
|
|
328
440
|
// priceModelUsage returned $0 and the reward rounded to 0 — even though real
|
|
@@ -370,6 +482,82 @@ await test('REWARD_MARGIN_PCT exported as 20', () => {
|
|
|
370
482
|
assert.equal(REWARD_MARGIN_PCT, 20);
|
|
371
483
|
});
|
|
372
484
|
|
|
485
|
+
// =========================================================================
|
|
486
|
+
// (5) reward MODE — cost-plus-only vs cost-plus-and-estimate (task 1002270,
|
|
487
|
+
// ADR 0146). rewardPolicy() maps a raw branding.reward object → the
|
|
488
|
+
// effective policy (pure, so no branding()-cache juggling); the derived
|
|
489
|
+
// perTaskCredit boolean is what insertMultipliedConfirmCredit gates the
|
|
490
|
+
// per-task ESTIMATE payout on. The cost-plus session reward is untouched.
|
|
491
|
+
// =========================================================================
|
|
492
|
+
|
|
493
|
+
await test('rewardPolicy: default (no mode) → pay both streams; margin/peg resolve', () => {
|
|
494
|
+
const p = rewardPolicy({});
|
|
495
|
+
assert.equal(p.mode, REWARD_MODE_DEFAULT);
|
|
496
|
+
assert.equal(p.perTaskCredit, true, 'a default instance pays the per-task estimate');
|
|
497
|
+
assert.equal(p.marginPct, 20);
|
|
498
|
+
assert.equal(p.usdPeg, 1);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
await test('rewardPolicy: mode "cost-plus-only" → per-task estimate payout OFF, cost-plus untouched', () => {
|
|
502
|
+
const p = rewardPolicy({ mode: 'cost-plus-only', marginPct: 20, usdPeg: 1 });
|
|
503
|
+
assert.equal(p.mode, REWARD_MODE_COST_PLUS_ONLY);
|
|
504
|
+
assert.equal(p.perTaskCredit, false, 'cost-plus-only suppresses the per-task estimate payout');
|
|
505
|
+
assert.equal(p.marginPct, 20, 'the cost-plus margin is unaffected — that stream is still paid');
|
|
506
|
+
assert.equal(p.usdPeg, 1);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
await test('rewardPolicy: unknown/garbage mode → default (a typo never silently zeroes rewards)', () => {
|
|
510
|
+
for (const bad of ['costplus', 'COST-PLUS-ONLY', 'only', '', null, undefined, 42]) {
|
|
511
|
+
const p = rewardPolicy({ mode: bad });
|
|
512
|
+
assert.equal(p.perTaskCredit, true, `garbage mode ${JSON.stringify(bad)} must default to paying`);
|
|
513
|
+
assert.equal(p.mode, REWARD_MODE_DEFAULT);
|
|
514
|
+
}
|
|
515
|
+
assert.equal(rewardPolicy(null).perTaskCredit, true, 'a missing reward object → default');
|
|
516
|
+
assert.equal(rewardPolicy(undefined).perTaskCredit, true);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
await test('rewardPolicy: cost-plus-only still honors a custom margin + peg', () => {
|
|
520
|
+
const p = rewardPolicy({ mode: 'cost-plus-only', marginPct: 35, usdPeg: 2 });
|
|
521
|
+
assert.equal(p.perTaskCredit, false);
|
|
522
|
+
assert.equal(p.marginPct, 35);
|
|
523
|
+
assert.equal(p.usdPeg, 2);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
await test('insertMultipliedConfirmCredit: cost-plus-only mode writes NO credit_log row', async () => {
|
|
527
|
+
const client = fakeClient();
|
|
528
|
+
const res = await insertMultipliedConfirmCredit(
|
|
529
|
+
client, 7, 100, 60, 'feature', 'task.shipped', { perTaskCredit: false }
|
|
530
|
+
);
|
|
531
|
+
assert.equal(res.creditsAwarded, 0, 'no per-task estimate credit paid');
|
|
532
|
+
assert.equal(res.skipped, REWARD_MODE_COST_PLUS_ONLY, 'reports why it was skipped');
|
|
533
|
+
assert.equal(client.queries.length, 0, 'no INSERT (nor SELECT) — the estimate payout is fully suppressed');
|
|
534
|
+
assert.equal(res.raw, 60, 'raw + multiplier still reported for the audit shape');
|
|
535
|
+
assert.equal(res.multiplier, KIND_MULTIPLIERS.feature);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
await test('insertMultipliedConfirmCredit: default/both mode still writes the row (behavior preserved)', async () => {
|
|
539
|
+
const client = fakeClient();
|
|
540
|
+
client.enqueue([{ id: 1 }]); // the INSERT
|
|
541
|
+
const res = await insertMultipliedConfirmCredit(
|
|
542
|
+
client, 7, 100, 60, 'feature', 'task.shipped', { perTaskCredit: true }
|
|
543
|
+
);
|
|
544
|
+
assert.equal(res.creditsAwarded, 60, '60 × 1.0 (feature) still paid when both streams are on');
|
|
545
|
+
assert.equal(client.queries.length, 1, 'the credit_log INSERT runs');
|
|
546
|
+
assert.match(client.queries[0].sql, /INSERT INTO credit_log/);
|
|
547
|
+
assert.equal(client.queries[0].params[3], 'task.shipped', 'booked under the pay-on-land reason');
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
await test('cost-plus-only cascade: a 0 per-task credit → idea-promotion slice is 0 (no bonus row)', async () => {
|
|
551
|
+
// In cost-plus-only mode the lifecycle's awardLandingCredit passes the (now 0)
|
|
552
|
+
// creditsAwarded into maybeAwardIdeaPromotionBonus, whose 25% slice of the
|
|
553
|
+
// unpaid estimate must therefore also be 0 — no lineage lookup, no bonus row.
|
|
554
|
+
// This mirrors the live lifecycle cascade without importing that module.
|
|
555
|
+
const client = fakeClient();
|
|
556
|
+
const res = await maybeAwardIdeaPromotionBonus(client, 100, 1, 0);
|
|
557
|
+
assert.equal(res.ideaBonusAwarded, 0);
|
|
558
|
+
assert.equal(client.queries.length, 0, 'no lineage lookup, no bonus INSERT when the base credit is 0');
|
|
559
|
+
});
|
|
560
|
+
|
|
373
561
|
// -------------------------------------------------------------------------
|
|
374
562
|
// runner summary
|
|
375
563
|
// -------------------------------------------------------------------------
|