@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
package/tests/branding.mjs
CHANGED
|
@@ -19,12 +19,15 @@ const NEUTRAL = {
|
|
|
19
19
|
envPrefix: 'CLOUDBONGOS',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
// A GENERIC sample instance pack — exercises the resolver's override/merge/precedence logic
|
|
23
|
+
// without baking any real instance's identity into the (published) test. (task 1002219 / audit H5:
|
|
24
|
+
// swept the former customer-1 sample — Off the Boats / amazonprimea / Hermeslines — to neutral.)
|
|
25
|
+
const SAMPLE = {
|
|
26
|
+
identity: { productName: 'Acme Rockets', worldName: 'Acme', company: { name: 'Acme Corp', site: 'https://acme.example' } },
|
|
27
|
+
llmProjectName: 'Acme Rockets',
|
|
28
|
+
domains: { publicOrigin: 'https://acme.example', cookieDomain: '.acme.example' },
|
|
29
|
+
currency: { label: 'tokens' },
|
|
30
|
+
envPrefix: 'ACME',
|
|
28
31
|
};
|
|
29
32
|
|
|
30
33
|
test('neutral starter resolves to the plain identity (no host leak)', () => {
|
|
@@ -34,33 +37,42 @@ test('neutral starter resolves to the plain identity (no host leak)', () => {
|
|
|
34
37
|
});
|
|
35
38
|
|
|
36
39
|
test('instance config overrides the neutral starter, deep-merged', () => {
|
|
37
|
-
const b = resolveBranding({ neutral: NEUTRAL, instance:
|
|
38
|
-
assert.equal(b.identity.worldName, '
|
|
39
|
-
assert.equal(b.identity.company.name, '
|
|
40
|
-
assert.equal(b.domains.cookieDomain, '.
|
|
40
|
+
const b = resolveBranding({ neutral: NEUTRAL, instance: SAMPLE });
|
|
41
|
+
assert.equal(b.identity.worldName, 'Acme');
|
|
42
|
+
assert.equal(b.identity.company.name, 'Acme Corp');
|
|
43
|
+
assert.equal(b.domains.cookieDomain, '.acme.example');
|
|
41
44
|
// a field present only in neutral survives the merge:
|
|
42
45
|
assert.equal(b.domains.buildersOrigin, 'http://localhost:3000');
|
|
43
46
|
});
|
|
44
47
|
|
|
45
48
|
test('env override beats instance config (precedence)', () => {
|
|
46
|
-
const b = resolveBranding({ neutral: NEUTRAL, instance:
|
|
49
|
+
const b = resolveBranding({ neutral: NEUTRAL, instance: SAMPLE, env: { ACME_PUBLIC_ORIGIN: 'https://staging.example.com' } });
|
|
47
50
|
assert.equal(b.domains.publicOrigin, 'https://staging.example.com');
|
|
48
51
|
});
|
|
49
52
|
|
|
50
53
|
test('env override is ignored when empty', () => {
|
|
51
|
-
const b = resolveBranding({ neutral: NEUTRAL, instance:
|
|
52
|
-
assert.equal(b.domains.publicOrigin, 'https://
|
|
54
|
+
const b = resolveBranding({ neutral: NEUTRAL, instance: SAMPLE, env: { ACME_PUBLIC_ORIGIN: '' } });
|
|
55
|
+
assert.equal(b.domains.publicOrigin, 'https://acme.example');
|
|
53
56
|
});
|
|
54
57
|
|
|
55
58
|
test('provisioningOrigin: optional, passes through config, env-overridable (task 2101)', () => {
|
|
56
59
|
// Not required — unset resolves to undefined, and callers fall back to publicOrigin.
|
|
57
|
-
assert.equal(resolveBranding({ neutral: NEUTRAL, instance:
|
|
60
|
+
assert.equal(resolveBranding({ neutral: NEUTRAL, instance: SAMPLE }).domains.provisioningOrigin, undefined);
|
|
58
61
|
// Set in the instance pack, it survives the deep-merge.
|
|
59
|
-
const inst = resolveBranding({ neutral: NEUTRAL, instance: { ...
|
|
60
|
-
assert.equal(inst.domains.provisioningOrigin, 'https://get.
|
|
62
|
+
const inst = resolveBranding({ neutral: NEUTRAL, instance: { ...SAMPLE, domains: { ...SAMPLE.domains, provisioningOrigin: 'https://get.acme.example' } } });
|
|
63
|
+
assert.equal(inst.domains.provisioningOrigin, 'https://get.acme.example');
|
|
61
64
|
// And <PREFIX>_PROVISIONING_ORIGIN beats config (same precedence as the other origins).
|
|
62
|
-
const env = resolveBranding({ neutral: NEUTRAL, instance:
|
|
63
|
-
assert.equal(env.domains.provisioningOrigin, 'https://get.
|
|
65
|
+
const env = resolveBranding({ neutral: NEUTRAL, instance: SAMPLE, env: { ACME_PROVISIONING_ORIGIN: 'https://get.acme.example' } });
|
|
66
|
+
assert.equal(env.domains.provisioningOrigin, 'https://get.acme.example');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('landing.postLogin: config passes through, <PREFIX>_POST_LOGIN_LANDING overrides (task 1002240)', () => {
|
|
70
|
+
// Set in the instance pack, it survives the deep-merge.
|
|
71
|
+
const inst = resolveBranding({ neutral: NEUTRAL, instance: { ...SAMPLE, landing: { postLogin: '/gate/' } } });
|
|
72
|
+
assert.equal(inst.landing.postLogin, '/gate/');
|
|
73
|
+
// And the env override beats config (same precedence as the origins).
|
|
74
|
+
const env = resolveBranding({ neutral: NEUTRAL, instance: { ...SAMPLE, landing: { postLogin: '/builders' } }, env: { ACME_POST_LOGIN_LANDING: '/gate/' } });
|
|
75
|
+
assert.equal(env.landing.postLogin, '/gate/');
|
|
64
76
|
});
|
|
65
77
|
|
|
66
78
|
test('missing required field throws', () => {
|
|
@@ -80,16 +92,22 @@ test('deepMerge does not mutate its inputs', () => {
|
|
|
80
92
|
assert.deepEqual(out, { a: { x: 1, y: 2 } });
|
|
81
93
|
});
|
|
82
94
|
|
|
83
|
-
test('the committed neutral +
|
|
95
|
+
test('the committed neutral pack loads + resolves to the neutral identity (the trunk carries no instance branding.json)', () => {
|
|
96
|
+
// The neutral trunk ships config/branding.neutral.json only — no config/branding.json — so
|
|
97
|
+
// loadBranding resolves to the plain Cloud Bongos identity (task 1002219 / audit H5: this used to
|
|
98
|
+
// assert the customer-1 OTB identity, stale since the amazonprimea→cloudbongos rebrand, ADR 0139/0140).
|
|
84
99
|
const b = loadBranding({ env: {} });
|
|
85
|
-
assert.equal(b.identity.productName, '
|
|
86
|
-
assert.
|
|
87
|
-
assert.
|
|
100
|
+
assert.equal(b.identity.productName, 'Cloud Bongos');
|
|
101
|
+
assert.equal(b.identity.worldName, 'Cloud Bongos');
|
|
102
|
+
assert.ok(b.domains.publicOrigin.startsWith('http://localhost'));
|
|
103
|
+
// The portable post-login landing default is the hall — never a /gate/ orbs
|
|
104
|
+
// site (which only a platform-home instance ships). Task 1002240.
|
|
105
|
+
assert.equal(b.landing.postLogin, '/builders');
|
|
88
106
|
});
|
|
89
107
|
|
|
90
108
|
test('resolveBranding does not freeze its input objects (no shared refs)', () => {
|
|
91
109
|
const neutral = structuredClone(NEUTRAL);
|
|
92
|
-
const instance = structuredClone(
|
|
110
|
+
const instance = structuredClone(SAMPLE);
|
|
93
111
|
resolveBranding({ neutral, instance });
|
|
94
112
|
assert.equal(Object.isFrozen(neutral), false);
|
|
95
113
|
assert.equal(Object.isFrozen(neutral.domains), false);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// tests/cf_ruleset_preflight.mjs — DB-free, network-free unit tests for the
|
|
2
|
+
// Cloudflare RULESET scope preflight (scripts/gds/cf-tunnel.js
|
|
3
|
+
// classifyRulesetScope + checkRulesetScope, task 1002511).
|
|
4
|
+
//
|
|
5
|
+
// WHY ITS OWN FILE: `cf_tunnel` is in run-unit-tests.js's INTEGRATION exclusion
|
|
6
|
+
// set, so anything added only to tests/cf_tunnel.mjs never runs in the default
|
|
7
|
+
// DB-free unit gate that /builder-ship smokes. This file is auto-discovered by
|
|
8
|
+
// that gate, so the preflight's behaviour is actually enforced on every ship.
|
|
9
|
+
//
|
|
10
|
+
// Run: node tests/cf_ruleset_preflight.mjs
|
|
11
|
+
import { test } from 'node:test';
|
|
12
|
+
import assert from 'node:assert/strict';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const cf = require('../scripts/gds/cf-tunnel.js');
|
|
16
|
+
|
|
17
|
+
const PHASE = 'http_request_late_transform';
|
|
18
|
+
|
|
19
|
+
// ── the constants the rule + the preflight share ────────────────────────────
|
|
20
|
+
test('the cookie-strip rule constants are exported for callers + this test', () => {
|
|
21
|
+
assert.equal(cf.COOKIE_STRIP_PHASE, PHASE);
|
|
22
|
+
assert.match(cf.COOKIE_STRIP_RULE_DESC, /Cookie/);
|
|
23
|
+
assert.match(cf.COOKIE_STRIP_EXPRESSION, /term-/);
|
|
24
|
+
assert.match(cf.COOKIE_STRIP_EXPRESSION, /sandbox-/);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// ── classifyRulesetScope: PURE, and a NEGATIVE detector only ────────────────
|
|
28
|
+
test('classifyRulesetScope: 403 → NOT readable + guidance naming the zone and Transform Rules', () => {
|
|
29
|
+
const r = cf.classifyRulesetScope({
|
|
30
|
+
status: 403,
|
|
31
|
+
body: { success: false, errors: [{ message: 'Actor does not have permission' }] },
|
|
32
|
+
phase: PHASE,
|
|
33
|
+
zone: 'cloudbongos.com',
|
|
34
|
+
});
|
|
35
|
+
assert.equal(r.readable, false);
|
|
36
|
+
assert.match(r.guidance, /Transform Rules/);
|
|
37
|
+
assert.match(r.guidance, /Cache Rules/);
|
|
38
|
+
assert.match(r.guidance, /cloudbongos\.com/);
|
|
39
|
+
assert.match(r.guidance, /API Tokens/i);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('classifyRulesetScope: 401 → NOT readable (definitively no scope)', () => {
|
|
43
|
+
const r = cf.classifyRulesetScope({ status: 401, body: null, phase: PHASE, zone: 'cloudbongos.com' });
|
|
44
|
+
assert.equal(r.readable, false);
|
|
45
|
+
assert.match(r.reason, /401/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('classifyRulesetScope: 200 → readable, and NEVER claims edit scope is confirmed', () => {
|
|
49
|
+
const r = cf.classifyRulesetScope({
|
|
50
|
+
status: 200,
|
|
51
|
+
body: { success: true, result: { id: 'rs1', rules: [] } },
|
|
52
|
+
phase: PHASE,
|
|
53
|
+
zone: 'cloudbongos.com',
|
|
54
|
+
});
|
|
55
|
+
assert.equal(r.readable, true);
|
|
56
|
+
// HONESTY GATE: a GET proves READ, not EDIT. There must be no `editable` field,
|
|
57
|
+
// and the human-readable text must not read as a confirmed security control.
|
|
58
|
+
assert.equal('editable' in r, false, 'a read probe must never report an `editable` verdict');
|
|
59
|
+
assert.doesNotMatch(`${r.reason} ${r.guidance || ''}`, /scope confirmed|edit scope (is )?(confirmed|verified|present)/i);
|
|
60
|
+
assert.match(r.reason, /not proven|read/i, 'the reason must say edit scope is unproven');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('classifyRulesetScope: 404 entrypoint → readable (an empty phase is legitimate)', () => {
|
|
64
|
+
const r = cf.classifyRulesetScope({
|
|
65
|
+
status: 404,
|
|
66
|
+
body: { success: false, errors: [{ message: 'ruleset not found' }] },
|
|
67
|
+
phase: PHASE,
|
|
68
|
+
zone: 'cloudbongos.com',
|
|
69
|
+
});
|
|
70
|
+
assert.equal(r.readable, true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('classifyRulesetScope: 500 → NOT readable, reported as inconclusive (never a pass)', () => {
|
|
74
|
+
const r = cf.classifyRulesetScope({ status: 500, body: null, phase: PHASE, zone: 'cloudbongos.com' });
|
|
75
|
+
assert.equal(r.readable, false);
|
|
76
|
+
assert.match(r.reason, /inconclusive|failed/i);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ── checkRulesetScope: its OWN raw fetch, injectable, never a real call ─────
|
|
80
|
+
test('checkRulesetScope: probes the phase entrypoint (NOT /user/tokens/verify) and reports readable', async () => {
|
|
81
|
+
const calls = [];
|
|
82
|
+
const fetchImpl = async (url, opts = {}) => {
|
|
83
|
+
calls.push({ url, auth: opts.headers.Authorization });
|
|
84
|
+
if (url.includes('/zones?name=')) return { status: 200, json: async () => ({ success: true, result: [{ id: 'zoneA', name: 'cloudbongos.com' }] }) };
|
|
85
|
+
return { status: 200, json: async () => ({ success: true, result: { id: 'rs1', rules: [] } }) };
|
|
86
|
+
};
|
|
87
|
+
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: 'tok_1', fetchImpl });
|
|
88
|
+
assert.equal(r.readable, true);
|
|
89
|
+
assert.equal(r.zone, 'cloudbongos.com');
|
|
90
|
+
const probe = calls[calls.length - 1];
|
|
91
|
+
assert.match(probe.url, new RegExp(`/zones/zoneA/rulesets/phases/${PHASE}/entrypoint$`));
|
|
92
|
+
assert.equal(probe.auth, 'Bearer tok_1');
|
|
93
|
+
// An IP-locked token returns "Invalid API Token" from verify (builder-box-lifecycle
|
|
94
|
+
// learning) — the preflight must probe the real operation instead.
|
|
95
|
+
assert.ok(!calls.some((c) => /tokens\/verify/.test(c.url)), 'must not use /user/tokens/verify');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('checkRulesetScope: 403 on the entrypoint → NOT readable + actionable guidance', async () => {
|
|
99
|
+
const fetchImpl = async (url) => {
|
|
100
|
+
if (url.includes('/zones?name=')) return { status: 200, json: async () => ({ success: true, result: [{ id: 'zoneA', name: 'cloudbongos.com' }] }) };
|
|
101
|
+
return { status: 403, json: async () => ({ success: false, errors: [{ message: 'Actor does not have permission' }] }) };
|
|
102
|
+
};
|
|
103
|
+
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: 'tok_1', fetchImpl });
|
|
104
|
+
assert.equal(r.readable, false);
|
|
105
|
+
assert.match(r.guidance, /Transform Rules/);
|
|
106
|
+
assert.match(r.guidance, /cloudbongos\.com/);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('checkRulesetScope: a known zoneId skips the zone lookup (one call only)', async () => {
|
|
110
|
+
const calls = [];
|
|
111
|
+
const fetchImpl = async (url) => {
|
|
112
|
+
calls.push(url);
|
|
113
|
+
return { status: 200, json: async () => ({ success: true, result: { id: 'rs1', rules: [] } }) };
|
|
114
|
+
};
|
|
115
|
+
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: 'tok_1', zoneId: 'zoneGiven', fetchImpl });
|
|
116
|
+
assert.equal(r.readable, true);
|
|
117
|
+
assert.equal(calls.length, 1);
|
|
118
|
+
assert.match(calls[0], /\/zones\/zoneGiven\/rulesets\//);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('checkRulesetScope: no token → NOT readable, and ZERO network calls', async () => {
|
|
122
|
+
let called = false;
|
|
123
|
+
const fetchImpl = async () => { called = true; return { status: 200, json: async () => ({}) }; };
|
|
124
|
+
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: '', fetchImpl });
|
|
125
|
+
assert.equal(r.readable, false);
|
|
126
|
+
assert.match(r.reason, /no CLOUDFLARE_API_TOKEN/);
|
|
127
|
+
assert.equal(called, false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('checkRulesetScope: a thrown fetch is caught → NOT readable, never throws', async () => {
|
|
131
|
+
const fetchImpl = async () => { throw new Error('ENETUNREACH'); };
|
|
132
|
+
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: 'tok', fetchImpl });
|
|
133
|
+
assert.equal(r.readable, false);
|
|
134
|
+
assert.match(r.reason, /failed/i);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('checkRulesetScope: the zone lookup rejecting the token → NOT readable', async () => {
|
|
138
|
+
const fetchImpl = async () => ({ status: 403, json: async () => ({ success: false, errors: [{ message: 'Invalid API Token' }] }) });
|
|
139
|
+
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: 'tok', fetchImpl });
|
|
140
|
+
assert.equal(r.readable, false);
|
|
141
|
+
assert.match(r.guidance, /CLOUDFLARE_API_TOKEN/);
|
|
142
|
+
});
|
package/tests/cf_tunnel.mjs
CHANGED
|
@@ -373,6 +373,10 @@ await ta('ensureCookieStripRule failure does NOT block ensureNamedTunnel (best-e
|
|
|
373
373
|
const r = await cf.ensureNamedTunnel({ login: 'carol', zone: 'amazonprimea.com', terminalZone: 'amazonprimea.com' });
|
|
374
374
|
assert.equal(r.token, 'tokZ', 'provisioning completes despite the cookie-strip rule failing');
|
|
375
375
|
assert.equal(r.hostname, 'term-carol.amazonprimea.com');
|
|
376
|
+
// task 1002511: the failure is reported STRUCTURALLY, not only logged, so box.js
|
|
377
|
+
// can surface it in the provision summary instead of burying it mid-run.
|
|
378
|
+
assert.equal(r.edgeRules.cookieStrip.applied, false, 'a failed cookie-strip PUT must never report applied');
|
|
379
|
+
assert.ok(r.edgeRules.cookieStrip.error, 'the failure reason is captured');
|
|
376
380
|
});
|
|
377
381
|
|
|
378
382
|
console.log('edge cache bypass (#1065):');
|
|
@@ -446,6 +450,39 @@ await ta('ensureCacheBypassRule failure does NOT block ensureNamedTunnel (best-e
|
|
|
446
450
|
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
447
451
|
const r = await cf.ensureNamedTunnel({ login: 'carol', zone: 'amazonprimea.com', terminalZone: 'amazonprimea.com' });
|
|
448
452
|
assert.equal(r.token, 'tokZ', 'provisioning completes despite the cache rule failing');
|
|
453
|
+
assert.equal(r.edgeRules.cacheBypass.applied, false);
|
|
454
|
+
assert.ok(r.edgeRules.cacheBypass.error, 'the cache-rule failure reason is captured');
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
await ta('ensureNamedTunnel reports edgeRules.applied on the success path (both legs land)', async () => {
|
|
458
|
+
const RS = '/zones/zoneZ/rulesets/phases/http_request_late_transform/entrypoint';
|
|
459
|
+
const CS = '/zones/zoneZ/rulesets/phases/http_request_cache_settings/entrypoint';
|
|
460
|
+
const { fetchImpl } = makeFetch({
|
|
461
|
+
'GET /accounts/acct123/cfd_tunnel': () => [],
|
|
462
|
+
'POST /accounts/acct123/cfd_tunnel': ({ body }) => ({ id: 'tunZ', name: body.name }),
|
|
463
|
+
'PUT /accounts/acct123/cfd_tunnel/tunZ/configurations': ({ body }) => ({ config: body.config }),
|
|
464
|
+
'GET /accounts/acct123/cfd_tunnel/tunZ/token': () => 'tokZ',
|
|
465
|
+
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
466
|
+
'GET /zones/zoneZ/dns_records': () => [],
|
|
467
|
+
'POST /zones/zoneZ/dns_records': ({ body }) => ({ id: `rec-${body.name}`, ...body }),
|
|
468
|
+
[`GET ${RS}`]: () => ({ id: 'rs1', rules: [] }),
|
|
469
|
+
[`PUT ${RS}`]: ({ body }) => ({ id: 'rs1', rules: body.rules }),
|
|
470
|
+
[`GET ${CS}`]: () => ({ id: 'rs2', rules: [] }),
|
|
471
|
+
[`PUT ${CS}`]: ({ body }) => ({ id: 'rs2', rules: body.rules }),
|
|
472
|
+
});
|
|
473
|
+
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
474
|
+
const r = await cf.ensureNamedTunnel({ login: 'carol', zone: 'amazonprimea.com', terminalZone: 'amazonprimea.com' });
|
|
475
|
+
assert.equal(r.edgeRules.cookieStrip.applied, true);
|
|
476
|
+
assert.equal(r.edgeRules.cookieStrip.error, null);
|
|
477
|
+
assert.equal(r.edgeRules.cacheBypass.applied, true);
|
|
478
|
+
assert.equal(r.token, 'tokZ', 'the bake values still come back');
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
await ta('ensureNamedTunnel in DRY-RUN reports applied:false (a plan never applied anything)', async () => {
|
|
482
|
+
const cf = new CfTunnelApi({ accountId: ACCT, fetchImpl: () => { throw new Error('should not fetch'); } });
|
|
483
|
+
const r = await cf.ensureNamedTunnel({ login: 'alice', zone: 'amazonprimea.com', terminalZone: 'amazonprimea.com' });
|
|
484
|
+
assert.equal(r.edgeRules.cookieStrip.applied, false, 'dry-run must not read as "the security rule is in place"');
|
|
485
|
+
assert.equal(r.edgeRules.cacheBypass.applied, false);
|
|
449
486
|
});
|
|
450
487
|
|
|
451
488
|
console.log('name helpers:');
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// tests/claim_gate_rebase.mjs — ADR 0120 part 3 (real DB).
|
|
2
|
+
//
|
|
3
|
+
// The needs_rebase claim gate: a builder who owes a rebase on an un-landed
|
|
4
|
+
// confirmed task is refused NEW claims (409 REBASE_REQUIRED, naming the
|
|
5
|
+
// offending task) until they land or release it. claimTask runs a real
|
|
6
|
+
// BEGIN/FOR UPDATE transaction with many queries, so this is a real-DB
|
|
7
|
+
// integration test (self-skips without a reachable Postgres; also in the
|
|
8
|
+
// INTEGRATION set so the default DB-free lane never runs it).
|
|
9
|
+
//
|
|
10
|
+
// Run: DATABASE_URL=... node tests/claim_gate_rebase.mjs
|
|
11
|
+
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
import { strict as assert } from 'node:assert';
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const { Pool } = require('pg');
|
|
16
|
+
|
|
17
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL || undefined });
|
|
18
|
+
try {
|
|
19
|
+
await pool.query('SELECT 1');
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.log(`claim_gate_rebase.mjs SKIPPED — no reachable Postgres (${e.code || e.message}).`);
|
|
22
|
+
await pool.end().catch(() => {});
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const seams = require('../src/module-seams.js');
|
|
27
|
+
if (!seams.hasProvider('reward')) seams.registerProvider('reward', require('../modules/economy/reward.js'));
|
|
28
|
+
const db = require('../modules/lifecycle/db.js');
|
|
29
|
+
|
|
30
|
+
const VER = 'ZZZ-CG', WORKER = 90000201, STALLED = 90000201, FRESH = 90000202;
|
|
31
|
+
const q = (sql, params) => pool.query(sql, params).then((r) => r.rows);
|
|
32
|
+
|
|
33
|
+
async function cleanup() {
|
|
34
|
+
await q(`DELETE FROM builder_achievements WHERE builder_id=$1`, [WORKER]).catch(() => {});
|
|
35
|
+
await q(`DELETE FROM credit_log WHERE builder_id=$1 OR task_id = ANY($2::bigint[])`, [WORKER, [STALLED, FRESH]]).catch(() => {});
|
|
36
|
+
await q(`DELETE FROM claims WHERE task_id = ANY($1::bigint[])`, [[STALLED, FRESH]]).catch(() => {});
|
|
37
|
+
await q(`DELETE FROM tasks WHERE id = ANY($1::bigint[])`, [[STALLED, FRESH]]).catch(() => {});
|
|
38
|
+
await q(`DELETE FROM builders WHERE id=$1`, [WORKER]).catch(() => {});
|
|
39
|
+
await q(`DELETE FROM versions WHERE id=$1`, [VER]).catch(() => {});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let pass = 0;
|
|
43
|
+
const ok = (cond, msg) => { assert.ok(cond, msg); console.log(` ok ${msg}`); pass++; };
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await cleanup();
|
|
47
|
+
await q(`INSERT INTO versions (id,name,status,track) VALUES ($1,'cg','planning','internal') ON CONFLICT (id) DO NOTHING`, [VER]);
|
|
48
|
+
// Archon so no rank/newcomer gate interferes; the gate under test is REBASE_REQUIRED.
|
|
49
|
+
await q(`INSERT INTO builders (id,github_id,github_login,display_name,rank) VALUES ($1,'cg-w','cg-worker','CG Worker','archon')`, [WORKER]);
|
|
50
|
+
// STALLED: a confirmed task owned by WORKER, flagged needs_rebase.
|
|
51
|
+
await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,shipped_by) VALUES ($1,$2,'stalled','confirmed',10,'feature',$3)`, [STALLED, VER, WORKER]);
|
|
52
|
+
await q(`INSERT INTO claims (task_id,builder_id,claimed_at) VALUES ($1,$2, now())`, [STALLED, WORKER]);
|
|
53
|
+
await db.flagTaskNeedsRebase(String(STALLED), 'strand:no_branch_no_tip');
|
|
54
|
+
// FRESH: a ready, claimable task the worker will try to pick up.
|
|
55
|
+
await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind) VALUES ($1,$2,'fresh','ready',10,'feature')`, [FRESH, VER]);
|
|
56
|
+
|
|
57
|
+
// 1) With STALLED flagged, claiming FRESH is refused with REBASE_REQUIRED.
|
|
58
|
+
let threw = null;
|
|
59
|
+
try {
|
|
60
|
+
await db.claimTask({ taskId: String(FRESH), builderId: String(WORKER), worktreeName: 'cg-fresh' });
|
|
61
|
+
} catch (e) { threw = e; }
|
|
62
|
+
ok(threw && threw.code === 'REBASE_REQUIRED', `claim refused with REBASE_REQUIRED (got ${threw && (threw.code || threw.message)})`);
|
|
63
|
+
ok(Array.isArray(threw.rebaseTasks) && threw.rebaseTasks.some((t) => Number(t.id) === STALLED),
|
|
64
|
+
'the 409 names the offending stalled task');
|
|
65
|
+
// FRESH must be untouched (still ready, unclaimed).
|
|
66
|
+
const freshStatus = (await q(`SELECT status FROM tasks WHERE id=$1`, [FRESH]))[0].status;
|
|
67
|
+
ok(freshStatus === 'ready', 'the refused task stays ready (no partial claim)');
|
|
68
|
+
|
|
69
|
+
// ADR 0120 part 5: the SAME owed-rebase read, reached through the `lifecycle`
|
|
70
|
+
// kernel PORT — the exact wire the /me route resolves so the CLI (/builder-start)
|
|
71
|
+
// + the hall can surface the gate. Proves the port delegates to db and returns
|
|
72
|
+
// the fields those surfaces render.
|
|
73
|
+
const lifecycle = require('../modules/lifecycle/lifecycle.js');
|
|
74
|
+
const owedViaPort = await lifecycle.needsRebaseTasksForBuilder(String(WORKER));
|
|
75
|
+
ok(owedViaPort.some((t) => Number(t.id) === STALLED),
|
|
76
|
+
'the lifecycle port surfaces the owed rebase (the /me → CLI/hall read path)');
|
|
77
|
+
ok(owedViaPort.every((t) => 'title' in t && 'needs_rebase_reason' in t && 'needs_rebase_at' in t),
|
|
78
|
+
'each owed row carries the fields the CLI + hall render');
|
|
79
|
+
|
|
80
|
+
// 2) Landing the stalled task clears the flag → the gate opens.
|
|
81
|
+
await db.shipTask({ taskId: String(STALLED), builderId: String(WORKER) });
|
|
82
|
+
const stalledFlag = (await q(`SELECT needs_rebase FROM tasks WHERE id=$1`, [STALLED]))[0].needs_rebase;
|
|
83
|
+
ok(stalledFlag === false, 'shipping the stalled task cleared needs_rebase');
|
|
84
|
+
|
|
85
|
+
// 3) Now the claim succeeds.
|
|
86
|
+
const claim = await db.claimTask({ taskId: String(FRESH), builderId: String(WORKER), worktreeName: 'cg-fresh' });
|
|
87
|
+
ok(claim && claim.id && Number(claim.task_id) === FRESH, 'claim succeeds once the rebase chore is cleared');
|
|
88
|
+
const activeClaim = (await q(`SELECT builder_id FROM claims WHERE task_id=$1 AND released_at IS NULL`, [FRESH]))[0];
|
|
89
|
+
ok(activeClaim && Number(activeClaim.builder_id) === WORKER, 'the worker now holds an active claim on the task');
|
|
90
|
+
|
|
91
|
+
console.log(`\nclaim_gate_rebase (ADR 0120 part 3): ${pass} passed, 0 failed`);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.error('claim_gate_rebase.mjs FAILED:', e.stack || e.message);
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
} finally {
|
|
96
|
+
await cleanup();
|
|
97
|
+
await pool.end();
|
|
98
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// tests/clawback_confirmed_backlog.mjs — ADR 0120 part 4 (real DB).
|
|
2
|
+
//
|
|
3
|
+
// Runs the ACTUAL migration SQL (migrations/core_194_confirmed_backlog_clawback.sql)
|
|
4
|
+
// against seeded fixtures and asserts the one-time clawback + re-gate:
|
|
5
|
+
// • each STILL-CONFIRMED task's task.confirmed credit is reversed (a negative
|
|
6
|
+
// task.confirmed_reversal row) and builders.total_credits nets back down,
|
|
7
|
+
// • those tasks are flagged needs_rebase='clawback:confirmed_backlog',
|
|
8
|
+
// • a SHIPPED task (legitimately landed) is left completely untouched,
|
|
9
|
+
// • the migration is idempotent (a second run books nothing new).
|
|
10
|
+
//
|
|
11
|
+
// Real-DB integration test: self-skips without a reachable Postgres; also in the
|
|
12
|
+
// INTEGRATION set so the default DB-free lane never runs it.
|
|
13
|
+
//
|
|
14
|
+
// Run: DATABASE_URL=... node tests/clawback_confirmed_backlog.mjs
|
|
15
|
+
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { strict as assert } from 'node:assert';
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const { Pool } = require('pg');
|
|
23
|
+
|
|
24
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const MIGRATION_SQL = readFileSync(join(here, '..', 'migrations', 'core_194_confirmed_backlog_clawback.sql'), 'utf8');
|
|
26
|
+
|
|
27
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL || undefined });
|
|
28
|
+
try {
|
|
29
|
+
await pool.query('SELECT 1');
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.log(`clawback_confirmed_backlog.mjs SKIPPED — no reachable Postgres (${e.code || e.message}).`);
|
|
32
|
+
await pool.end().catch(() => {});
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const VER = 'ZZZ-CB', A = 90000301, B = 90000302, T_A = 90000301, T_B = 90000302, T_S = 90000303;
|
|
37
|
+
const q = (sql, params) => pool.query(sql, params).then((r) => r.rows);
|
|
38
|
+
const total = async (id) => Number((await q(`SELECT total_credits FROM builders WHERE id=$1`, [id]))[0]?.total_credits ?? 0);
|
|
39
|
+
const reversals = async (taskId) => q(`SELECT delta FROM credit_log WHERE task_id=$1 AND reason='task.confirmed_reversal'`, [taskId]);
|
|
40
|
+
const flag = async (taskId) => (await q(`SELECT needs_rebase, needs_rebase_reason FROM tasks WHERE id=$1`, [taskId]))[0];
|
|
41
|
+
|
|
42
|
+
async function cleanup() {
|
|
43
|
+
await q(`DELETE FROM builder_achievements WHERE builder_id = ANY($1::bigint[])`, [[A, B]]).catch(() => {});
|
|
44
|
+
await q(`DELETE FROM credit_log WHERE builder_id = ANY($1::bigint[]) OR task_id = ANY($2::bigint[])`, [[A, B], [T_A, T_B, T_S]]).catch(() => {});
|
|
45
|
+
await q(`DELETE FROM tasks WHERE id = ANY($1::bigint[])`, [[T_A, T_B, T_S]]).catch(() => {});
|
|
46
|
+
await q(`DELETE FROM builders WHERE id = ANY($1::bigint[])`, [[A, B]]).catch(() => {});
|
|
47
|
+
await q(`DELETE FROM versions WHERE id=$1`, [VER]).catch(() => {});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let pass = 0;
|
|
51
|
+
const ok = (cond, msg) => { assert.ok(cond, msg); console.log(` ok ${msg}`); pass++; };
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await cleanup();
|
|
55
|
+
await q(`INSERT INTO versions (id,name,status,track) VALUES ($1,'cb','planning','internal') ON CONFLICT (id) DO NOTHING`, [VER]);
|
|
56
|
+
await q(`INSERT INTO builders (id,github_id,github_login,display_name,rank) VALUES
|
|
57
|
+
($1,'cb-a','cb-a','CB A','thetes'), ($2,'cb-b','cb-b','CB B','thetes')`, [A, B]);
|
|
58
|
+
// Two still-confirmed tasks (unlanded) + one legitimately shipped task.
|
|
59
|
+
await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,shipped_by) VALUES
|
|
60
|
+
($1,$4,'confirmed A','confirmed',20,'feature',$2),
|
|
61
|
+
($5,$4,'confirmed B','confirmed',15,'feature',$3),
|
|
62
|
+
($6,$4,'shipped legit','shipped',30,'feature',$2)`, [T_A, A, B, VER, T_B, T_S]);
|
|
63
|
+
// Seed the credits the OLD confirm-time rule would have paid (credit_log INSERT
|
|
64
|
+
// fires the total_credits trigger, exactly like a real award).
|
|
65
|
+
await q(`INSERT INTO credit_log (builder_id,task_id,delta,reason,description) VALUES
|
|
66
|
+
($1,$2,20,'task.confirmed','seed A'),
|
|
67
|
+
($3,$4,15,'task.confirmed','seed B'),
|
|
68
|
+
($1,$5,30,'task.shipped','seed shipped-legit')`, [A, T_A, B, T_B, T_S]);
|
|
69
|
+
|
|
70
|
+
ok(await total(A) === 50, 'seed: builder A total_credits = 50 (20 confirmed + 30 shipped)');
|
|
71
|
+
ok(await total(B) === 15, 'seed: builder B total_credits = 15 (confirmed)');
|
|
72
|
+
|
|
73
|
+
// ---- run the ACTUAL migration ----
|
|
74
|
+
await pool.query(MIGRATION_SQL);
|
|
75
|
+
|
|
76
|
+
// 1. Reversals booked for the confirmed tasks, NOT the shipped one.
|
|
77
|
+
const rA = await reversals(T_A), rB = await reversals(T_B), rS = await reversals(T_S);
|
|
78
|
+
ok(rA.length === 1 && Number(rA[0].delta) === -20, 'confirmed task A: one -20 reversal');
|
|
79
|
+
ok(rB.length === 1 && Number(rB[0].delta) === -15, 'confirmed task B: one -15 reversal');
|
|
80
|
+
ok(rS.length === 0, 'the SHIPPED task is NOT clawed back (legitimately landed)');
|
|
81
|
+
|
|
82
|
+
// 2. total_credits netted back down by the reversed amounts.
|
|
83
|
+
ok(await total(A) === 30, 'builder A total_credits 50 → 30 (confirmed credit reversed, shipped kept)');
|
|
84
|
+
ok(await total(B) === 0, 'builder B total_credits 15 → 0');
|
|
85
|
+
|
|
86
|
+
// 3. Confirmed tasks re-gated; shipped task untouched.
|
|
87
|
+
ok((await flag(T_A)).needs_rebase === true && (await flag(T_A)).needs_rebase_reason === 'clawback:confirmed_backlog', 'task A flagged needs_rebase (clawback)');
|
|
88
|
+
ok((await flag(T_B)).needs_rebase === true, 'task B flagged needs_rebase');
|
|
89
|
+
ok((await flag(T_S)).needs_rebase === false, 'the shipped task is NOT flagged');
|
|
90
|
+
|
|
91
|
+
// 4. Idempotent — a second run books nothing new.
|
|
92
|
+
await pool.query(MIGRATION_SQL);
|
|
93
|
+
ok((await reversals(T_A)).length === 1, 'idempotent: still exactly one reversal for task A after a second run');
|
|
94
|
+
ok(await total(A) === 30, 'idempotent: builder A total_credits unchanged on re-run');
|
|
95
|
+
|
|
96
|
+
console.log(`\nclawback_confirmed_backlog (ADR 0120 part 4): ${pass} passed, 0 failed`);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.error('clawback_confirmed_backlog.mjs FAILED:', e.stack || e.message);
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
} finally {
|
|
101
|
+
await cleanup();
|
|
102
|
+
await pool.end();
|
|
103
|
+
}
|
|
@@ -46,6 +46,26 @@ test('fmtUsd: two-decimal dollar string', () => {
|
|
|
46
46
|
assert.equal(cli.fmtUsd(1.239), '$1.24'); // rounds via toFixed
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
// hallBase(base) — the builders'-hall root for a given API base (task 1002233).
|
|
50
|
+
// Pass an explicit base so the test never depends on the module-load env.
|
|
51
|
+
test('hallBase: derives the hall root from the base URL origin (path/query dropped)', () => {
|
|
52
|
+
assert.equal(cli.hallBase('https://cloudbongos.com'), 'https://cloudbongos.com/builders');
|
|
53
|
+
assert.equal(cli.hallBase('https://cloudbongos.com/'), 'https://cloudbongos.com/builders');
|
|
54
|
+
assert.equal(cli.hallBase('https://inst.example/api/gds'), 'https://inst.example/builders');
|
|
55
|
+
assert.equal(cli.hallBase('http://localhost:3000'), 'http://localhost:3000/builders');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('hallBase: reflects whatever base it is given — no hardcoded founder host', () => {
|
|
59
|
+
const out = cli.hallBase('https://staging.example.org');
|
|
60
|
+
assert.equal(out, 'https://staging.example.org/builders');
|
|
61
|
+
assert.ok(!/amazonprimea/.test(out), 'must not reintroduce a hardcoded amazonprimea host');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('hallBase: a non-URL base falls back to `${base}/builders` (never throws)', () => {
|
|
65
|
+
assert.equal(cli.hallBase('not a url'), 'not a url/builders');
|
|
66
|
+
assert.equal(cli.hallBase(''), '/builders');
|
|
67
|
+
});
|
|
68
|
+
|
|
49
69
|
test('fmtUsd: null renders the em-dash placeholder (edge)', () => {
|
|
50
70
|
assert.equal(cli.fmtUsd(null), '—');
|
|
51
71
|
});
|