@bongos/core 1.18.3 → 1.18.19
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 +1576 -286
- 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/migrate.sh +99 -14
- 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 +124 -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/migrate_root_resolution.mjs +124 -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/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
package/scripts/gds/cf-tunnel.js
CHANGED
|
@@ -89,6 +89,128 @@ function ipv4Dispatcher() {
|
|
|
89
89
|
|
|
90
90
|
const CfApiError = makeApiError('CfApiError', 'CF_API_ERROR');
|
|
91
91
|
|
|
92
|
+
// The Cloudflare token permission each ruleset phase is gated behind, named exactly
|
|
93
|
+
// as it reads in the dashboard's token editor so the guidance is click-followable.
|
|
94
|
+
const RULESET_PHASE_PERMISSION = {
|
|
95
|
+
[COOKIE_STRIP_PHASE]: 'Transform Rules',
|
|
96
|
+
[CACHE_BYPASS_PHASE]: 'Cache Rules',
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// The one fix for every not-readable verdict: widen the token. Both edit scopes are
|
|
100
|
+
// named because provisioning applies BOTH ruleset legs (cookie-strip + cache-bypass)
|
|
101
|
+
// and an operator in the token editor should add them in one pass.
|
|
102
|
+
function rulesetScopeGuidance(zone) {
|
|
103
|
+
return `Widen CLOUDFLARE_API_TOKEN on the control plane: Cloudflare dashboard → My Profile → API Tokens → `
|
|
104
|
+
+ `edit the token → Permissions → add "Zone → Transform Rules → Edit" (the ADR 0053 cookie-strip) and `
|
|
105
|
+
+ `"Zone → Cache Rules → Edit" (the sandbox cache bypass) → Zone Resources → Include → Specific zone → ${zone} `
|
|
106
|
+
+ `→ Continue → Save. Only the account owner can do this. Until then, apply the rule by hand in the dashboard `
|
|
107
|
+
+ `(see docs/recipes/builder-box-lifecycle.md).`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// PURE. Given the outcome of the READ-ONLY phase-entrypoint probe (HTTP status +
|
|
111
|
+
// parsed Cloudflare JSON), say what we actually know about the token's scope.
|
|
112
|
+
//
|
|
113
|
+
// HONESTY — THIS IS A NEGATIVE DETECTOR, NOT A POSITIVE ONE. The probe is a GET, so
|
|
114
|
+
// a 200 proves the token can READ the phase entrypoint; it does NOT prove the token
|
|
115
|
+
// can EDIT it. Cloudflare exposes no read-only way to prove edit scope — only the
|
|
116
|
+
// real PUT does. So:
|
|
117
|
+
// - 401/403 → DEFINITIVELY no scope. This is the actionable case the preflight
|
|
118
|
+
// exists to catch, and the only verdict we assert about the token.
|
|
119
|
+
// - 200/404 → readable. Edit scope stays UNPROVEN until the rule PUT runs.
|
|
120
|
+
// - anything else → inconclusive, reported as not-readable so the operator is
|
|
121
|
+
// warned rather than falsely reassured.
|
|
122
|
+
// The field is named `readable` and never `editable` for exactly this reason, and no
|
|
123
|
+
// verdict here may render as "scope confirmed".
|
|
124
|
+
function classifyRulesetScope({ status, body, phase, zone }) {
|
|
125
|
+
const z = String(zone || '');
|
|
126
|
+
const perm = RULESET_PHASE_PERMISSION[phase] || 'Transform Rules';
|
|
127
|
+
if (status === 401 || status === 403) {
|
|
128
|
+
return {
|
|
129
|
+
readable: false,
|
|
130
|
+
reason: `Cloudflare refused to read the "${phase}" ruleset on ${z} (HTTP ${status}) — the token definitively lacks zone "${perm}" scope there`,
|
|
131
|
+
guidance: rulesetScopeGuidance(z),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// A zone with no rules in this phase yet has no entrypoint ruleset. That is a
|
|
135
|
+
// legitimate empty state (ensureCookieStripRule's PUT creates it), not a denial.
|
|
136
|
+
if (status === 404) {
|
|
137
|
+
return {
|
|
138
|
+
readable: true,
|
|
139
|
+
reason: `the "${phase}" phase has no entrypoint ruleset on ${z} yet (HTTP 404) — readable; edit scope is NOT proven until the rule is applied`,
|
|
140
|
+
guidance: '',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (status !== 200 || !body || body.success !== true) {
|
|
144
|
+
const errs = body && Array.isArray(body.errors) ? body.errors.map((e) => e && e.message).filter(Boolean).join('; ') : '';
|
|
145
|
+
return {
|
|
146
|
+
readable: false,
|
|
147
|
+
reason: `the "${phase}" ruleset probe on ${z} failed and is inconclusive (HTTP ${status}${errs ? `: ${errs}` : ''})`,
|
|
148
|
+
guidance: `Re-run once network/Cloudflare recovers. If it keeps failing, ${rulesetScopeGuidance(z)}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
readable: true,
|
|
153
|
+
reason: `the token can READ the "${phase}" ruleset on ${z} — edit scope is NOT proven until the rule is applied`,
|
|
154
|
+
guidance: '',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Read-only preflight for a zone ruleset phase. Does its OWN raw fetch rather than
|
|
159
|
+
// routing through CfTunnelApi._req, because _req THROWS on failure and is dry-run
|
|
160
|
+
// gated — a preflight must return a verdict (never throw) and must run on the
|
|
161
|
+
// dry-run path too. Mirrors provision.js's checkDnsTokenScope.
|
|
162
|
+
//
|
|
163
|
+
// Deliberately does NOT use `GET /user/tokens/verify`: an IP-locked token answers
|
|
164
|
+
// "Invalid API Token" there even when it is valid (docs/recipes/builder-box-
|
|
165
|
+
// lifecycle.md). Probe the real operation instead.
|
|
166
|
+
//
|
|
167
|
+
// Returns { zone, phase, readable, reason, guidance[, zoneId] }. Never throws.
|
|
168
|
+
async function checkRulesetScope({
|
|
169
|
+
zone, phase = COOKIE_STRIP_PHASE, token = process.env.CLOUDFLARE_API_TOKEN,
|
|
170
|
+
fetchImpl, zoneId = null, dispatcher,
|
|
171
|
+
} = {}) {
|
|
172
|
+
const base = { zone, phase };
|
|
173
|
+
if (!zone) return { ...base, readable: false, reason: 'no Cloudflare zone given', guidance: 'set CLOUDFLARE_ZONE on the control plane' };
|
|
174
|
+
if (!token) {
|
|
175
|
+
return { ...base, readable: false, reason: `no CLOUDFLARE_API_TOKEN configured`,
|
|
176
|
+
guidance: `Set CLOUDFLARE_API_TOKEN in the control-plane box.env. ${rulesetScopeGuidance(zone)}` };
|
|
177
|
+
}
|
|
178
|
+
const doFetch = fetchImpl || (typeof fetch !== 'undefined' ? fetch : null);
|
|
179
|
+
if (!doFetch) return { ...base, readable: false, reason: 'no fetch implementation available', guidance: 'run on a Node with global fetch, or inject fetchImpl' };
|
|
180
|
+
const init = { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } };
|
|
181
|
+
if (dispatcher !== undefined) init.dispatcher = dispatcher;
|
|
182
|
+
else { const d = ipv4Dispatcher(); if (d) init.dispatcher = d; }
|
|
183
|
+
|
|
184
|
+
let id = zoneId;
|
|
185
|
+
if (!id) {
|
|
186
|
+
let res;
|
|
187
|
+
try {
|
|
188
|
+
res = await doFetch(`${CF_API_BASE}/zones?name=${encodeURIComponent(zone)}`, init);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
return { ...base, readable: false, reason: `Cloudflare zone lookup failed: ${e.message}`, guidance: 'check control-plane network + token, then retry' };
|
|
191
|
+
}
|
|
192
|
+
const body = await res.json().catch(() => null);
|
|
193
|
+
const match = (body && Array.isArray(body.result) ? body.result : []).find((r) => r && r.id);
|
|
194
|
+
if (!match) {
|
|
195
|
+
const errs = body && Array.isArray(body.errors) ? body.errors.map((e) => e && e.message).filter(Boolean).join('; ') : '';
|
|
196
|
+
return { ...base, readable: false,
|
|
197
|
+
reason: `could not resolve zone "${zone}" (HTTP ${res.status}${errs ? `: ${errs}` : ''})`,
|
|
198
|
+
guidance: `Check that CLOUDFLARE_API_TOKEN is valid and its Zone Resources include ${zone}. ${rulesetScopeGuidance(zone)}` };
|
|
199
|
+
}
|
|
200
|
+
id = match.id;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let status = 0; let bodyJson = null;
|
|
204
|
+
try {
|
|
205
|
+
const res = await doFetch(`${CF_API_BASE}/zones/${id}/rulesets/phases/${phase}/entrypoint`, init);
|
|
206
|
+
status = res.status;
|
|
207
|
+
bodyJson = await res.json().catch(() => null);
|
|
208
|
+
} catch (e) {
|
|
209
|
+
return { ...base, zoneId: id, readable: false, reason: `Cloudflare ruleset probe failed: ${e.message}`, guidance: 'check control-plane network + token, then retry' };
|
|
210
|
+
}
|
|
211
|
+
return { ...base, zoneId: id, ...classifyRulesetScope({ status, body: bodyJson, phase, zone }) };
|
|
212
|
+
}
|
|
213
|
+
|
|
92
214
|
// Deterministic per-builder names so teardown can find what provision created
|
|
93
215
|
// WITHOUT persisting an id in the DB (no migration): the tunnel name and the DNS
|
|
94
216
|
// hostname are both pure functions of the login.
|
|
@@ -359,25 +481,38 @@ class CfTunnelApi extends JsonHttpApi {
|
|
|
359
481
|
const zoneId = await this.getZoneId(zone);
|
|
360
482
|
await this.upsertDnsRoute({ zoneId, name: hostname, tunnelId });
|
|
361
483
|
await this.upsertDnsRoute({ zoneId, name: previewHost, tunnelId });
|
|
484
|
+
// Both edge-rule legs are BEST-EFFORT and report their outcome STRUCTURALLY so
|
|
485
|
+
// the caller can surface a failure in its summary instead of leaving it buried
|
|
486
|
+
// in a mid-run log line (task 1002511). `applied` is false in dry-run because
|
|
487
|
+
// nothing WAS applied — these are security/caching controls, and a plan must
|
|
488
|
+
// never read as though the rule is in place.
|
|
489
|
+
const edgeRules = {
|
|
490
|
+
cookieStrip: { applied: false, error: null },
|
|
491
|
+
cacheBypass: { applied: false, error: null },
|
|
492
|
+
};
|
|
362
493
|
// SECURITY (#919 / SR#11): strip the apex cookie at the edge for the box
|
|
363
|
-
// subdomains.
|
|
364
|
-
//
|
|
494
|
+
// subdomains. A CF token without Transform Rules scope must not block
|
|
495
|
+
// provisioning (the box still works; the rule is defense-in-depth). Warn
|
|
365
496
|
// loudly so the operator adds the scope or applies the rule once by hand.
|
|
366
497
|
try {
|
|
367
498
|
await this.ensureCookieStripRule({ zone });
|
|
499
|
+
edgeRules.cookieStrip.applied = !this.dryRun;
|
|
368
500
|
} catch (e) {
|
|
501
|
+
edgeRules.cookieStrip.error = e.message;
|
|
369
502
|
this.log(`⚠ #919 cookie-strip Transform Rule NOT applied (${e.message}) — add zone "Transform Rules" scope to CLOUDFLARE_API_TOKEN or apply it once in the CF dashboard (ADR 0053)`);
|
|
370
503
|
}
|
|
371
504
|
// #1065: stop Cloudflare's 4h Browser Cache TTL from serving stale sandbox
|
|
372
|
-
// game code.
|
|
373
|
-
//
|
|
374
|
-
//
|
|
505
|
+
// game code. A CF token without Cache-Rules scope must not block provisioning.
|
|
506
|
+
// Warn loudly so the operator adds the scope or applies the rule once by hand
|
|
507
|
+
// (operator blocker, like the cookie-strip rule).
|
|
375
508
|
try {
|
|
376
509
|
await this.ensureCacheBypassRule({ zone });
|
|
510
|
+
edgeRules.cacheBypass.applied = !this.dryRun;
|
|
377
511
|
} catch (e) {
|
|
512
|
+
edgeRules.cacheBypass.error = e.message;
|
|
378
513
|
this.log(`⚠ #1065 sandbox cache-bypass Cache Rule NOT applied (${e.message}) — add zone "Cache Rules" (Account Rulesets) scope to CLOUDFLARE_API_TOKEN or add it once in the CF dashboard`);
|
|
379
514
|
}
|
|
380
|
-
return { tunnelId, token, hostname, previewHostname: previewHost, created };
|
|
515
|
+
return { tunnelId, token, hostname, previewHostname: previewHost, created, edgeRules };
|
|
381
516
|
}
|
|
382
517
|
|
|
383
518
|
// Tear down the per-builder tunnel + its DNS route. Best-effort: each leg is
|
|
@@ -416,4 +551,8 @@ module.exports = {
|
|
|
416
551
|
tunnelName, terminalHostname, previewHostname,
|
|
417
552
|
// #1065: the sandbox edge cache-bypass rule constants, exported for the test.
|
|
418
553
|
CACHE_BYPASS_PHASE, CACHE_BYPASS_RULE_DESC, CACHE_BYPASS_EXPRESSION,
|
|
554
|
+
// #919 / SR#11: the cookie-strip rule constants + the read-only scope preflight
|
|
555
|
+
// box.js runs before any droplet spend (task 1002511).
|
|
556
|
+
COOKIE_STRIP_PHASE, COOKIE_STRIP_RULE_DESC, COOKIE_STRIP_EXPRESSION,
|
|
557
|
+
classifyRulesetScope, checkRulesetScope,
|
|
419
558
|
};
|
package/scripts/gds/claim.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
const { execSync } = require('node:child_process');
|
|
11
11
|
const fs = require('node:fs');
|
|
12
12
|
const path = require('node:path');
|
|
13
|
-
const { cliClient, requireSession, fmtTouches, printOnboardingNudge } = require('./cli-lib');
|
|
13
|
+
const { cliClient, requireSession, fmtTouches, printOnboardingNudge, rewardPolicy, costPlusOnlyNote } = require('./cli-lib');
|
|
14
14
|
const { nextNumbers, padAdr, padMig } = require('./next-number');
|
|
15
15
|
const { CORE_NS } = require('./migration-namespace');
|
|
16
16
|
const { contextPack, formatContextPack } = require('./context-pack');
|
|
@@ -85,6 +85,28 @@ function captureHeadSha() {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// Auto-detect this claim's worktree name when --worktree is omitted, so the
|
|
89
|
+
// server's one-active-claim guard (keyed on the WORKING TREE, not the shared auth
|
|
90
|
+
// session) can tell genuinely-parallel worktrees apart. Only names a LINKED git
|
|
91
|
+
// worktree — where git-dir != git-common-dir, i.e. a real `.claude/worktrees/<name>`
|
|
92
|
+
// checkout — using its top-level directory basename (the `<name>` the convention
|
|
93
|
+
// already uses). The MAIN checkout returns null (unchanged behaviour: one claim at
|
|
94
|
+
// a time there — parallelism is what worktrees are for). Best-effort: any git
|
|
95
|
+
// hiccup yields null, so this can never break a claim.
|
|
96
|
+
function detectWorktreeName() {
|
|
97
|
+
try {
|
|
98
|
+
const gitDir = execSync('git rev-parse --git-dir', { encoding: 'utf8' }).trim();
|
|
99
|
+
const commonDir = execSync('git rev-parse --git-common-dir', { encoding: 'utf8' }).trim();
|
|
100
|
+
// Same dir → the main checkout, not a linked worktree → leave unnamed.
|
|
101
|
+
if (path.resolve(gitDir) === path.resolve(commonDir)) return null;
|
|
102
|
+
const top = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
|
|
103
|
+
const name = path.basename(top);
|
|
104
|
+
return name || null;
|
|
105
|
+
} catch (_) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
88
110
|
// task 1278: nudge (don't block) when this session's build scripts are behind
|
|
89
111
|
// origin/main. The completion cards + skills are CLI-script features that ride
|
|
90
112
|
// the LOCAL checkout, not the deployed server — a worktree branched from a stale
|
|
@@ -233,7 +255,10 @@ async function main() {
|
|
|
233
255
|
process.exit(2);
|
|
234
256
|
}
|
|
235
257
|
const wIdx = args.indexOf('--worktree');
|
|
236
|
-
|
|
258
|
+
// Explicit --worktree always wins; otherwise auto-detect a linked worktree so
|
|
259
|
+
// parallel worktrees off one login each hold their own claim (the server guard
|
|
260
|
+
// keys on this). null in the main checkout → unchanged one-claim behaviour.
|
|
261
|
+
const worktree = (wIdx >= 0 ? args[wIdx + 1] : null) || detectWorktreeName();
|
|
237
262
|
// touches[] cleanup [5/8] (task 880): touches[] overlap with another active
|
|
238
263
|
// claim is ADVISORY for an interactive human claim (just a warning — git merge
|
|
239
264
|
// is the real collision detector). The autonomous --parallel runner passes
|
|
@@ -255,7 +280,13 @@ async function main() {
|
|
|
255
280
|
const { claim, task } = r.data;
|
|
256
281
|
console.log(`Claimed task #${task.id}: ${task.title}`);
|
|
257
282
|
console.log(`Claim id: ${claim.id}`);
|
|
258
|
-
|
|
283
|
+
// The reward figure is the task's ADVERTISED estimate. On a cost-plus-only
|
|
284
|
+
// instance it is never paid, so label it as an estimate and name the real
|
|
285
|
+
// payer right here — this line is where the expectation forms (task 1002469).
|
|
286
|
+
const rewardLabel = rewardPolicy(r.data.reward).perTaskCredit ? 'Credit reward' : 'Credit reward (est. only)';
|
|
287
|
+
console.log(`Version: ${task.version_id} · Priority: P${task.priority ?? '?'} · ${rewardLabel}: ${task.credits_reward}`);
|
|
288
|
+
const cpNote = costPlusOnlyNote(r.data.reward);
|
|
289
|
+
if (cpNote) console.log(` ${cpNote}`);
|
|
259
290
|
if (task.parallel_safe === true) console.log('Parallel-safe: yes (eligible for a parallel autonomous slot)');
|
|
260
291
|
// BV1.R53 (task 1422): module_key is the primary scope signal. touches[] is
|
|
261
292
|
// demoted to derived/advisory for module-keyed tasks; fall back to it for
|
package/scripts/gds/cli-lib.js
CHANGED
|
@@ -27,16 +27,105 @@ const SESSION_READ_PATHS = [
|
|
|
27
27
|
...ic.configReadPaths('gds-session.json'),
|
|
28
28
|
...ic.configReadPaths('pms-session.json'),
|
|
29
29
|
];
|
|
30
|
-
// The
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
// consumer targeted the founder's (game-only) box. Override with <PREFIX>_API_BASE
|
|
35
|
-
// for local dev / staging:
|
|
30
|
+
// The API base every CLI call targets, resolved ONCE through the single source of
|
|
31
|
+
// truth in instance-config: <PREFIX>_API_BASE (then legacy GDS_/OTB_/PMS_) → the
|
|
32
|
+
// persisted session's api_base → THIS instance's publicOrigin from the branding
|
|
33
|
+
// pack. Override for local dev / staging:
|
|
36
34
|
// CLOUDBONGOS_API_BASE=http://localhost:3000 node scripts/gds/start.js
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
const
|
|
35
|
+
// No CLI script hard-codes another instance's URL (task 1002201; pre-ADR-0139 the
|
|
36
|
+
// literal 'https://amazonprimea.com' was baked in at four sites here). The session
|
|
37
|
+
// leg was added at task 1002427 — this const previously stopped at the branding
|
|
38
|
+
// default, so module-level consumers (reauth's Settings URL, the hall links
|
|
39
|
+
// hallBase() prints) named the checkout's own instance instead of the one the
|
|
40
|
+
// builder is signed into.
|
|
41
|
+
const API_BASE = ic.resolveApiBase();
|
|
42
|
+
|
|
43
|
+
// User-Agent for the GDS CLI's HTTP calls — the branding-derived neutral platform UA
|
|
44
|
+
// from the single source, src/branding.js userAgent() (`<repo.name|envPrefix>-gds/1.0`,
|
|
45
|
+
// neutral fallback `cloud-bongos-gds/1.0`), matching the `cloud-bongos-` convention the
|
|
46
|
+
// hooks use (tests/hook_user_agents.mjs). Was the hardcoded 'amazonprimea-gds-cli/1.0'
|
|
47
|
+
// at both fetch sites — task 1002234. Resolved once so apiCall + cliClient never diverge
|
|
48
|
+
// and branding is read a single time; falls back neutrally if branding is unreadable.
|
|
49
|
+
const CLI_USER_AGENT = (() => {
|
|
50
|
+
try { return require('../../src/branding').userAgent(); }
|
|
51
|
+
catch (_) { return 'cloud-bongos-gds/1.0'; }
|
|
52
|
+
})();
|
|
53
|
+
|
|
54
|
+
// productName() — the instance's product name for user-facing CLI copy, from the
|
|
55
|
+
// branding pack (task 1002428). Was hardcoded as the founder brand at several
|
|
56
|
+
// print sites, so a non-OTB instance told ITS builders about someone else's
|
|
57
|
+
// product. Neutral fallback, never a foreign brand.
|
|
58
|
+
function productName() {
|
|
59
|
+
try {
|
|
60
|
+
const id = require('../../src/branding').branding().identity || {};
|
|
61
|
+
if (id.productName) return id.productName;
|
|
62
|
+
} catch (_) { /* fall through */ }
|
|
63
|
+
return 'Cloud Bongos';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// rewardPolicy() — the instance's RESOLVED reward policy (ADR 0146), for CLI copy
|
|
67
|
+
// that prints a task's `credits_reward`. On a `cost-plus-only` instance that
|
|
68
|
+
// number is an advertised GUIDE and is never paid; the cost-plus session reward
|
|
69
|
+
// is the sole stream. Printing it bare reads as a promise, and a builder who
|
|
70
|
+
// then ships and sees `+0` reasonably concludes the payout broke (it cost a real
|
|
71
|
+
// session on 2026-07-26 — task 1002469).
|
|
72
|
+
//
|
|
73
|
+
// PREFER THE SERVER'S ANSWER. The mode is INSTANCE config, and an instance's
|
|
74
|
+
// config/branding.json is NOT in the builder's checkout — a laptop resolving
|
|
75
|
+
// locally sees only config/branding.neutral.json and would report the paying
|
|
76
|
+
// default on the very instance that withholds. So callers pass the `reward`
|
|
77
|
+
// block the server sends (GET /me, POST /claims); the local read is a fallback
|
|
78
|
+
// for an older server that doesn't send one, and the ADR default is the floor.
|
|
79
|
+
// Resolution itself is always economy's (ADR 0146) — never re-derived here.
|
|
80
|
+
function rewardPolicy(fromServer) {
|
|
81
|
+
if (fromServer && typeof fromServer.mode === 'string') {
|
|
82
|
+
return { mode: fromServer.mode, perTaskCredit: fromServer.perTaskCredit !== false };
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const p = require('../../modules/economy/credits').rewardConfig();
|
|
86
|
+
if (p && typeof p.mode === 'string') return p;
|
|
87
|
+
} catch (_) { /* fall through */ }
|
|
88
|
+
return { mode: 'cost-plus-and-estimate', perTaskCredit: true };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// The one-line builder-facing explanation shown wherever an estimate is
|
|
92
|
+
// displayed on a cost-plus-only instance. Returns null on a paying instance so
|
|
93
|
+
// every call site is a no-op there. Single source for the wording so claim /
|
|
94
|
+
// start / ship say the SAME thing (task 1002469).
|
|
95
|
+
function costPlusOnlyNote(fromServer) {
|
|
96
|
+
if (rewardPolicy(fromServer).perTaskCredit) return null;
|
|
97
|
+
return 'This instance pays cost-plus only: you earn real session cost + 20%, not the estimate above.';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// hallBase(base) — the instance's builders' hall root for a given API base, e.g.
|
|
101
|
+
// https://cloudbongos.com/builders. Derived from the RESOLVED base (env → session →
|
|
102
|
+
// branding default), never a hardcoded host — so a CLI-printed link always points at
|
|
103
|
+
// the instance the command is actually talking to, not the founder's amazonprimea box
|
|
104
|
+
// (task 1002233; the printed-link twin of the task-1002201 default-base fix). Falls
|
|
105
|
+
// back to `${base}/builders` when base isn't a parseable absolute URL.
|
|
106
|
+
function hallBase(base = API_BASE) {
|
|
107
|
+
try { return `${new URL(base).origin}/builders`; }
|
|
108
|
+
catch { return `${String(base || '').replace(/\/+$/, '')}/builders`; }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Node-24-on-Windows workaround: every CLI invocation here is a short-lived
|
|
112
|
+
// process that makes one or a handful of requests, but a kept-alive socket can
|
|
113
|
+
// still be mid-close when the caller's process.exit() runs, tripping a
|
|
114
|
+
// UV_HANDLE_CLOSING assertion that corrupts piped JSON stdout. Connection reuse
|
|
115
|
+
// buys these one-shot processes nothing, so route requests through a dispatcher
|
|
116
|
+
// with keep-alive effectively disabled (mirrors cf-tunnel.js's dispatcher
|
|
117
|
+
// pattern). Degrades to the default dispatcher if undici can't be required.
|
|
118
|
+
let _noKeepAliveDispatcher;
|
|
119
|
+
let _noKeepAliveTried = false;
|
|
120
|
+
function noKeepAliveDispatcher() {
|
|
121
|
+
if (_noKeepAliveTried) return _noKeepAliveDispatcher;
|
|
122
|
+
_noKeepAliveTried = true;
|
|
123
|
+
try {
|
|
124
|
+
const { Agent } = require('undici');
|
|
125
|
+
_noKeepAliveDispatcher = new Agent({ keepAliveTimeout: 1, keepAliveMaxTimeout: 1 });
|
|
126
|
+
} catch { _noKeepAliveDispatcher = undefined; }
|
|
127
|
+
return _noKeepAliveDispatcher;
|
|
128
|
+
}
|
|
40
129
|
|
|
41
130
|
async function readSessionFile(p) {
|
|
42
131
|
try {
|
|
@@ -97,18 +186,37 @@ function hasFlag(name, args = process.argv.slice(2)) {
|
|
|
97
186
|
return args.includes(name);
|
|
98
187
|
}
|
|
99
188
|
|
|
189
|
+
// stripBom(s) — remove a leading UTF-8 BOM (U+FEFF), the artifact PowerShell's
|
|
190
|
+
// `Out-File`/`>` redirection writes. JSON.parse does not strip it, so a
|
|
191
|
+
// --body-file written from PS 5.1 fails to parse unless this runs first.
|
|
192
|
+
// Non-string input passes through unchanged. Extracted (rather than left
|
|
193
|
+
// inline in api.js) so it's unit-testable — api.js itself has no exports.
|
|
194
|
+
function stripBom(s) {
|
|
195
|
+
if (typeof s !== 'string') return s;
|
|
196
|
+
return s.charCodeAt(0) === 0xfeff ? s.slice(1) : s;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// resolveQuiet(args, env) — true when api.js's stderr status banner should be
|
|
200
|
+
// suppressed: the --quiet flag or the GDS_QUIET env var. Both default to the
|
|
201
|
+
// live process, matching hasFlag()'s own default-args contract. Default OFF —
|
|
202
|
+
// existing pipelines that scrape the banner off stderr are unaffected.
|
|
203
|
+
function resolveQuiet(args = process.argv.slice(2), env = process.env) {
|
|
204
|
+
return hasFlag('--quiet', args) || !!env.GDS_QUIET;
|
|
205
|
+
}
|
|
206
|
+
|
|
100
207
|
// readSessionToken() — the { token, apiBase } a token-only raw fetch needs, read
|
|
101
|
-
// synchronously from the session file.
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
208
|
+
// synchronously from the session file. apiBase resolves the one way every base
|
|
209
|
+
// resolution does — <PREFIX>_API_BASE (incl. legacy GDS_/PMS_) || session.api_base
|
|
210
|
+
// || the branding default — via ic.resolveApiBase, passing the session we already
|
|
211
|
+
// loaded so it needs no second disk read (task 1002427). Returns null when not
|
|
212
|
+
// signed in. Callers that must FAIL OPEN (e.g. the sound player) use this + their
|
|
213
|
+
// own fetch rather than apiCall (which exits on 401).
|
|
106
214
|
function readSessionToken() {
|
|
107
215
|
const j = loadSessionSync();
|
|
108
216
|
if (!j || !j.token) return null;
|
|
109
217
|
return {
|
|
110
218
|
token: j.token,
|
|
111
|
-
apiBase: ic.
|
|
219
|
+
apiBase: ic.resolveApiBase({ session: j }),
|
|
112
220
|
};
|
|
113
221
|
}
|
|
114
222
|
|
|
@@ -121,7 +229,7 @@ function printUnauthGuidanceAndExit() {
|
|
|
121
229
|
// Stable, single-screen guidance. Builders should see exactly this when
|
|
122
230
|
// their CLI session breaks — no raw API JSON, no stack trace.
|
|
123
231
|
console.error('');
|
|
124
|
-
console.error('✖ Your
|
|
232
|
+
console.error('✖ Your Bongos CLI session is no longer valid.');
|
|
125
233
|
console.error('');
|
|
126
234
|
console.error(' Recover the fast (UI-first) way — run /builder-reauth, or:');
|
|
127
235
|
console.error(' node scripts/gds/reauth.js');
|
|
@@ -164,7 +272,7 @@ function printBoxScopeGuidanceAndExit() {
|
|
|
164
272
|
console.error('');
|
|
165
273
|
console.error(' /builder-reauth (or: node scripts/gds/reauth.js)');
|
|
166
274
|
console.error('');
|
|
167
|
-
console.error(
|
|
275
|
+
console.error(` It opens your Settings page (${hallBase()}/settings) on`);
|
|
168
276
|
console.error(' YOUR machine — under "CLI Access" click "Re-issue CLI token", copy the one-liner, paste it back.');
|
|
169
277
|
console.error(' No terminal Device Flow. Once re-authed, re-run whichever command you were');
|
|
170
278
|
console.error(' trying — if it had already done work, it resumes from where it stopped (task 941).');
|
|
@@ -189,6 +297,18 @@ function safeBranding() {
|
|
|
189
297
|
catch { return {}; }
|
|
190
298
|
}
|
|
191
299
|
|
|
300
|
+
// True when THIS checkout declares an instance identity — i.e. config/branding.json
|
|
301
|
+
// exists. A NEUTRAL checkout (the portable core, or a fresh template carrying only
|
|
302
|
+
// branding.neutral.json) returns false: its resolved publicOrigin is just the neutral
|
|
303
|
+
// placeholder, not a real declared instance, so there is nothing to enforce a session
|
|
304
|
+
// against. Injectable so the pure guard stays unit-testable. (task 1002399, area 2)
|
|
305
|
+
function defaultHasInstanceConfig() {
|
|
306
|
+
try {
|
|
307
|
+
const { INSTANCE_PATH } = require('../../src/branding');
|
|
308
|
+
return !!(INSTANCE_PATH && fs.existsSync(INSTANCE_PATH));
|
|
309
|
+
} catch { return false; }
|
|
310
|
+
}
|
|
311
|
+
|
|
192
312
|
// Host of an origin string, or null if unparseable. Comparing HOSTS (not full
|
|
193
313
|
// origins) tolerates scheme / trailing-slash noise between branding + session.
|
|
194
314
|
function instanceOriginHost(origin) {
|
|
@@ -200,12 +320,18 @@ function instanceOriginHost(origin) {
|
|
|
200
320
|
// DIFFERENT instance than this repo is configured for. Fails OPEN (no mismatch)
|
|
201
321
|
// when: an explicit <PREFIX>_API_BASE override is set (dev/staging — the operator
|
|
202
322
|
// is deliberately steering the base); OR there is no session (nothing to borrow —
|
|
203
|
-
// the no-session/401 path handles that); OR
|
|
323
|
+
// the no-session/401 path handles that); OR this checkout is NEUTRAL (no
|
|
324
|
+
// config/branding.json — no declared instance to enforce, so trust the session:
|
|
325
|
+
// this is what lets a neutral core checkout run every /builder-* command with NO
|
|
326
|
+
// <PREFIX>_API_BASE prefix — task 1002399); OR the repo declares no publicOrigin
|
|
204
327
|
// (unknown identity — you can't assert a mismatch you can't define). Otherwise the
|
|
205
|
-
// mismatch is expectedHost !== sessionHost
|
|
206
|
-
|
|
328
|
+
// mismatch is expectedHost !== sessionHost — the guard that still fires for a
|
|
329
|
+
// BRANDED checkout whose session points at a different instance (the 2026-07-08
|
|
330
|
+
// hazard).
|
|
331
|
+
function detectInstanceMismatch({ session, brand = safeBranding(), env = process.env, hasInstanceConfig = defaultHasInstanceConfig } = {}) {
|
|
207
332
|
if (ic.resolveEnv('API_BASE', { env })) return { mismatch: false };
|
|
208
333
|
if (!session || !session.api_base) return { mismatch: false };
|
|
334
|
+
if (!hasInstanceConfig()) return { mismatch: false };
|
|
209
335
|
const expected = brand && brand.domains && brand.domains.publicOrigin;
|
|
210
336
|
if (!expected) return { mismatch: false };
|
|
211
337
|
const expectedHost = instanceOriginHost(expected);
|
|
@@ -284,10 +410,10 @@ async function apiCall(method, urlPath, body = null, opts = {}) {
|
|
|
284
410
|
// (local dev / staging override), then the logged-in instance, then prod. OTB
|
|
285
411
|
// sessions already store api_base='https://amazonprimea.com', so this is a no-op
|
|
286
412
|
// for the OTB instance and only bites once someone logs in to a different one.
|
|
287
|
-
const base = ic.
|
|
413
|
+
const base = ic.resolveApiBase({ session: session || null });
|
|
288
414
|
const headers = {
|
|
289
415
|
'Content-Type': 'application/json',
|
|
290
|
-
'User-Agent':
|
|
416
|
+
'User-Agent': CLI_USER_AGENT,
|
|
291
417
|
};
|
|
292
418
|
if (session && session.token) {
|
|
293
419
|
headers['Authorization'] = `Bearer ${session.token}`;
|
|
@@ -302,6 +428,7 @@ async function apiCall(method, urlPath, body = null, opts = {}) {
|
|
|
302
428
|
method,
|
|
303
429
|
headers,
|
|
304
430
|
body: body ? JSON.stringify(body) : null,
|
|
431
|
+
dispatcher: noKeepAliveDispatcher(),
|
|
305
432
|
});
|
|
306
433
|
const text = await res.text();
|
|
307
434
|
let data;
|
|
@@ -355,7 +482,7 @@ async function cliClient(opts = {}) {
|
|
|
355
482
|
printInstanceMismatchAndExit(mm);
|
|
356
483
|
}
|
|
357
484
|
}
|
|
358
|
-
const base = ic.
|
|
485
|
+
const base = ic.resolveApiBase({ session: session || null });
|
|
359
486
|
const token = (session && session.token) || null;
|
|
360
487
|
const key = `${base}|${token || ''}|${allowUnauthenticated}|${allowBoxScope}`;
|
|
361
488
|
if (_clientCache.has(key)) return _clientCache.get(key);
|
|
@@ -363,7 +490,8 @@ async function cliClient(opts = {}) {
|
|
|
363
490
|
const { createClient } = await _clientModPromise;
|
|
364
491
|
const authedFetch = async (url, init) => {
|
|
365
492
|
init = init || {};
|
|
366
|
-
init.headers = Object.assign({ 'User-Agent':
|
|
493
|
+
init.headers = Object.assign({ 'User-Agent': CLI_USER_AGENT }, init.headers);
|
|
494
|
+
if (init.dispatcher === undefined) init.dispatcher = noKeepAliveDispatcher();
|
|
367
495
|
const res = await fetch(url, init);
|
|
368
496
|
if (res.status === 401 && !allowUnauthenticated && !_unauthExitInFlight) {
|
|
369
497
|
_unauthExitInFlight = true;
|
|
@@ -386,7 +514,7 @@ async function cliClient(opts = {}) {
|
|
|
386
514
|
async function requireSession() {
|
|
387
515
|
const s = await loadSession();
|
|
388
516
|
if (!s || !s.token) {
|
|
389
|
-
console.error(`No
|
|
517
|
+
console.error(`No Bongos session found at ${SESSION_PATH}.`);
|
|
390
518
|
console.error(`You're not signed in yet — pick the line that fits:`);
|
|
391
519
|
console.error(` • First time on the project → run /builder-setup (one-time GitHub sign-in that also registers you)`);
|
|
392
520
|
console.error(` • Signed in before / expired → run /builder-reauth (fast UI re-issue of your CLI token)`);
|
|
@@ -482,7 +610,7 @@ function nudgeActionForStage(stage) {
|
|
|
482
610
|
case 'auth_complete':
|
|
483
611
|
return 'Run node scripts/gds/setup.js to authenticate.';
|
|
484
612
|
case 'pick_disciplines':
|
|
485
|
-
return
|
|
613
|
+
return `Choose the work that calls you — open the hall at ${hallBase()} and pick engineer, artist, ideator, or any mix.`;
|
|
486
614
|
case 'first_start':
|
|
487
615
|
return 'Run /builder-start to survey what you can claim.';
|
|
488
616
|
case 'first_claim':
|
|
@@ -537,7 +665,7 @@ function printCostWarning(meBody) {
|
|
|
537
665
|
// when nothing needs action (covered / satisfied items don't nag) or when the
|
|
538
666
|
// payload is absent (older API / network glitch). One line per item, with the
|
|
539
667
|
// concrete place to fix it. The hall Standing card renders the same `needs` data.
|
|
540
|
-
const HALL_BASE =
|
|
668
|
+
const HALL_BASE = hallBase();
|
|
541
669
|
function printNeedsNudge(meBody) {
|
|
542
670
|
if (!meBody || typeof meBody !== 'object') return;
|
|
543
671
|
const needs = meBody.needs;
|
|
@@ -606,12 +734,18 @@ function parseSessionRequest(text) {
|
|
|
606
734
|
|
|
607
735
|
module.exports = {
|
|
608
736
|
API_BASE,
|
|
737
|
+
hallBase,
|
|
738
|
+
productName,
|
|
739
|
+
rewardPolicy,
|
|
740
|
+
costPlusOnlyNote,
|
|
609
741
|
SESSION_PATH,
|
|
610
742
|
loadSession,
|
|
611
743
|
loadSessionSync,
|
|
612
744
|
saveSession,
|
|
613
745
|
arg,
|
|
614
746
|
hasFlag,
|
|
747
|
+
stripBom,
|
|
748
|
+
resolveQuiet,
|
|
615
749
|
readSessionToken,
|
|
616
750
|
apiCall,
|
|
617
751
|
cliClient,
|
package/scripts/gds/code.js
CHANGED
|
@@ -67,11 +67,15 @@ async function main() {
|
|
|
67
67
|
|
|
68
68
|
if (local) return runLocal({ log });
|
|
69
69
|
|
|
70
|
-
console.log(
|
|
71
|
-
const { box } = await lib.prepareConnection({ log });
|
|
70
|
+
console.log(`▸ Connecting VS Code to your ${require('./cli-lib').productName()} dev box…`);
|
|
71
|
+
const { box, keyPath } = await lib.prepareConnection({ log });
|
|
72
72
|
const host = box.hostname;
|
|
73
73
|
const authority = lib.remoteAuthority(host);
|
|
74
74
|
|
|
75
|
+
// BV1.R101: re-scope /workspace to the active claim before the window opens, so
|
|
76
|
+
// VS Code shows exactly that task's code. Best-effort — VS Code opens regardless.
|
|
77
|
+
lib.resyncBoxOverSsh({ host, keyPath, log });
|
|
78
|
+
|
|
75
79
|
if (!noExtension) {
|
|
76
80
|
log(` Installing Claude's VS Code extension on the box (${lib.CLAUDE_VSCODE_EXTENSION})…`);
|
|
77
81
|
const ext = runCode(['--remote', authority, '--install-extension', lib.CLAUDE_VSCODE_EXTENSION]);
|