@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/scripts/gds/ship.js
CHANGED
|
@@ -31,6 +31,13 @@
|
|
|
31
31
|
// # empty-diff guard; confirms directly (no diff to
|
|
32
32
|
// # grade). REFUSED if the working tree is dirty —
|
|
33
33
|
// # that means you forgot to commit (task 1363/1365).
|
|
34
|
+
// node scripts/gds/ship.js <task-id> --base <ref> # grade a task whose commits ALREADY landed on main
|
|
35
|
+
// # (e.g. a release task: package-core needs the merge
|
|
36
|
+
// # first). Pins <ref> as the pre-task baseline so the
|
|
37
|
+
// # grader reads the REAL diff <ref>...HEAD and confirms
|
|
38
|
+
// # via the RANK-OPEN graded path — the sub-Archon-safe
|
|
39
|
+
// # alternative to --allow-empty (task 1002422 / blocker
|
|
40
|
+
// # 1000082). Implies db-only (nothing left to merge).
|
|
34
41
|
// node scripts/gds/ship.js <task-id> --regrade # re-grade a task STUCK at `completed` (a ship
|
|
35
42
|
// # whose grade failed): re-runs ONLY the grade step
|
|
36
43
|
// # against the committed diff, no claim required.
|
|
@@ -54,7 +61,20 @@ const fs = require('node:fs');
|
|
|
54
61
|
const os = require('node:os');
|
|
55
62
|
const path = require('node:path');
|
|
56
63
|
const readline = require('node:readline');
|
|
57
|
-
const { cliClient, requireSession, loadSession, printOnboardingNudge, arg } = require('./cli-lib');
|
|
64
|
+
const { cliClient, requireSession, loadSession, loadSessionSync, printOnboardingNudge, arg, rewardPolicy } = require('./cli-lib');
|
|
65
|
+
|
|
66
|
+
// The instance's reward policy, as reported by the server on GET /me (ADR 0146 /
|
|
67
|
+
// task 1002469). Cached module-level because the credits print + the completion
|
|
68
|
+
// card are reached through several call chains and the mode is a single
|
|
69
|
+
// instance-wide fact — threading it through each would be noise. Stays null
|
|
70
|
+
// until a /me lands; rewardPolicy(null) then degrades to the paying default, so
|
|
71
|
+
// a missed fetch under-explains rather than wrongly claiming "not paid".
|
|
72
|
+
let serverReward = null;
|
|
73
|
+
function noteServerReward(meRes) {
|
|
74
|
+
const r = meRes && meRes.ok && meRes.data && meRes.data.reward;
|
|
75
|
+
if (r && typeof r.mode === 'string') serverReward = r;
|
|
76
|
+
return meRes;
|
|
77
|
+
}
|
|
58
78
|
// ship-preflight + merge-lock carved to modules/lifecycle/ (BV1.R86). A script may
|
|
59
79
|
// require a module directly (it's outside the kernel boundary — the grading/economy
|
|
60
80
|
// CLI precedent).
|
|
@@ -95,7 +115,11 @@ const { playSound } = (() => {
|
|
|
95
115
|
return { playSound: () => {} };
|
|
96
116
|
}
|
|
97
117
|
})();
|
|
98
|
-
|
|
118
|
+
// The laptop-mode SSH deploy target is resolved at land time from config/deploy.json
|
|
119
|
+
// ("sshTarget") or <PREFIX>_DEPLOY_SSH_TARGET — see resolveDeployTarget(). There is NO
|
|
120
|
+
// hardcoded default: the core carries no deploy target of its own. The founder
|
|
121
|
+
// instance's old game-box SSH target used to be baked in here, which post-migration
|
|
122
|
+
// meant a laptop-mode ship deployed to the WRONG server (task 1002202).
|
|
99
123
|
const BEHIND_MAIN_LIMIT = 5;
|
|
100
124
|
|
|
101
125
|
// arg() — the =-aware flag-value parser now lives in cli-lib (the canonical
|
|
@@ -199,8 +223,14 @@ function claudeConfigBase() {
|
|
|
199
223
|
}
|
|
200
224
|
|
|
201
225
|
function encodeProjectPath(absProjectPath) {
|
|
202
|
-
// Claude Code's encoding:
|
|
203
|
-
|
|
226
|
+
// Claude Code's encoding: every NON-ALPHANUMERIC char becomes '-'. The old
|
|
227
|
+
// `[/.]` class silently no-op'd on Windows (separator '\', drive ':', spaces),
|
|
228
|
+
// so the transcript was never found, no session_record was written, and the
|
|
229
|
+
// cost-plus reward — the only stream a cost-plus-only instance pays (ADR 0146)
|
|
230
|
+
// — could never book: every Windows ship paid 0 (task 1002471). Same rule as
|
|
231
|
+
// scripts/gds/onboarding-config.js, which already had it right. POSIX output is
|
|
232
|
+
// unchanged for paths whose only specials are '/' and '.'.
|
|
233
|
+
return String(absProjectPath).replace(/[^a-zA-Z0-9]/g, '-');
|
|
204
234
|
}
|
|
205
235
|
|
|
206
236
|
// resolveTranscriptPath — find this session's transcript .jsonl. Claude Code
|
|
@@ -323,7 +353,11 @@ const {
|
|
|
323
353
|
// snippetOf — short, single-line, scrubbed excerpt for footgun context.
|
|
324
354
|
function snippetOf(text, extraSecrets, max = 160) {
|
|
325
355
|
if (typeof text !== 'string' || text.length === 0) return undefined;
|
|
326
|
-
|
|
356
|
+
// Drop NULs first: Postgres rejects a literal NUL in jsonb, so ONE of them anywhere in
|
|
357
|
+
// ONE snippet made the whole digest unstorable — POST /sessions answered 500 and
|
|
358
|
+
// the session never paid. UTF-16LE tool output (`wsl --list` is the observed
|
|
359
|
+
// case) is full of them, and \s below does NOT match NUL, so it must go here.
|
|
360
|
+
const flat = text.replace(/\0/g, '').replace(/\s+/g, ' ').trim();
|
|
327
361
|
if (!flat) return undefined;
|
|
328
362
|
const cut = flat.length > max ? flat.slice(0, max) : flat;
|
|
329
363
|
const scrubbed = scrubSecrets(cut, extraSecrets);
|
|
@@ -762,9 +796,13 @@ function buildMergeArgv(branch, valueSummary) {
|
|
|
762
796
|
// Two ways the confirmed→shipped land can run, chosen by a SINGLE committed
|
|
763
797
|
// switch (config/deploy.json `mode`, env OTB_DEPLOY_MODE overrides):
|
|
764
798
|
//
|
|
765
|
-
// 'laptop' (default) — the operator's machine merges to main locally and
|
|
766
|
-
//
|
|
767
|
-
//
|
|
799
|
+
// 'laptop' (default) — the operator's machine merges to main locally and, IFF a
|
|
800
|
+
// deploy target is configured (config/deploy.json "sshTarget" or
|
|
801
|
+
// <PREFIX>_DEPLOY_SSH_TARGET, resolveDeployTarget()), SSHes `~/deploy.sh` on it.
|
|
802
|
+
// The core carries NO target of its own, so by default this is a merge-to-main
|
|
803
|
+
// with no SSH deploy — a change reaches a live instance via package-core +
|
|
804
|
+
// `bongos upgrade` (pin-bump), NOT an SSH deploy (ADR 0108). When a target IS
|
|
805
|
+
// set it requires that host's SSH key, so only the operator can land.
|
|
768
806
|
//
|
|
769
807
|
// 'ci' — NO builder box holds the prod key. ship pushes the branch, opens a
|
|
770
808
|
// PR, and enables GitHub auto-merge; once required checks pass, GitHub
|
|
@@ -815,12 +853,7 @@ function effectiveDeployMode(mode, { canPushLocally, envForced } = {}) {
|
|
|
815
853
|
}
|
|
816
854
|
|
|
817
855
|
function resolveDeployMode() {
|
|
818
|
-
|
|
819
|
-
try {
|
|
820
|
-
configRaw = fs.readFileSync(path.join(REPO_ROOT, 'config', 'deploy.json'), 'utf8');
|
|
821
|
-
} catch (_) {
|
|
822
|
-
/* no config file → safe default */
|
|
823
|
-
}
|
|
856
|
+
const configRaw = readDeployConfigRaw();
|
|
824
857
|
const mode = parseDeployMode(process.env.OTB_DEPLOY_MODE, configRaw);
|
|
825
858
|
const envForced = ['ci', 'laptop'].includes(
|
|
826
859
|
String(process.env.OTB_DEPLOY_MODE || '').trim().toLowerCase()
|
|
@@ -834,6 +867,111 @@ function resolveDeployMode() {
|
|
|
834
867
|
return mode;
|
|
835
868
|
}
|
|
836
869
|
|
|
870
|
+
// readDeployConfigRaw — raw contents of config/deploy.json (or null if absent /
|
|
871
|
+
// unreadable). One reader shared by resolveDeployMode (the mode switch) and
|
|
872
|
+
// resolveDeployTarget (the SSH target) so they never diverge on where the file is.
|
|
873
|
+
function readDeployConfigRaw() {
|
|
874
|
+
try {
|
|
875
|
+
return fs.readFileSync(path.join(REPO_ROOT, 'config', 'deploy.json'), 'utf8');
|
|
876
|
+
} catch (_) {
|
|
877
|
+
return null; // no config file → callers apply their safe default
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// safeBranding — the instance brand pack, or null if it can't be read. Guarded the
|
|
882
|
+
// same way ship.js guards the sound player (line ~91): a deploy must never crash
|
|
883
|
+
// because the brand file is missing on a fresh / re-seeded checkout.
|
|
884
|
+
function safeBranding() {
|
|
885
|
+
try {
|
|
886
|
+
return require('../../src/branding').branding();
|
|
887
|
+
} catch (_) {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// readSessionApiBase — the signed-in session's persisted api_base (the instance the
|
|
893
|
+
// CLI is actually pointed at), or null when not signed in / unreadable. This is the
|
|
894
|
+
// session's OWN value only — deliberately NOT readSessionToken().apiBase, which
|
|
895
|
+
// already folds in the env override + branding default; resolveInstanceBase orders
|
|
896
|
+
// that precedence itself so it stays pure over its injected inputs.
|
|
897
|
+
function readSessionApiBase() {
|
|
898
|
+
try {
|
|
899
|
+
const s = loadSessionSync();
|
|
900
|
+
return (s && typeof s.api_base === 'string' && s.api_base.trim()) ? s.api_base.trim() : null;
|
|
901
|
+
} catch (_) {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// resolveInstanceBase — the ORIGIN of the instance this ship is landing on, used by
|
|
907
|
+
// the ci-land deployed-version probe (/version), the /healthz re-probe, the ci
|
|
908
|
+
// PR-body deploy note, and the manual-confirm curl guidance. Precedence mirrors the
|
|
909
|
+
// CLI's own base resolution: <PREFIX>_API_BASE env → the signed-in session's
|
|
910
|
+
// api_base → branding publicOrigin → null (unknowable). There is NO hardcoded
|
|
911
|
+
// founder host — 5 sites here baked in `https://amazonprimea.com`, so a ci-land /
|
|
912
|
+
// manual-confirm on a migrated instance pointed the builder at the WRONG box; the
|
|
913
|
+
// ci-land/guidance sibling of resolveDeployTarget()'s laptop-mode fix (task 1002232
|
|
914
|
+
// ⊃ task 1002202). Pure over its injected inputs (env / sessionApiBase / branding)
|
|
915
|
+
// so the precedence is unit-tested without fs/env/network; the defaults read the
|
|
916
|
+
// real session file + brand pack.
|
|
917
|
+
function resolveInstanceBase({
|
|
918
|
+
env = process.env,
|
|
919
|
+
sessionApiBase = readSessionApiBase(),
|
|
920
|
+
branding = safeBranding(),
|
|
921
|
+
} = {}) {
|
|
922
|
+
const ic = require('../../src/instance-config');
|
|
923
|
+
const trim = (s) => (typeof s === 'string' && s.trim() ? s.trim().replace(/\/+$/, '') : null);
|
|
924
|
+
const envBase = trim(ic.resolveEnv('API_BASE', { env }));
|
|
925
|
+
if (envBase) return envBase;
|
|
926
|
+
const sess = trim(sessionApiBase);
|
|
927
|
+
if (sess) return sess;
|
|
928
|
+
const origin = branding && branding.domains && branding.domains.publicOrigin;
|
|
929
|
+
return trim(origin);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// deployedVersionUrl — the instance's /version endpoint, or null when no instance
|
|
933
|
+
// origin is resolvable (then fetchDeployedVersion is a no-op). Derived from
|
|
934
|
+
// resolveInstanceBase — no hardcoded host (task 1002232).
|
|
935
|
+
function deployedVersionUrl() {
|
|
936
|
+
const base = resolveInstanceBase();
|
|
937
|
+
return base ? `${base}/version` : null;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// resolveDeployTarget — WHERE a laptop-mode ship SSHes `~/deploy.sh`, and which
|
|
941
|
+
// /healthz it re-probes when deploy.sh's own health check races the restart window.
|
|
942
|
+
// Pure (env + raw config + branding → target) and exported so the precedence and the
|
|
943
|
+
// safe "no target" default are unit-tested without touching fs/env/network.
|
|
944
|
+
//
|
|
945
|
+
// There is deliberately NO hardcoded default. The founder instance's old game-box
|
|
946
|
+
// SSH target was baked in here, so a laptop-mode ship from a migrated
|
|
947
|
+
// checkout SSHed the WRONG server (task 1002202). The core carries no deploy target
|
|
948
|
+
// of its own — under the standalone model a core change reaches a live instance via
|
|
949
|
+
// package-core + `bongos upgrade` (pin-bump), not an SSH deploy (ADR 0108). An
|
|
950
|
+
// instance whose OWN checkout is its live server opts back into laptop-SSH deploy by
|
|
951
|
+
// setting the target:
|
|
952
|
+
// config/deploy.json → { "sshTarget": "user@host", "healthzUrl": "https://…/healthz" }
|
|
953
|
+
// env → <PREFIX>_DEPLOY_SSH_TARGET, <PREFIX>_DEPLOY_HEALTHZ_URL
|
|
954
|
+
// healthzUrl, when unset, falls back to the instance's own publicOrigin + /healthz
|
|
955
|
+
// (never a hardcoded host); null when even that is unknown (re-probe then skipped).
|
|
956
|
+
function resolveDeployTarget({ env = process.env, configRaw = null, branding = null } = {}) {
|
|
957
|
+
const ic = require('../../src/instance-config');
|
|
958
|
+
let cfg = {};
|
|
959
|
+
if (configRaw) {
|
|
960
|
+
try { cfg = JSON.parse(configRaw) || {}; } catch (_) { cfg = {}; }
|
|
961
|
+
}
|
|
962
|
+
const str = (v) => (typeof v === 'string' && v.trim() ? v.trim() : null);
|
|
963
|
+
|
|
964
|
+
const sshTarget = ic.resolveEnv('DEPLOY_SSH_TARGET', { env }) || str(cfg.sshTarget) || null;
|
|
965
|
+
|
|
966
|
+
let healthzUrl = ic.resolveEnv('DEPLOY_HEALTHZ_URL', { env }) || str(cfg.healthzUrl) || null;
|
|
967
|
+
const publicOrigin = branding && branding.domains && branding.domains.publicOrigin;
|
|
968
|
+
if (!healthzUrl && str(publicOrigin)) {
|
|
969
|
+
try { healthzUrl = new URL('/healthz', publicOrigin).toString(); }
|
|
970
|
+
catch (_) { /* malformed origin → leave healthzUrl null */ }
|
|
971
|
+
}
|
|
972
|
+
return { sshTarget, healthzUrl };
|
|
973
|
+
}
|
|
974
|
+
|
|
837
975
|
// gh runners — mirror gitArgvOk/gitOk but for the GitHub CLI. No shell, ever:
|
|
838
976
|
// branch names + DB-sourced value_summary reach `gh` as literal argv elements
|
|
839
977
|
// (same injection discipline as #681). ghArgvOk = boolean side-effect; ghJson =
|
|
@@ -889,7 +1027,10 @@ function deployedCommitStatus(versionBody, mergeSha) {
|
|
|
889
1027
|
}
|
|
890
1028
|
|
|
891
1029
|
// Fetch + parse the prod /version JSON (#858). null on any failure — never throws.
|
|
892
|
-
|
|
1030
|
+
// Default target is the resolved instance origin (no hardcoded host — task 1002232);
|
|
1031
|
+
// null when none is resolvable, in which case the probe is skipped.
|
|
1032
|
+
function fetchDeployedVersion(url = deployedVersionUrl()) {
|
|
1033
|
+
if (!url) return null; // no resolvable instance origin → nothing to probe
|
|
893
1034
|
// stdio override DISCARDS stderr (a curl progress/error line must not pollute
|
|
894
1035
|
// the JSON parse) — the documented per-caller difference from ghJson.
|
|
895
1036
|
const out = argvOut('curl', ['-fsS', '--max-time', '10', url], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
@@ -1032,11 +1173,15 @@ async function ciLandServer(taskId, branch, valueSummary) {
|
|
|
1032
1173
|
// Pure + exported for unit tests (no git/network). Accepts the publish-status
|
|
1033
1174
|
// response shape verbatim — getPublishStatus returns snake_case (pr_state,
|
|
1034
1175
|
// deploy_status); camelCase aliases are tolerated for test convenience.
|
|
1176
|
+
// task 1002472: 'unconfigured' means the repo has no deploy-prod.yml to wait for
|
|
1177
|
+
// (ADR 0108 pin-bump publishing), so the merge IS the land. Mirrors
|
|
1178
|
+
// github-push.landConfirmed — kept in step by tests/ship_ci_deploy.mjs, since this
|
|
1179
|
+
// CLI cannot import the server module.
|
|
1035
1180
|
function classifyLandOutcome(s) {
|
|
1036
1181
|
const merged = !!(s && s.merged);
|
|
1037
1182
|
const deployStatus = s && (s.deploy_status ?? s.deployStatus);
|
|
1038
1183
|
const prState = s && (s.pr_state ?? s.prState);
|
|
1039
|
-
if (merged && deployStatus === 'success') return 'shipped';
|
|
1184
|
+
if (merged && (deployStatus === 'success' || deployStatus === 'unconfigured')) return 'shipped';
|
|
1040
1185
|
if (prState === 'closed' && !merged) return 'failed';
|
|
1041
1186
|
return 'pending';
|
|
1042
1187
|
}
|
|
@@ -1221,11 +1366,12 @@ async function ciLandLocal(taskId, branch, valueSummary) {
|
|
|
1221
1366
|
// GitHub's PR/issue number space (dead links for GDS task ids — task 916),
|
|
1222
1367
|
// so the task ref is written as "task NNN" and the summary is de-#-ed.
|
|
1223
1368
|
const title = `task ${taskId}: ${sanitizeGitHubRefs(valueSummary) || branch}`.slice(0, 240);
|
|
1369
|
+
const deployTargetNote = resolveInstanceBase() || 'the configured instance';
|
|
1224
1370
|
const body =
|
|
1225
1371
|
`Auto-opened by ship.js (deploy mode: ci) for task ${taskId}.\n\n` +
|
|
1226
1372
|
`${sanitizeGitHubRefs(valueSummary) || '(no value summary)'}\n\n` +
|
|
1227
1373
|
`Merging this PR triggers .github/workflows/deploy-prod.yml to deploy to ` +
|
|
1228
|
-
|
|
1374
|
+
`${deployTargetNote}. Auto-merge is enabled — GitHub lands it once the ` +
|
|
1229
1375
|
`required checks pass.`;
|
|
1230
1376
|
if (!existing || existing.state === 'CLOSED' || existing.state === 'MERGED') {
|
|
1231
1377
|
console.log(' ci-land: opening PR -> main...');
|
|
@@ -1307,12 +1453,14 @@ function pollCiDeploy(taskId, branch, headSha) {
|
|
|
1307
1453
|
// real deploy into a false negative on a transient miss or a concurrent
|
|
1308
1454
|
// later deploy).
|
|
1309
1455
|
const status = deployedCommitStatus(fetchDeployedVersion(), mergeSha);
|
|
1456
|
+
const instanceBase = resolveInstanceBase();
|
|
1457
|
+
const healthzUrl = instanceBase ? `${instanceBase}/healthz` : null;
|
|
1310
1458
|
if (status === 'match') {
|
|
1311
1459
|
console.log(' ci-land: /version confirms prod is live on the merge commit (commit-pinned).');
|
|
1312
1460
|
} else if (status === 'mismatch') {
|
|
1313
1461
|
console.log(' ci-land: deploy run succeeded; /version shows a different commit (a later deploy has since superseded it — fine, our change is in main).');
|
|
1314
|
-
} else if (shellOk(
|
|
1315
|
-
console.log(
|
|
1462
|
+
} else if (healthzUrl && shellOk(`curl -fsS --max-time 10 ${healthzUrl} -o /dev/null`)) {
|
|
1463
|
+
console.log(` ci-land: /version unavailable; ${healthzUrl} 200 — treating the successful deploy run as authoritative.`);
|
|
1316
1464
|
} else {
|
|
1317
1465
|
console.log(' ci-land: deploy run succeeded but /version + healthz probes both missed (likely transient). Treating the successful deploy run as authoritative.');
|
|
1318
1466
|
}
|
|
@@ -1337,6 +1485,16 @@ function pollCiDeploy(taskId, branch, headSha) {
|
|
|
1337
1485
|
// Also refuses when the branch is far behind origin/main — merging a stale
|
|
1338
1486
|
// branch is a known regression vector.
|
|
1339
1487
|
|
|
1488
|
+
// parseDeletedUpstreamPaths — pure: turn `git log --diff-filter=D --name-only
|
|
1489
|
+
// --pretty=format:` output (one deleted path per line, blank lines between
|
|
1490
|
+
// commits) into a deduped, order-preserving list. Exported so preflight's
|
|
1491
|
+
// upstream-deletion warning (step (d)) is unit-tested without a live git repo.
|
|
1492
|
+
// Null/empty input (no matches, or the git call failed closed) → [].
|
|
1493
|
+
function parseDeletedUpstreamPaths(rawLogOutput) {
|
|
1494
|
+
if (!rawLogOutput) return [];
|
|
1495
|
+
return [...new Set(rawLogOutput.split('\n').map((s) => s.trim()).filter(Boolean))];
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1340
1498
|
function preflight(claim, opts = {}) {
|
|
1341
1499
|
// (a) Working-tree check — refuse if any TRACKED file carries an uncommitted
|
|
1342
1500
|
// modification or staged change. This fires post-commit, so a clean tree
|
|
@@ -1409,6 +1567,39 @@ function preflight(claim, opts = {}) {
|
|
|
1409
1567
|
}
|
|
1410
1568
|
}
|
|
1411
1569
|
|
|
1570
|
+
// (d) Upstream-deletion warning (WARN-ONLY — never blocks, never changes the
|
|
1571
|
+
// exit code). main can delete or relocate a surface between when a task was
|
|
1572
|
+
// claimed and when it ships (a cutover, a carve, a rename — the R89 hall-ui
|
|
1573
|
+
// cutover class); the branch's own diff still touches the OLD path, and an
|
|
1574
|
+
// oblivious ship can silently revert the upstream deletion or leave this
|
|
1575
|
+
// branch's work orphaned. Scoped to the paths THIS branch's diff actually
|
|
1576
|
+
// touches — the same baseline/diff computeGraderBaseline/computeChangedFiles
|
|
1577
|
+
// compute for the grader — so an unrelated upstream deletion elsewhere in the
|
|
1578
|
+
// repo never fires it. Silent no-op if origin/main isn't resolvable (offline /
|
|
1579
|
+
// never fetched) or this branch's diff is empty — the check itself must never
|
|
1580
|
+
// be why a ship breaks. parseDeletedUpstreamPaths is the pure parsing seam,
|
|
1581
|
+
// exported + unit-tested (tests/ship_upstream_deletion.mjs) without a live git
|
|
1582
|
+
// repo; this block is just the git I/O + presentation around it.
|
|
1583
|
+
const upstreamDelBaseline = computeGraderBaseline(claim);
|
|
1584
|
+
const upstreamDelTouched = upstreamDelBaseline ? computeChangedFiles(upstreamDelBaseline) : [];
|
|
1585
|
+
if (upstreamDelTouched.length > 0) {
|
|
1586
|
+
const deletedUpstreamStr = argvOut('git', [
|
|
1587
|
+
'log', 'HEAD..origin/main', '--diff-filter=D', '--name-only', '--pretty=format:',
|
|
1588
|
+
'--', ...upstreamDelTouched,
|
|
1589
|
+
]);
|
|
1590
|
+
const deletedUpstream = parseDeletedUpstreamPaths(deletedUpstreamStr);
|
|
1591
|
+
if (deletedUpstream.length > 0) {
|
|
1592
|
+
console.error('');
|
|
1593
|
+
console.error(`⚠ Upstream deletion warning: main has removed ${deletedUpstream.length} path(s) this branch still touches, since this task was claimed:`);
|
|
1594
|
+
for (const f of deletedUpstream) console.error(` - ${f}`);
|
|
1595
|
+
console.error(' This looks like the R89 hall-ui cutover class: a surface removed or relocated upstream while the claim was in flight.');
|
|
1596
|
+
console.error(' Verify these paths still exist on main (or that the removal/rename is intentional) before shipping —');
|
|
1597
|
+
console.error(" shipping anyway may silently revert the upstream deletion or leave this branch's work orphaned.");
|
|
1598
|
+
console.error(' Warning only — not blocking; the ship proceeds.');
|
|
1599
|
+
console.error('');
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1412
1603
|
console.log(` preflight: working tree clean, branch ${behind} commits behind main, ship tooling current — OK`);
|
|
1413
1604
|
return { ok: true };
|
|
1414
1605
|
}
|
|
@@ -1585,13 +1776,80 @@ function autoLinkifyChangedDocs(ac) {
|
|
|
1585
1776
|
// full unified diff against the baseline, and (for the audit-trail signals)
|
|
1586
1777
|
// the shortstat summary.
|
|
1587
1778
|
//
|
|
1779
|
+
// task 1002422 (blocker 1000082): a builder-settable pre-task baseline. When a
|
|
1780
|
+
// task's artifact reached main BEFORE the ship (a release task: package-core
|
|
1781
|
+
// archives committed HEAD, so the version bump must merge first), merge-base(
|
|
1782
|
+
// origin/main, HEAD) == HEAD → an EMPTY committed diff → the --allow-empty /
|
|
1783
|
+
// skip-grade / Archon-only /confirm dead-end that NO sub-Archon builder can
|
|
1784
|
+
// finish. --base <ref> pins the pre-task commit so the grader reads the REAL diff
|
|
1785
|
+
// <base>...HEAD and the ship confirms via the rank-open graded path instead. Set
|
|
1786
|
+
// once in main() after resolveBaseRef() validates it; null on a normal ship.
|
|
1787
|
+
let baseOverrideSha = null;
|
|
1788
|
+
|
|
1789
|
+
// resolveBaseRef — validate + resolve a --base <ref> to a commit sha. Injection-
|
|
1790
|
+
// safe: the ref reaches git as a literal argv element (execFile, no shell), never
|
|
1791
|
+
// a string interpolation. Pure over injected git runners so the unit test drives
|
|
1792
|
+
// it with fakes (no real repo). Returns { ok:true, sha } or { ok:false, reason }.
|
|
1793
|
+
function resolveBaseRef(rawBase, { revParse, isAncestor } = {}) {
|
|
1794
|
+
const ref = String(rawBase == null ? '' : rawBase).trim();
|
|
1795
|
+
if (!ref) return { ok: false, reason: 'empty ref' };
|
|
1796
|
+
// Conservative allowlist (defence-in-depth even though transport is argv): no
|
|
1797
|
+
// whitespace, no leading dash (git would read it as a flag), no shell metachar.
|
|
1798
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._/~^-]*$/.test(ref)) {
|
|
1799
|
+
return { ok: false, reason: `invalid ref syntax "${ref}"` };
|
|
1800
|
+
}
|
|
1801
|
+
const rp = revParse || ((r) => argvOut('git', ['rev-parse', '--verify', '--quiet', `${r}^{commit}`]));
|
|
1802
|
+
const sha = rp(ref);
|
|
1803
|
+
if (!sha) return { ok: false, reason: `"${ref}" is not a commit this repo knows` };
|
|
1804
|
+
const anc = isAncestor || ((s) => gitArgvOk(['merge-base', '--is-ancestor', s, 'HEAD']));
|
|
1805
|
+
if (!anc(sha)) {
|
|
1806
|
+
return { ok: false, reason: `"${ref}" (${sha.slice(0, 9)}) is not an ancestor of HEAD — --base must be a commit your work built on top of` };
|
|
1807
|
+
}
|
|
1808
|
+
return { ok: true, sha };
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// baseRefShipGuard — the SECURITY invariant behind --base. --base decouples the
|
|
1812
|
+
// GRADED diff (<base>...HEAD) from what the merge leg lands (the whole branch:
|
|
1813
|
+
// autoMerge runs `git merge <branch>`, bringing every commit ahead of main, not
|
|
1814
|
+
// just <base>...HEAD). So if HEAD has commits AHEAD of origin/main, honoring
|
|
1815
|
+
// --base would let the grader review only a SUBSET while the full branch merges —
|
|
1816
|
+
// an unreviewed-code path onto main. --base is therefore ONLY valid for work
|
|
1817
|
+
// ALREADY fully on main (its documented premise: "nothing left to merge"). Pure
|
|
1818
|
+
// over the ahead-count string (git rev-list --count origin/main..HEAD) so it is
|
|
1819
|
+
// unit-tested without a repo. '0' → allowed; anything else (or null) → refuse.
|
|
1820
|
+
function baseRefShipGuard(aheadCount) {
|
|
1821
|
+
if (aheadCount === '0') return { ok: true };
|
|
1822
|
+
const n = aheadCount == null ? 'an unknown number of' : aheadCount;
|
|
1823
|
+
return { ok: false, reason: `HEAD is ${n} commit(s) ahead of origin/main — --base is only for work already merged to main` };
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
// applyBaseOverride — the SINGLE source of truth for --base validation, shared by
|
|
1827
|
+
// shipMain + regradeMain so the security guard can never diverge between entry
|
|
1828
|
+
// points. Resolves the ref, enforces the 0-ahead invariant, and sets the module-
|
|
1829
|
+
// level baseOverrideSha on success. Returns { ok:true, sha } | { ok:false, reason }
|
|
1830
|
+
// (the caller prints + exits). runGit is injectable for tests.
|
|
1831
|
+
function applyBaseOverride(baseRefRaw, { runGit = gitOk } = {}) {
|
|
1832
|
+
const r = resolveBaseRef(baseRefRaw);
|
|
1833
|
+
if (!r.ok) return { ok: false, reason: `--base ${baseRefRaw} is unusable (${r.reason})` };
|
|
1834
|
+
// Freshen origin/main (best-effort; a stale ref only OVER-counts → errs to refuse).
|
|
1835
|
+
runGit('git fetch origin main --quiet');
|
|
1836
|
+
const guard = baseRefShipGuard(runGit('git rev-list --count origin/main..HEAD'));
|
|
1837
|
+
if (!guard.ok) return { ok: false, reason: guard.reason };
|
|
1838
|
+
baseOverrideSha = r.sha;
|
|
1839
|
+
return { ok: true, sha: r.sha };
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1588
1842
|
// #536: the grader's baseline is computed INDEPENDENTLY of touches[] — always
|
|
1589
1843
|
// from merge-base, never gated on the declared scope. (Historically the
|
|
1590
1844
|
// touches-gated drift scanner returned a null baseline for empty touches[],
|
|
1591
1845
|
// which once blinded the grader to a fully-committed diff and hard-FAILed
|
|
1592
1846
|
// empty-touches newcomer ships; the scanner is gone as of task 879, but the
|
|
1593
1847
|
// grader keeping its own merge-base baseline remains the correct invariant.)
|
|
1594
|
-
|
|
1848
|
+
// baseSha (task 1002422): a validated --base override wins over merge-base, so a
|
|
1849
|
+
// task whose commits already landed on main still grades its real diff instead of
|
|
1850
|
+
// seeing an empty one.
|
|
1851
|
+
function computeGraderBaseline(claim, baseSha = baseOverrideSha) {
|
|
1852
|
+
if (baseSha) return baseSha;
|
|
1595
1853
|
return (
|
|
1596
1854
|
gitOk('git merge-base origin/main HEAD') ||
|
|
1597
1855
|
gitOk('git merge-base main HEAD') ||
|
|
@@ -1679,7 +1937,7 @@ async function runSubagentGrader({ task, valueSummary, baseline, changedFiles: p
|
|
|
1679
1937
|
// fail-closed layers).
|
|
1680
1938
|
let builderRank;
|
|
1681
1939
|
try {
|
|
1682
|
-
const meRes = await (await cliClient()).me.getMe();
|
|
1940
|
+
const meRes = noteServerReward(await (await cliClient()).me.getMe());
|
|
1683
1941
|
if (meRes.ok) builderRank = meRes.data?.builder?.rank;
|
|
1684
1942
|
} catch (_) { /* unknown rank → pre-pass fails open; audit + hook still gate */ }
|
|
1685
1943
|
const runOpts = {
|
|
@@ -1860,6 +2118,23 @@ function regenerateApiDocs() {
|
|
|
1860
2118
|
// (not fail-if-forgotten): before this, ship regenerated the spec but not the client,
|
|
1861
2119
|
// so a route change left the client stale until the api_client test caught it in CI.
|
|
1862
2120
|
// Non-fatal + safe on every ship path; the api_client freshness test backstops main.
|
|
2121
|
+
|
|
2122
|
+
// Where the devbox Electron app vendors the CJS client (ADR 0045). It lives inside the
|
|
2123
|
+
// dev-box MODULE (ADR 0145) — an instance that ships without that module has no copy.
|
|
2124
|
+
const VENDORED_CLIENT_REL = 'modules/dev-box/app/src/vendor/bongos-client.cjs';
|
|
2125
|
+
|
|
2126
|
+
// The git pathspec for the regenerated client. Pure + exported so the rule is unit-tested
|
|
2127
|
+
// with no git/fs (task 1002417). `git status` tolerates an unmatched pathspec (exit 0) but
|
|
2128
|
+
// `git add` aborts the ENTIRE staging step (exit 128), which leaves the regenerated client
|
|
2129
|
+
// unstaged and the worktree dirty — and a dirty worktree makes the ship's auto-merge bail,
|
|
2130
|
+
// stranding the task at `confirmed` with no PR. So the vendored copy is named ONLY when it
|
|
2131
|
+
// is actually on disk.
|
|
2132
|
+
function apiClientPathspec({ worktreeRoot, existsSync = fs.existsSync } = {}) {
|
|
2133
|
+
const paths = ['clients/bongos-client'];
|
|
2134
|
+
if (existsSync(path.join(worktreeRoot, VENDORED_CLIENT_REL))) paths.push(VENDORED_CLIENT_REL);
|
|
2135
|
+
return paths;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
1863
2138
|
function regenerateApiClient() {
|
|
1864
2139
|
console.log(' regenerating typed API client (clients/bongos-client) from the OpenAPI spec...');
|
|
1865
2140
|
if (!shellOk(`"${process.execPath}" ${JSON.stringify(path.join(__dirname, 'gen-api-client.js'))}`)) {
|
|
@@ -1868,23 +2143,28 @@ function regenerateApiClient() {
|
|
|
1868
2143
|
}
|
|
1869
2144
|
// The devbox Electron app can't require() clients/ at runtime, so it vendors the CJS
|
|
1870
2145
|
// build (ADR 0045); keep the copy byte-identical (the api_client test asserts it).
|
|
1871
|
-
// Present only
|
|
2146
|
+
// Present only where the dev-box module ships — an instance without it has no vendored
|
|
2147
|
+
// copy, so both the vendor step and the git pathspec below drop out (task 1002417: the
|
|
2148
|
+
// app moved from top-level devbox-app/ to modules/dev-box/app/ per ADR 0145, and these
|
|
2149
|
+
// paths were not updated, so the guard below silently never fired).
|
|
1872
2150
|
// Resolve it WORKTREE-LOCALLY (like the generator above, __dirname=scripts/gds), NOT
|
|
1873
2151
|
// via REPO_ROOT: on a ship from a linked worktree REPO_ROOT is the MAIN checkout, so a
|
|
1874
2152
|
// REPO_ROOT path would vendor the main tree while gen-api-client just wrote THIS tree,
|
|
1875
2153
|
// leaving this worktree's vendored copy stale → the api_client byte-identity test reds
|
|
1876
2154
|
// the PR (task 2060 fix). __dirname keeps the generator, the vendored file, and the
|
|
1877
2155
|
// git add all on the same tree.
|
|
1878
|
-
const
|
|
2156
|
+
const worktreeRoot = path.join(__dirname, '..', '..');
|
|
2157
|
+
const vendorScript = path.join(worktreeRoot, 'modules', 'dev-box', 'app', 'scripts', 'vendor-client.js');
|
|
1879
2158
|
if (fs.existsSync(vendorScript)) {
|
|
1880
2159
|
shellOk(`"${process.execPath}" ${JSON.stringify(vendorScript)}`);
|
|
1881
2160
|
}
|
|
1882
|
-
const
|
|
2161
|
+
const clientPathspec = apiClientPathspec({ worktreeRoot }).join(' ');
|
|
2162
|
+
const dirty = gitOk(`git status --porcelain ${clientPathspec}`);
|
|
1883
2163
|
if (!dirty) {
|
|
1884
2164
|
console.log(' API client already current — nothing to commit');
|
|
1885
2165
|
return 'current';
|
|
1886
2166
|
}
|
|
1887
|
-
shellOk(
|
|
2167
|
+
shellOk(`git add ${clientPathspec}`);
|
|
1888
2168
|
if (shellOk('git commit -m "chore(api-client): auto-regenerate typed client + devbox vendor (task 2051)"')) {
|
|
1889
2169
|
console.log(' committed auto-regenerated API client');
|
|
1890
2170
|
return 'committed';
|
|
@@ -2129,26 +2409,48 @@ async function autoMerge(taskId, valueSummary, { dbOnly = false } = {}) {
|
|
|
2129
2409
|
return false;
|
|
2130
2410
|
}
|
|
2131
2411
|
|
|
2132
|
-
|
|
2133
|
-
//
|
|
2134
|
-
//
|
|
2135
|
-
//
|
|
2136
|
-
//
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
console.log('
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2412
|
+
// Deploy step. The core carries NO deploy target of its own — under the
|
|
2413
|
+
// standalone model a core change reaches a live instance via package-core +
|
|
2414
|
+
// `bongos upgrade` (pin-bump), NOT an SSH deploy (ADR 0108). So unless an
|
|
2415
|
+
// instance whose own checkout IS its live server has configured a target
|
|
2416
|
+
// (config/deploy.json "sshTarget" or <PREFIX>_DEPLOY_SSH_TARGET), the land is
|
|
2417
|
+
// complete at "merged + pushed to main" and the SSH deploy is skipped. (This
|
|
2418
|
+
// used to SSH a hardcoded target — the founder instance's old game box, i.e. the
|
|
2419
|
+
// WRONG server post-migration. task 1002202.)
|
|
2420
|
+
const { sshTarget, healthzUrl } = resolveDeployTarget({
|
|
2421
|
+
configRaw: readDeployConfigRaw(),
|
|
2422
|
+
branding: safeBranding(),
|
|
2423
|
+
});
|
|
2424
|
+
if (!sshTarget) {
|
|
2425
|
+
console.log(' merged + pushed to main. No deploy target configured, so no SSH deploy —');
|
|
2426
|
+
console.log(' expected for the core. To publish to a live instance run the standalone release');
|
|
2427
|
+
console.log(' flow: package-core → bongos upgrade (pin-bump), per ADR 0108. (Not an SSH deploy.)');
|
|
2428
|
+
} else {
|
|
2429
|
+
console.log(` triggering deploy on ${sshTarget}...`);
|
|
2430
|
+
// Quote ~/deploy.sh so the LOCAL shell doesn't tilde-expand it before ssh
|
|
2431
|
+
// transmits the command; with the quotes ssh sends the literal `~/deploy.sh`
|
|
2432
|
+
// and the REMOTE shell expands it. (Matches /merge-mode SKILL.md:81.)
|
|
2433
|
+
if (!shellOk(`ssh -o ConnectTimeout=10 ${sshTarget} "~/deploy.sh"`)) {
|
|
2434
|
+
// task 984 / idea 180: ~/deploy.sh's internal /healthz curl can race the
|
|
2435
|
+
// service-restart window (Node not yet bound) and exit non-zero even though
|
|
2436
|
+
// the deploy succeeded — which used to false-fail the ship and force a
|
|
2437
|
+
// needless /merge-mode. Before concluding failure, re-probe the CONFIGURED
|
|
2438
|
+
// /healthz with backoff (curl's own --retry; cross-platform, no sleep) —
|
|
2439
|
+
// never a hardcoded host. --retry-all-errors retries connection-refused
|
|
2440
|
+
// during the bind window; -f makes a non-200 an error so a 502/503 also
|
|
2441
|
+
// retries. If it comes up within the window the deploy really succeeded.
|
|
2442
|
+
if (healthzUrl) {
|
|
2443
|
+
console.log(' auto-merge: ~/deploy.sh returned non-zero — re-probing /healthz with backoff before concluding...');
|
|
2444
|
+
if (shellOk(`curl -fsS --max-time 10 --retry 5 --retry-delay 3 --retry-all-errors ${JSON.stringify(healthzUrl)} -o /dev/null`)) {
|
|
2445
|
+
console.log(' auto-merge: /healthz 200 on re-probe — deploy actually succeeded (deploy.sh raced the restart window). Proceeding.');
|
|
2446
|
+
} else {
|
|
2447
|
+
console.log(' auto-merge: deploy failed (/healthz never returned 200 after retries); task stays at confirmed');
|
|
2448
|
+
return false;
|
|
2449
|
+
}
|
|
2450
|
+
} else {
|
|
2451
|
+
console.log(' auto-merge: ~/deploy.sh returned non-zero and no healthzUrl configured to re-probe; task stays at confirmed');
|
|
2452
|
+
return false;
|
|
2453
|
+
}
|
|
2152
2454
|
}
|
|
2153
2455
|
}
|
|
2154
2456
|
|
|
@@ -2344,7 +2646,7 @@ function formatGradeOutcome(g, taskId) {
|
|
|
2344
2646
|
out.push('When the grader is reachable again, re-grade:');
|
|
2345
2647
|
out.push(` node scripts/gds/ship.js ${taskId} --regrade`);
|
|
2346
2648
|
out.push('Or land it now via manual-confirm:');
|
|
2347
|
-
out.push(` curl -X POST -H "Authorization: Bearer <token>"
|
|
2649
|
+
out.push(` curl -X POST -H "Authorization: Bearer <token>" ${resolveInstanceBase() || '<instance-origin>'}/api/gds/tasks/${taskId}/confirm`);
|
|
2348
2650
|
return out.join('\n');
|
|
2349
2651
|
}
|
|
2350
2652
|
|
|
@@ -2371,7 +2673,7 @@ function formatGradeOutcome(g, taskId) {
|
|
|
2371
2673
|
out.push('Grade failed — task stays at completed. Fix the issues above, commit, then re-grade:');
|
|
2372
2674
|
out.push(` node scripts/gds/ship.js ${taskId} --regrade`);
|
|
2373
2675
|
out.push('Last resort (only if the grade was wrong):');
|
|
2374
|
-
out.push(` curl -X POST -H "Authorization: Bearer <token>"
|
|
2676
|
+
out.push(` curl -X POST -H "Authorization: Bearer <token>" ${resolveInstanceBase() || '<instance-origin>'}/api/gds/tasks/${taskId}/confirm`);
|
|
2375
2677
|
}
|
|
2376
2678
|
return out.join('\n');
|
|
2377
2679
|
}
|
|
@@ -2391,7 +2693,14 @@ function printCreditsAndAchievements({ postGradeStatus, credits, creditsRaw, cre
|
|
|
2391
2693
|
if (postGradeStatus === 'completed') {
|
|
2392
2694
|
console.log(`Credits awarded: ${credits} (none — task at completed, grade not passed)`);
|
|
2393
2695
|
} else if (postGradeStatus === 'confirmed') {
|
|
2394
|
-
console.log(`Credits awarded: ${credits} (
|
|
2696
|
+
console.log(`Credits awarded: ${credits} (none yet — credits land when the task merges to main)`);
|
|
2697
|
+
} else if (!rewardPolicy(serverReward).perTaskCredit) {
|
|
2698
|
+
// cost-plus-only (ADR 0146): the per-task estimate is never paid, so this is
|
|
2699
|
+
// 0 BY DESIGN. Left unexplained it reads as a broken payout — it sent a real
|
|
2700
|
+
// session diagnosing the reward gate on 2026-07-26 (task 1002469).
|
|
2701
|
+
console.log(`Credits awarded: ${credits} — expected: this instance pays cost-plus only.`);
|
|
2702
|
+
console.log(" The task's estimate is a guide, never a payout. You're paid for this session's");
|
|
2703
|
+
console.log(' real cost + 20%, booked separately from your token spend.');
|
|
2395
2704
|
} else {
|
|
2396
2705
|
console.log(`Credits awarded: ${credits}`);
|
|
2397
2706
|
}
|
|
@@ -2526,7 +2835,7 @@ async function postShipSync(taskId, postGradeStatus) {
|
|
|
2526
2835
|
// read make the script exit non-zero after a successful ship.
|
|
2527
2836
|
async function onboardingNudgeBestEffort() {
|
|
2528
2837
|
try {
|
|
2529
|
-
const meRes = await (await cliClient()).me.getMe();
|
|
2838
|
+
const meRes = noteServerReward(await (await cliClient()).me.getMe());
|
|
2530
2839
|
if (meRes.ok) {
|
|
2531
2840
|
console.log('');
|
|
2532
2841
|
printOnboardingNudge(meRes.data);
|
|
@@ -2671,7 +2980,7 @@ async function printCompletionCard({ taskId, postGradeStatus, card, usage }) {
|
|
|
2671
2980
|
let total = null;
|
|
2672
2981
|
let widgetsEnabled = true;
|
|
2673
2982
|
try {
|
|
2674
|
-
const me = await (await cliClient()).me.getMe();
|
|
2983
|
+
const me = noteServerReward(await (await cliClient()).me.getMe());
|
|
2675
2984
|
if (me.ok && me.data) {
|
|
2676
2985
|
if (me.data.builder) total = me.data.builder.total_credits;
|
|
2677
2986
|
if (me.data.render) widgetsEnabled = me.data.render.widgets_enabled !== false;
|
|
@@ -2717,7 +3026,7 @@ async function printCompletionCard({ taskId, postGradeStatus, card, usage }) {
|
|
|
2717
3026
|
added,
|
|
2718
3027
|
removed,
|
|
2719
3028
|
grade: card.grade || null,
|
|
2720
|
-
credits: { awarded: card.creditsAwarded, total },
|
|
3029
|
+
credits: { awarded: card.creditsAwarded, total, perTaskCredit: rewardPolicy(serverReward).perTaskCredit },
|
|
2721
3030
|
usage: { tokens, costUsd, newTokens, cachedTokens },
|
|
2722
3031
|
deploy: { commit },
|
|
2723
3032
|
};
|
|
@@ -2742,6 +3051,20 @@ async function printCompletionCard({ taskId, postGradeStatus, card, usage }) {
|
|
|
2742
3051
|
} catch (_) {
|
|
2743
3052
|
/* a summary card must never fail a ship */
|
|
2744
3053
|
}
|
|
3054
|
+
|
|
3055
|
+
// task 1002521: the card above says SHIPPED, which means "merged" — it does
|
|
3056
|
+
// NOT mean users can see it. Publishing is manual and owner-gated, so a ship
|
|
3057
|
+
// can land on main and change nothing live, and until now nothing said so.
|
|
3058
|
+
// Blocker 1000097: 171 commits (a whole governance system) sat unreleased for
|
|
3059
|
+
// ten days while every layer reported success. Warn, never block — a shipped
|
|
3060
|
+
// task is still shipped, and an offline builder must not be punished for it.
|
|
3061
|
+
try {
|
|
3062
|
+
const { check, report } = require('./deploy-divergence');
|
|
3063
|
+
const r = await check();
|
|
3064
|
+
if (!r.undetermined && !r.ok) report(r);
|
|
3065
|
+
} catch (_) {
|
|
3066
|
+
/* a divergence warning must never fail a ship */
|
|
3067
|
+
}
|
|
2745
3068
|
}
|
|
2746
3069
|
|
|
2747
3070
|
// Decide whether a builder may re-grade a task via `--regrade` (#678). Pure so
|
|
@@ -2882,7 +3205,7 @@ async function shipMain() {
|
|
|
2882
3205
|
const args = process.argv.slice(2);
|
|
2883
3206
|
const taskId = Number(args[0]);
|
|
2884
3207
|
if (!Number.isFinite(taskId)) {
|
|
2885
|
-
console.error('usage: node scripts/gds/ship.js <task-id> --notes "..." --summary "..." [--surprise "text" | --surprise none | --no-surprise] [--no-merge] [--db-only] [--verified] [--allow-empty]');
|
|
3208
|
+
console.error('usage: node scripts/gds/ship.js <task-id> --notes "..." --summary "..." [--surprise "text" | --surprise none | --no-surprise] [--no-merge] [--db-only] [--verified] [--allow-empty] [--base <ref>]');
|
|
2886
3209
|
process.exit(2);
|
|
2887
3210
|
}
|
|
2888
3211
|
const notes = arg('--notes', args) || '';
|
|
@@ -2891,7 +3214,12 @@ async function shipMain() {
|
|
|
2891
3214
|
const force = args.includes('--force');
|
|
2892
3215
|
// task 391 (idea #37): a deliberate zero-commit / direct-SQL ship. Without it,
|
|
2893
3216
|
// a branch with no commits ahead of main is REFUSED (not silently shipped).
|
|
2894
|
-
|
|
3217
|
+
// task 1002422: --base <ref> also implies db-only — the work is already on main
|
|
3218
|
+
// (that's the whole point of --base), so the merge leg has nothing to merge and
|
|
3219
|
+
// advances straight to shipped. Unlike --allow-empty it does NOT skip the grade:
|
|
3220
|
+
// the pinned baseline gives the grader a real diff (see resolveBaseRef / main).
|
|
3221
|
+
const baseRefRaw = arg('--base', args);
|
|
3222
|
+
const dbOnly = args.includes('--db-only') || args.includes('--allow-empty') || Boolean(baseRefRaw);
|
|
2895
3223
|
// task 1053 (F): explicit attestation that a verification task's verify
|
|
2896
3224
|
// actually passed. Required to confirm a kind='verify' task (the server
|
|
2897
3225
|
// enforces it too); a no-op for every other task.
|
|
@@ -2913,7 +3241,22 @@ async function shipMain() {
|
|
|
2913
3241
|
const surpriseProvided = surpriseRaw !== null;
|
|
2914
3242
|
const noSurprise = args.includes('--no-surprise');
|
|
2915
3243
|
|
|
2916
|
-
|
|
3244
|
+
// task 1002422 (blocker 1000082): resolve + validate --base BEFORE any heavy
|
|
3245
|
+
// work so a bad ref aborts with the claim STILL ACTIVE. On success it sets the
|
|
3246
|
+
// module-level baseOverrideSha that computeGraderBaseline() reads, so the grader
|
|
3247
|
+
// reads <base>...HEAD (a real diff) and the ship takes the rank-open graded path
|
|
3248
|
+
// instead of the --allow-empty → skip-grade → Archon-only /confirm dead-end.
|
|
3249
|
+
if (baseRefRaw) {
|
|
3250
|
+
const res = applyBaseOverride(baseRefRaw);
|
|
3251
|
+
if (!res.ok) {
|
|
3252
|
+
console.error(`Ship aborted: ${res.reason}. No claim resolved, no credits awarded.`);
|
|
3253
|
+
console.error('Omit --base and ship normally — the grader reviews ALL your commits and the merge lands them. Your claim is still active.');
|
|
3254
|
+
process.exit(2);
|
|
3255
|
+
}
|
|
3256
|
+
console.log(`--base: grading ${res.sha.slice(0, 9)}...HEAD — the task's real committed diff (work already on main). Graded via the rank-open path; no --allow-empty needed.`);
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
const me = noteServerReward(await (await cliClient()).me.getMe());
|
|
2917
3260
|
if (!me.ok) {
|
|
2918
3261
|
console.error('failed to load profile:', me.data);
|
|
2919
3262
|
process.exit(1);
|
|
@@ -3382,17 +3725,31 @@ async function regradeMain() {
|
|
|
3382
3725
|
const args = process.argv.slice(2);
|
|
3383
3726
|
const taskId = Number(args[0]);
|
|
3384
3727
|
if (!Number.isFinite(taskId)) {
|
|
3385
|
-
console.error('usage: node scripts/gds/ship.js <task-id> --regrade [--summary "..."] [--no-merge] [--force] [--verified]');
|
|
3728
|
+
console.error('usage: node scripts/gds/ship.js <task-id> --regrade [--summary "..."] [--no-merge] [--force] [--verified] [--base <ref>]');
|
|
3386
3729
|
process.exit(2);
|
|
3387
3730
|
}
|
|
3388
3731
|
const summaryArg = arg('--summary', args) || '';
|
|
3389
3732
|
const skipMerge = args.includes('--no-merge');
|
|
3390
3733
|
const force = args.includes('--force');
|
|
3391
|
-
|
|
3734
|
+
// task 1002422: honor --base on regrade/resume too, so `--regrade --base <ref>`
|
|
3735
|
+
// and shipMain's resume-grade dispatch grade the real <base>...HEAD diff and
|
|
3736
|
+
// advance already-on-main work, instead of aborting "nothing to grade". Same
|
|
3737
|
+
// 0-ahead security guard as shipMain (applyBaseOverride is the shared gate).
|
|
3738
|
+
const baseRefRaw = arg('--base', args);
|
|
3739
|
+
const dbOnly = args.includes('--db-only') || args.includes('--allow-empty') || Boolean(baseRefRaw); // task 391 — honor --db-only on a regrade too
|
|
3392
3740
|
const verified = args.includes('--verified'); // task 1053 (F) — see gate below
|
|
3393
3741
|
|
|
3742
|
+
if (baseRefRaw) {
|
|
3743
|
+
const res = applyBaseOverride(baseRefRaw);
|
|
3744
|
+
if (!res.ok) {
|
|
3745
|
+
console.error(`Regrade aborted: ${res.reason}. Omit --base and re-grade normally.`);
|
|
3746
|
+
process.exit(2);
|
|
3747
|
+
}
|
|
3748
|
+
console.log(`--base: re-grading ${res.sha.slice(0, 9)}...HEAD (work already on main).`);
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3394
3751
|
// ---- Eligibility: completed + (owner | archon) ----
|
|
3395
|
-
const me = await (await cliClient()).me.getMe();
|
|
3752
|
+
const me = noteServerReward(await (await cliClient()).me.getMe());
|
|
3396
3753
|
if (!me.ok) {
|
|
3397
3754
|
console.error('failed to load profile:', me.data);
|
|
3398
3755
|
process.exit(1);
|
|
@@ -3517,6 +3874,14 @@ async function regradeMain() {
|
|
|
3517
3874
|
// exported: it does I/O + network and is covered by the live smoke at ship.)
|
|
3518
3875
|
module.exports = {
|
|
3519
3876
|
scrubSecrets, snippetOf, buildSessionDigest, resolveTranscriptPath,
|
|
3877
|
+
// task 1002471: the project-path encoder, exported pure so the win32 shapes
|
|
3878
|
+
// (drive colon, backslash separators, spaces) are pinned cross-platform —
|
|
3879
|
+
// resolveTranscriptPath itself runs path.resolve, which can't carry a Windows
|
|
3880
|
+
// path through a Linux CI run.
|
|
3881
|
+
encodeProjectPath,
|
|
3882
|
+
// task 1002417: the regenerated-client pathspec rule, exported pure so the
|
|
3883
|
+
// "never name a path git add cannot match" invariant is tested without git/fs.
|
|
3884
|
+
apiClientPathspec, VENDORED_CLIENT_REL,
|
|
3520
3885
|
// task 1003: transcript-grade scrubber internals, exported pure so the planted-
|
|
3521
3886
|
// secret corpus test (tests/scrubber_corpus.mjs) can exercise each layer + tune.
|
|
3522
3887
|
shannonEntropy, looksHighEntropySecret, redactHighEntropy, collectEnvSecrets,
|
|
@@ -3524,7 +3889,7 @@ module.exports = {
|
|
|
3524
3889
|
// #678: re-grade path + the grade-step helpers it shares with shipMain.
|
|
3525
3890
|
// Exporting these formalizes learning #43 (which had to "replicate ~10
|
|
3526
3891
|
// lines" because runSubagentGrader wasn't reachable).
|
|
3527
|
-
evaluateRegradeEligibility, computeGraderBaseline, computeChangedFiles,
|
|
3892
|
+
evaluateRegradeEligibility, computeGraderBaseline, computeChangedFiles, resolveBaseRef, baseRefShipGuard,
|
|
3528
3893
|
runSubagentGrader, buildGradePayload,
|
|
3529
3894
|
// V4.R08: diff fingerprint + grade-reuse helpers — exported pure so the
|
|
3530
3895
|
// truth table (same hash+pass→reuse, fail→no reuse, mismatch→re-grade,
|
|
@@ -3548,7 +3913,12 @@ module.exports = {
|
|
|
3548
3913
|
gitArgvOk, buildMergeArgv, sanitizeGitHubRefs,
|
|
3549
3914
|
// #679 / ADR 0042: CI deploy mode — pure helpers exported for the regression
|
|
3550
3915
|
// test (mode precedence/safe-default, no-shell PR argv, deploy-run match).
|
|
3551
|
-
|
|
3916
|
+
// task 1002202: resolveDeployTarget — SSH target / healthz precedence + the safe
|
|
3917
|
+
// "no target" default (the core has none), exported for the same reason.
|
|
3918
|
+
// task 1002232: resolveInstanceBase — the ci-land/guidance origin precedence
|
|
3919
|
+
// (env → session api_base → branding publicOrigin → null), pure over injected
|
|
3920
|
+
// inputs so the precedence + the "no hardcoded host" guarantee are unit-tested.
|
|
3921
|
+
parseDeployMode, resolveDeployTarget, resolveInstanceBase, buildPrCreateArgv, buildPrMergeArgv, deployRunSucceeded,
|
|
3552
3922
|
// task 1049 (B): pure CI-land outcome classifier — a timed-out-but-armed PR is
|
|
3553
3923
|
// 'pending' (success), not failure. Exported so the semantics are unit-tested.
|
|
3554
3924
|
classifyLandOutcome,
|
|
@@ -3567,6 +3937,10 @@ module.exports = {
|
|
|
3567
3937
|
pickStaleGateReviewRun,
|
|
3568
3938
|
// task 1068: pure pre-stage-merge decision (skip on main/current, else merge).
|
|
3569
3939
|
decidePreMerge,
|
|
3940
|
+
// task 1002475: pure `git log --diff-filter=D` output parser behind
|
|
3941
|
+
// preflight's upstream-deletion warning — exported so it's unit-tested
|
|
3942
|
+
// without a live git repo.
|
|
3943
|
+
parseDeletedUpstreamPaths,
|
|
3570
3944
|
// task 1052 (E): box-aware "what to do next" for a confirmed-strand — a box
|
|
3571
3945
|
// (via 'server') is pointed at the resume, never at gh-only /merge-mode.
|
|
3572
3946
|
strandNextStep,
|