@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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/scripts/gds/abandon.js
CHANGED
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
// work another session already shipped — instead of leaving it in 'ready' as
|
|
11
11
|
// claimable noise.
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
// (immutable history) and on a task with an
|
|
15
|
-
// idempotent on an already-abandoned task. The
|
|
16
|
-
// audit_log by the server's writing middleware, so
|
|
13
|
+
// Metic+ (the route is requireRank('metic', 'archon') as of ADR 0152; Archon-only
|
|
14
|
+
// before that). Refuses on a shipped task (immutable history) and on a task with an
|
|
15
|
+
// active claim (release it first); idempotent on an already-abandoned task. The
|
|
16
|
+
// abandon + reason are recorded in audit_log by the server's writing middleware, so
|
|
17
|
+
// the ledger stays complete. Retired the wrong task? POST /tasks/<id>/promote
|
|
18
|
+
// restores it to backlog at the same rank (db.restoreTask) — no Archon SQL needed.
|
|
17
19
|
|
|
18
20
|
const { cliClient, requireSession, arg } = require('./cli-lib');
|
|
19
21
|
|
package/scripts/gds/api.js
CHANGED
|
@@ -18,17 +18,21 @@
|
|
|
18
18
|
// on a 2xx, 1 on any non-2xx or transport error, 2 on bad invocation.
|
|
19
19
|
|
|
20
20
|
const fs = require('node:fs');
|
|
21
|
-
const { cliClient, arg, isBoxScope, printBoxScopeGuidanceAndExit } = require('./cli-lib');
|
|
21
|
+
const { cliClient, arg, stripBom, resolveQuiet, isBoxScope, printBoxScopeGuidanceAndExit } = require('./cli-lib');
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
(async () => {
|
|
25
25
|
const method = (process.argv[2] || '').toUpperCase();
|
|
26
26
|
const urlPath = process.argv[3];
|
|
27
27
|
if (!method || !urlPath || !urlPath.startsWith('/')) {
|
|
28
|
-
console.error("usage: node scripts/gds/api.js <METHOD> <path> [--body '<json>' | --body-file <file|->]");
|
|
28
|
+
console.error("usage: node scripts/gds/api.js <METHOD> <path> [--body '<json>' | --body-file <file|->] [--quiet]");
|
|
29
29
|
process.exit(2);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// --quiet / GDS_QUIET: suppress the stderr status banner below. Default OFF —
|
|
33
|
+
// existing pipelines that scrape stderr for the status line are unaffected.
|
|
34
|
+
const quiet = resolveQuiet();
|
|
35
|
+
|
|
32
36
|
// Resolve an optional JSON request body. `--body-file -` reads stdin (the
|
|
33
37
|
// robust way to pass a body with awkward characters without shell quoting).
|
|
34
38
|
let body = null;
|
|
@@ -36,8 +40,9 @@ const { cliClient, arg, isBoxScope, printBoxScopeGuidanceAndExit } = require('./
|
|
|
36
40
|
const bodyInline = arg('--body');
|
|
37
41
|
try {
|
|
38
42
|
let raw = null;
|
|
39
|
-
if (bodyFile != null)
|
|
40
|
-
|
|
43
|
+
if (bodyFile != null) {
|
|
44
|
+
raw = stripBom(bodyFile === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(bodyFile, 'utf8'));
|
|
45
|
+
} else if (bodyInline != null) raw = bodyInline;
|
|
41
46
|
if (raw != null && raw.trim()) body = JSON.parse(raw);
|
|
42
47
|
} catch (e) {
|
|
43
48
|
console.error(`api: could not read/parse JSON body: ${e.message}`);
|
|
@@ -59,7 +64,7 @@ const { cliClient, arg, isBoxScope, printBoxScopeGuidanceAndExit } = require('./
|
|
|
59
64
|
console.error(`api: request failed: ${e.message}`);
|
|
60
65
|
process.exit(1);
|
|
61
66
|
}
|
|
62
|
-
console.error(`${method} ${urlPath} → ${res.status}`);
|
|
67
|
+
if (!quiet) console.error(`${method} ${urlPath} → ${res.status}`);
|
|
63
68
|
// task 1501: on a dev box the baked session is box-scoped (ADR 0053) and every
|
|
64
69
|
// non-bootstrap call (e.g. /me, /inbox) returns 403 box_scope. We pass
|
|
65
70
|
// allowUnauthenticated above (so a 401 stays a plain error, not the setup
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# scripts/gds/api.ps1 — PowerShell 5.1-safe wrapper around api.js.
|
|
2
|
+
#
|
|
3
|
+
# PS 5.1 wraps a native command's stderr into NativeCommandError records and sets
|
|
4
|
+
# $? false even on a clean exit (api.js always prints its status banner to
|
|
5
|
+
# stderr), so a plain `node scripts/gds/api.js ... 2>&1` looks like a failure when
|
|
6
|
+
# it isn't. This wrapper discards stderr, strips a leading UTF-8 BOM, and writes
|
|
7
|
+
# only the raw JSON to stdout so callers can `| ConvertFrom-Json` it directly.
|
|
8
|
+
#
|
|
9
|
+
# Usage: scripts/gds/api.ps1 <METHOD> <path> [--body '<json>' | --body-file <file>]
|
|
10
|
+
|
|
11
|
+
$apiJs = Join-Path $PSScriptRoot 'api.js'
|
|
12
|
+
$out = & node $apiJs @args 2>$null
|
|
13
|
+
$exitCode = $LASTEXITCODE
|
|
14
|
+
|
|
15
|
+
$text = $out -join "`n"
|
|
16
|
+
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) {
|
|
17
|
+
$text = $text.Substring(1)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if ($exitCode -ne 0) {
|
|
21
|
+
Write-Output "GDS-ERROR: scripts/gds/api.ps1 exited $exitCode"
|
|
22
|
+
exit $exitCode
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Write-Output $text
|
|
26
|
+
exit $exitCode
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/gds/api.sh — Git Bash/Linux passthrough wrapper around api.js.
|
|
3
|
+
# MSYS_NO_PATHCONV avoids Git Bash mangling API paths (e.g. /api/gds/inbox) into
|
|
4
|
+
# Windows paths before node ever sees them.
|
|
5
|
+
export MSYS_NO_PATHCONV=1
|
|
6
|
+
exec node "$(dirname "$0")/api.js" "$@"
|
|
@@ -102,8 +102,14 @@ function downstreamImpact(sha, files) {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
async function fetchShipped(days) {
|
|
105
|
+
// Derive the public read host from branding (statusOrigin → publicOrigin), never a
|
|
106
|
+
// hardcoded founder host (was https://status.amazonprimea.com — task 1002234). The
|
|
107
|
+
// recent-shipped feed is a public endpoint served on the status dashboard host.
|
|
108
|
+
const b = (() => { try { return require('../../src/branding').branding(); } catch (_) { return {}; } })();
|
|
109
|
+
const d = b.domains || {};
|
|
110
|
+
const statusBase = String(d.statusOrigin || d.publicOrigin || '').replace(/\/+$/, '');
|
|
105
111
|
const r = await fetch(
|
|
106
|
-
|
|
112
|
+
`${statusBase}/api/gds/public/recent-shipped?days=${days}&limit=500`
|
|
107
113
|
);
|
|
108
114
|
const data = await r.json();
|
|
109
115
|
return data.tasks || [];
|
|
@@ -44,7 +44,8 @@ const path = require('path');
|
|
|
44
44
|
const os = require('os');
|
|
45
45
|
|
|
46
46
|
const { cliClient, loadSession } = require('./cli-lib');
|
|
47
|
-
const { buildSessionDigest } = require('./ship');
|
|
47
|
+
const { buildSessionDigest, encodeProjectPath } = require('./ship');
|
|
48
|
+
const { looksLikeGraderTranscript } = require('./session-cost');
|
|
48
49
|
|
|
49
50
|
// ---- args ----------------------------------------------------------------
|
|
50
51
|
const ARGV = process.argv.slice(2);
|
|
@@ -68,7 +69,17 @@ const PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
|
|
|
68
69
|
// Project-path families this project has lived under (the cwd is encoded into
|
|
69
70
|
// the dir name by Claude Code; the project was renamed over its life). A dir is
|
|
70
71
|
// in-scope if its name contains any of these tokens.
|
|
72
|
+
//
|
|
73
|
+
// The first token is DERIVED from this checkout, not hardcoded: the legacy names
|
|
74
|
+
// below are the owner's old machine, so on every other clone inScope() matched
|
|
75
|
+
// nothing and the script reported "0 transcripts" while exiting 0 — the failure
|
|
76
|
+
// looked like success. encodeProjectPath is the same encoder Claude Code (and
|
|
77
|
+
// resolveTranscriptPath) uses, so the derived token IS the real dir name, and a
|
|
78
|
+
// per-task worktree dir (<encoded-root>--claude-worktrees-task-N) still matches
|
|
79
|
+
// on the prefix. The legacy tokens stay so the owner's older corpus keeps working.
|
|
80
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
71
81
|
const PROJECT_TOKENS = [
|
|
82
|
+
encodeProjectPath(REPO_ROOT),
|
|
72
83
|
'Offtheboats',
|
|
73
84
|
'Claude-OTB',
|
|
74
85
|
'LarsCode-Amazonprimea',
|
|
@@ -80,11 +91,13 @@ const PROJECT_TOKENS = [
|
|
|
80
91
|
// invocation, a claim/release slash command, or the builder-claim skill's
|
|
81
92
|
// "Claimed #N" confirmation. NOT markdown "#N" links, NOT API list responses
|
|
82
93
|
// (those carry many incidental task_id fields), NOT "/tasks/N/..." paths.
|
|
94
|
+
// Ids are 7 digits on a live instance (1002471); the original {1,4} matched none
|
|
95
|
+
// of them, so every modern session extracted zero task_ids and could never pay.
|
|
83
96
|
const TASK_MARKERS = [
|
|
84
|
-
/\bship\.js\s+(\d{1,
|
|
85
|
-
/\bbuilder-claim\s+(\d{1,
|
|
86
|
-
/\bbuilder-release\s+(\d{1,
|
|
87
|
-
/\bClaimed\s+(?:task\s+)?#(\d{1,
|
|
97
|
+
/\bship\.js\s+(\d{1,7})\b/g, // node scripts/gds/ship.js 1002471 [--flags]
|
|
98
|
+
/\bbuilder-claim\s+(\d{1,7})\b/g, // /builder-claim 1002471
|
|
99
|
+
/\bbuilder-release\s+(\d{1,7})\b/g, // /builder-release 1002471
|
|
100
|
+
/\bClaimed\s+(?:task\s+)?#(\d{1,7})\b/g, // "Claimed #1002471" / "Claimed task #1002471"
|
|
88
101
|
];
|
|
89
102
|
|
|
90
103
|
// Decode the human-and-tool text of one transcript entry: assistant text +
|
|
@@ -119,13 +132,53 @@ function extractTaskIds(entries) {
|
|
|
119
132
|
let m;
|
|
120
133
|
while ((m = re.exec(t)) !== null) {
|
|
121
134
|
const n = parseInt(m[1], 10);
|
|
122
|
-
|
|
135
|
+
// No upper clamp: the old `n < 10000` dropped every 7-digit id a SECOND
|
|
136
|
+
// time, so widening TASK_MARKERS alone would still have extracted nothing.
|
|
137
|
+
//
|
|
138
|
+
// Over-capture cannot pay for work you did not do. The id set here is a
|
|
139
|
+
// REQUEST; the server intersects it with the caller's own claims before
|
|
140
|
+
// storing — modules/sessions/routes/sessions.js calls
|
|
141
|
+
// db.filterTaskIdsClaimedBy(req.builder.id, requestedTaskIds), which is
|
|
142
|
+
// `SELECT DISTINCT task_id FROM claims WHERE builder_id = $1 AND task_id =
|
|
143
|
+
// ANY($2)` (modules/sessions/db.js). builder_id comes from the authenticated
|
|
144
|
+
// caller, never the body, so a forged transcript naming an unclaimed task
|
|
145
|
+
// yields an EMPTY task_ids — and the cost-plus reward is gated on a stored
|
|
146
|
+
// id reaching status='shipped' (ADR 0120), so it can never book.
|
|
147
|
+
// Confirmed empirically on the 2026-07-28 recovery: ids 2, 5, 600, 2615 and
|
|
148
|
+
// 999999 were extracted from quoted docs and every one was dropped server-side.
|
|
149
|
+
if (n > 0) found.add(n);
|
|
123
150
|
}
|
|
124
151
|
}
|
|
125
152
|
}
|
|
126
153
|
return [...found].sort((a, b) => a - b);
|
|
127
154
|
}
|
|
128
155
|
|
|
156
|
+
// buildUploadPayload — the POST /sessions body for one planned session. Pure +
|
|
157
|
+
// exported so the field set is unit-tested; the money rides on `model_usage`.
|
|
158
|
+
//
|
|
159
|
+
// model_usage MUST be top-level. The route promotes it to its own column and
|
|
160
|
+
// deletes it from the metrics jsonb (modules/sessions/routes/sessions.js), so it
|
|
161
|
+
// reads body.model_usage and never looks inside metrics. Omitting it left every
|
|
162
|
+
// backfilled reward on the task-1676 fallback, which prices the whole token total
|
|
163
|
+
// at the opus cache_read rate — deliberately conservative, i.e. an under-payment.
|
|
164
|
+
// Sent exactly as ship.js sends it so a backfilled row prices identically to one
|
|
165
|
+
// written at ship time.
|
|
166
|
+
function buildUploadPayload(p) {
|
|
167
|
+
const digest = (p && p.digest) || {};
|
|
168
|
+
const metrics = digest.metrics || {};
|
|
169
|
+
return {
|
|
170
|
+
session_id: p.sessionId,
|
|
171
|
+
task_ids: p.taskIds,
|
|
172
|
+
total_tokens: digest.totalTokens,
|
|
173
|
+
duration_seconds: digest.durationSeconds,
|
|
174
|
+
started_at: digest.startedAt,
|
|
175
|
+
ended_at: digest.endedAt,
|
|
176
|
+
detail: digest.detail,
|
|
177
|
+
metrics,
|
|
178
|
+
model_usage: metrics.model_usage || {},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
129
182
|
// ---- transcript discovery + parse ----------------------------------------
|
|
130
183
|
function inScope(dirName) {
|
|
131
184
|
return PROJECT_TOKENS.some((tok) => dirName.includes(tok));
|
|
@@ -218,6 +271,7 @@ async function main() {
|
|
|
218
271
|
let skippedPresent = 0;
|
|
219
272
|
let skippedEmpty = 0;
|
|
220
273
|
let skippedActive = 0;
|
|
274
|
+
let skippedGrader = 0;
|
|
221
275
|
for (const file of files) {
|
|
222
276
|
const sessionId = path.basename(file, '.jsonl');
|
|
223
277
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) continue; // server format guard
|
|
@@ -229,6 +283,18 @@ async function main() {
|
|
|
229
283
|
skippedPresent++;
|
|
230
284
|
continue;
|
|
231
285
|
}
|
|
286
|
+
// The ship-time subagent grader panel persists each worker as its OWN
|
|
287
|
+
// top-level transcript in this same project dir (they are the MAJORITY of
|
|
288
|
+
// files here — 36 of 52 in one real main dir, 146 of 151 across worktrees).
|
|
289
|
+
// A worker reads the diff, so it quotes the very ship markers above and would
|
|
290
|
+
// be uploaded as a session that shipped a real task — earning a cost-plus
|
|
291
|
+
// reward the live ship path never pays, for spend already captured by the
|
|
292
|
+
// worker cost envelope. Same exclusion, same reason as session-cost.js's
|
|
293
|
+
// ADR 0028 non-double-count invariant; reuse its detector.
|
|
294
|
+
if (looksLikeGraderTranscript(file)) {
|
|
295
|
+
skippedGrader++;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
232
298
|
const entries = parseEntries(file);
|
|
233
299
|
const digest = buildSessionDigest(entries, { extraSecrets });
|
|
234
300
|
// Skip sessions with no real LLM work. A 0-token digest is either an aborted
|
|
@@ -252,7 +318,8 @@ async function main() {
|
|
|
252
318
|
const totalTasks = plan.reduce((n, p) => n + p.taskIds.length, 0);
|
|
253
319
|
console.log(
|
|
254
320
|
`\nPlan: ${plan.length} new sessions to upload ` +
|
|
255
|
-
`(${skippedPresent} already present, ${skippedEmpty} empty/no-turns skipped` +
|
|
321
|
+
`(${skippedPresent} already present, ${skippedEmpty} empty/no-turns skipped, ` +
|
|
322
|
+
`${skippedGrader} grader-worker transcripts skipped` +
|
|
256
323
|
`${skippedActive ? `, ${skippedActive} current-session skipped` : ''}).`
|
|
257
324
|
);
|
|
258
325
|
console.log(`Task-id associations extracted: ${totalTasks} across ${plan.filter((p) => p.taskIds.length).length} sessions.\n`);
|
|
@@ -283,16 +350,7 @@ async function main() {
|
|
|
283
350
|
const api = await cliClient();
|
|
284
351
|
for (let i = 0; i < capped; i++) {
|
|
285
352
|
const p = plan[i];
|
|
286
|
-
const payload =
|
|
287
|
-
session_id: p.sessionId,
|
|
288
|
-
task_ids: p.taskIds,
|
|
289
|
-
total_tokens: p.digest.totalTokens,
|
|
290
|
-
duration_seconds: p.digest.durationSeconds,
|
|
291
|
-
started_at: p.digest.startedAt,
|
|
292
|
-
ended_at: p.digest.endedAt,
|
|
293
|
-
detail: p.digest.detail,
|
|
294
|
-
metrics: p.digest.metrics,
|
|
295
|
-
};
|
|
353
|
+
const payload = buildUploadPayload(p);
|
|
296
354
|
let attempts = 0;
|
|
297
355
|
for (;;) {
|
|
298
356
|
attempts++;
|
|
@@ -341,7 +399,9 @@ function finish(ok, failed, failures) {
|
|
|
341
399
|
|
|
342
400
|
// Pure helpers are exported for unit testing (matches the ship.js / session-eval
|
|
343
401
|
// convention). The script only executes when run directly, not when required.
|
|
344
|
-
module.exports = {
|
|
402
|
+
module.exports = {
|
|
403
|
+
entryText, extractTaskIds, inScope, buildUploadPayload, TASK_MARKERS, PROJECT_TOKENS,
|
|
404
|
+
};
|
|
345
405
|
|
|
346
406
|
if (require.main === module) {
|
|
347
407
|
main().catch((e) => {
|
package/scripts/gds/backup.js
CHANGED
|
@@ -26,19 +26,41 @@ async function fetchStatus() {
|
|
|
26
26
|
return r.data;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function fmtAge(hours) {
|
|
30
|
+
if (hours == null) return '(unknown age)';
|
|
31
|
+
if (hours < 48) return `${hours.toFixed(1)}h old`;
|
|
32
|
+
return `${Math.floor(hours / 24)} days old`;
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
function printStatus(data) {
|
|
30
36
|
const d = data.latest_dump;
|
|
31
37
|
if (!d) {
|
|
32
38
|
console.log(`Backup dir: ${data.backup_dir}`);
|
|
33
39
|
console.log('No dumps found yet.');
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log(' ⚠️ NO BACKUP EXISTS. There is no restore point for this database.');
|
|
34
42
|
return;
|
|
35
43
|
}
|
|
36
44
|
console.log(`Backup dir: ${data.backup_dir}`);
|
|
37
45
|
console.log(`Latest dump: ${d.filename}`);
|
|
38
|
-
console.log(` Timestamp: ${d.timestamp}`);
|
|
46
|
+
console.log(` Timestamp: ${d.timestamp} (${fmtAge(d.age_hours)})`);
|
|
39
47
|
console.log(` Size: ${fmtBytes(d.size_bytes)}`);
|
|
40
48
|
console.log(` Retained: ${d.count} dump(s)`);
|
|
41
49
|
if (data.trigger_running) console.log(' ⏳ A backup is currently running.');
|
|
50
|
+
// Loud on purpose. A stale backup previously showed as an ordinary timestamp
|
|
51
|
+
// the reader had to date themselves — which is how 13 days of failure went
|
|
52
|
+
// unnoticed on cloudbongos (task 1002479).
|
|
53
|
+
if (d.stale) {
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(` ⚠️ STALE — the newest dump is ${fmtAge(d.age_hours)}, past the ${d.stale_after_hours}h threshold.`);
|
|
56
|
+
console.log(' The nightly backup is probably failing. Check the unit:');
|
|
57
|
+
console.log(' systemctl status gds-db-backup.service');
|
|
58
|
+
console.log(' journalctl -u gds-db-backup.service -n 50 --no-pager');
|
|
59
|
+
console.log(' A 203/EXEC there means the script lost its exec bit — the unit');
|
|
60
|
+
console.log(' should invoke it via `/usr/bin/env bash <script>`.');
|
|
61
|
+
} else {
|
|
62
|
+
console.log(` ✅ Fresh (threshold ${d.stale_after_hours}h).`);
|
|
63
|
+
}
|
|
42
64
|
}
|
|
43
65
|
|
|
44
66
|
async function trigger() {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
const fs = require('node:fs');
|
|
28
28
|
const os = require('node:os');
|
|
29
29
|
const path = require('node:path');
|
|
30
|
+
const crypto = require('node:crypto');
|
|
30
31
|
const { spawnSync } = require('node:child_process');
|
|
31
32
|
const ic = require('../../src/instance-config');
|
|
32
33
|
const { cliClient } = require('./cli-lib');
|
|
@@ -90,13 +91,49 @@ function upsertSshConfigText(existing, { hostname, identityFile }) {
|
|
|
90
91
|
return `${base}${gap}${block}\n`;
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
94
|
+
// Does a known_hosts line name any of `names`? Field 1 is either a plain
|
|
95
|
+
// `host` / `host,ip` LIST, or — with HashKnownHosts on (the Debian/Ubuntu default)
|
|
96
|
+
// — the opaque `|1|<b64 salt>|<b64 hash>` form, hash = HMAC-SHA1(salt) of the name.
|
|
97
|
+
//
|
|
98
|
+
// (This duplicates ssh-setup.knownHostsLineMatchesHost on purpose: the Dev Box app
|
|
99
|
+
// under modules/dev-box/app/ is a separately-packaged subproject with ZERO requires
|
|
100
|
+
// into the repo — see modules/dev-box/CLAUDE.md — so the two cannot share a module.)
|
|
101
|
+
function knownHostsLineNamesAny(line, names) {
|
|
102
|
+
const t = String(line || '').trim();
|
|
103
|
+
if (!t || t.startsWith('#')) return false;
|
|
104
|
+
const field = t.split(/\s+/)[0] || '';
|
|
105
|
+
const wanted = names.filter(Boolean).map(String);
|
|
106
|
+
if (!wanted.length) return false;
|
|
107
|
+
if (!field.startsWith('|1|')) {
|
|
108
|
+
const listed = field.split(',');
|
|
109
|
+
return wanted.some((n) => listed.includes(n));
|
|
110
|
+
}
|
|
111
|
+
const [, , salt, hash] = field.split('|');
|
|
112
|
+
if (!salt || !hash) return false;
|
|
113
|
+
try {
|
|
114
|
+
const key = Buffer.from(salt, 'base64');
|
|
115
|
+
return wanted.some((n) => crypto.createHmac('sha1', key).update(n).digest('base64') === hash);
|
|
116
|
+
} catch (_) {
|
|
117
|
+
return false; // unparseable salt → leave the line alone rather than guess
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Authoritative known_hosts upsert: drop EVERY stale line for this box, keep every
|
|
122
|
+
// other host, then append the box's reported keys. hostKeysRaw is the newline-joined
|
|
123
|
+
// `<keytype> <base64>` string from /box/me; `ip` is optional and drops an IP-keyed
|
|
124
|
+
// line for the same box.
|
|
125
|
+
//
|
|
126
|
+
// It used to compare field 1 to the hostname with `!==`, which missed the two forms
|
|
127
|
+
// OpenSSH actually writes — `host,1.2.3.4` (what `StrictHostKeyChecking=accept-new`
|
|
128
|
+
// records) and the hashed `|1|salt|hash`. Those lines survived the "replace", so
|
|
129
|
+
// every re-pin ACCUMULATED another entry and the client kept verifying against the
|
|
130
|
+
// first, now-wrong one: "Host denied (verification failed)" on a box that had just
|
|
131
|
+
// re-reported valid keys. Comments and blank lines are preserved.
|
|
132
|
+
function buildKnownHostsUpsert(existing, hostname, hostKeysRaw, ip = null) {
|
|
133
|
+
const names = [hostname, ip];
|
|
97
134
|
const keep = (existing || '')
|
|
98
135
|
.split('\n')
|
|
99
|
-
.filter((line) => line.trim() && line
|
|
136
|
+
.filter((line) => line.trim() && !knownHostsLineNamesAny(line, names));
|
|
100
137
|
const added = (hostKeysRaw || '')
|
|
101
138
|
.split('\n')
|
|
102
139
|
.map((k) => k.trim())
|
|
@@ -106,16 +143,35 @@ function buildKnownHostsUpsert(existing, hostname, hostKeysRaw) {
|
|
|
106
143
|
return all.length ? all.join('\n') + '\n' : '';
|
|
107
144
|
}
|
|
108
145
|
|
|
146
|
+
// The on-box re-scope STEP (BV1.R101 / goal 1000051) — no `cd`, the caller
|
|
147
|
+
// positions cwd. Re-runs the box source-fetch so /workspace holds exactly the
|
|
148
|
+
// builder's ACTIVE-claim scope: "claim → open → the scoped folder." box-sync.js
|
|
149
|
+
// re-runs infra/box-source-fetch.sh, which calls GET /box/source-access —
|
|
150
|
+
// claim-driven since R94 — so a fresh claim (or a switch) re-scopes the ONE
|
|
151
|
+
// working folder in place (the owner's one-folder decision). Best-effort (`|| true`):
|
|
152
|
+
// a sync hiccup must never block the connection.
|
|
153
|
+
const RESYNC_STEP = `echo '↪ Syncing your box to your active claim…'; node scripts/gds/box-sync.js || true`;
|
|
154
|
+
|
|
109
155
|
// The remote command `ssh -t root@host …` runs. Lands the builder in /workspace;
|
|
110
|
-
//
|
|
111
|
-
//
|
|
156
|
+
// with `sync` (BV1.R101) it re-scopes that folder to the active claim first, so a
|
|
157
|
+
// builder who just claimed/switched arrives in a folder holding exactly that task's
|
|
158
|
+
// code. On the first connection to this box it then runs an interactive `claude` so
|
|
159
|
+
// the Trusted-Devices /login enrollment (interactive-only by design — ADR 0038) is
|
|
112
160
|
// completed once, then drops to a login shell. Silent on every later connect.
|
|
113
|
-
function buildRemoteShellCommand({ firstRun = false, workdir = WORKSPACE } = {}) {
|
|
161
|
+
function buildRemoteShellCommand({ firstRun = false, sync = false, workdir = WORKSPACE } = {}) {
|
|
114
162
|
const cd = `cd ${workdir} 2>/dev/null || true`;
|
|
163
|
+
const resync = sync ? `${RESYNC_STEP}; ` : '';
|
|
115
164
|
const login = firstRun
|
|
116
165
|
? `echo '↪ First time on this box — complete /login in claude once (browser device flow), then exit to a shell.'; claude || true; `
|
|
117
166
|
: '';
|
|
118
|
-
return `${cd}; ${login}exec bash -l`;
|
|
167
|
+
return `${cd}; ${resync}${login}exec bash -l`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// The full one-shot remote command for `bongos code` (VS Code Remote-SSH opens the
|
|
171
|
+
// folder directly rather than running a login shell, so the re-scope rides a
|
|
172
|
+
// separate one-shot ssh before the window opens): cd + the re-scope step, no shell.
|
|
173
|
+
function buildRemoteResyncCommand({ workdir = WORKSPACE } = {}) {
|
|
174
|
+
return `cd ${workdir} 2>/dev/null || true; ${RESYNC_STEP}`;
|
|
119
175
|
}
|
|
120
176
|
|
|
121
177
|
// VS Code Remote-SSH authority for a host (VS Code resolves it via the ~/.ssh
|
|
@@ -226,32 +282,47 @@ function ensureSshKey({ log = console.log } = {}) {
|
|
|
226
282
|
return { keyPath, pubKey, created };
|
|
227
283
|
}
|
|
228
284
|
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
// the
|
|
232
|
-
//
|
|
233
|
-
//
|
|
285
|
+
// PURE (unit tested): should we POST this key? The old inline version FAILED OPEN —
|
|
286
|
+
// when the fingerprint couldn't be computed (ssh-keygen missing from a leaner PATH)
|
|
287
|
+
// or the key list couldn't be fetched, it decided "don't register" and connected
|
|
288
|
+
// anyway, so a builder whose key was never on the server got a bare "Permission
|
|
289
|
+
// denied" with nothing to act on. Uncertainty now means REGISTER.
|
|
290
|
+
//
|
|
291
|
+
// A redundant POST costs nothing: POST /box/ssh-key upserts by fingerprint
|
|
292
|
+
// (modules/dev-box/routes/box.js — an existing fingerprint is explicitly exempt from
|
|
293
|
+
// MAX_SSH_KEYS_PER_BUILDER), so re-registering can neither add a row nor trip the
|
|
294
|
+
// cap. Do not "optimize" the skip back in.
|
|
295
|
+
function decideKeyRegistration({ created, fingerprint, listedKeys, listFailed } = {}) {
|
|
296
|
+
if (created) return { register: true, why: 'created' };
|
|
297
|
+
if (listFailed || !Array.isArray(listedKeys)) return { register: true, why: 'list_unavailable' };
|
|
298
|
+
if (!fingerprint) return { register: true, why: 'fingerprint_unknown' };
|
|
299
|
+
if (listedKeys.some((k) => k && k.fingerprint === fingerprint)) return { register: false, why: 'already_registered' };
|
|
300
|
+
return { register: true, why: 'not_listed' };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Register the public key with the GDS unless the server already demonstrably has
|
|
304
|
+
// it. Best-effort: a failure here is surfaced as a warning, not fatal (SSH will
|
|
305
|
+
// report a genuine auth problem clearly).
|
|
234
306
|
async function ensureKeyRegistered({ created, keyPath, pubKey, log = console.log } = {}) {
|
|
235
307
|
const api = await cliClient();
|
|
236
|
-
let
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
let listed = null;
|
|
308
|
+
let listedKeys = null;
|
|
309
|
+
let listFailed = false;
|
|
310
|
+
if (!created) {
|
|
240
311
|
try {
|
|
241
312
|
const res = await api.box.getBoxSshKey();
|
|
242
|
-
if (res.ok && res.data && Array.isArray(res.data.keys))
|
|
313
|
+
if (res.ok && res.data && Array.isArray(res.data.keys)) listedKeys = res.data.keys;
|
|
314
|
+
else listFailed = true;
|
|
243
315
|
} catch (_) {
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
if (fp && Array.isArray(listed)) {
|
|
247
|
-
needed = !listed.some((k) => k && k.fingerprint === fp);
|
|
248
|
-
} else if (fp == null && Array.isArray(listed) && listed.length > 0) {
|
|
249
|
-
needed = false; // can't compare, but keys exist → assume registered
|
|
250
|
-
} else {
|
|
251
|
-
needed = false; // no info → don't spam; SSH will surface a real auth failure
|
|
316
|
+
listFailed = true;
|
|
252
317
|
}
|
|
253
318
|
}
|
|
254
|
-
|
|
319
|
+
const { register } = decideKeyRegistration({
|
|
320
|
+
created,
|
|
321
|
+
fingerprint: created ? null : localKeyFingerprint(`${keyPath}.pub`),
|
|
322
|
+
listedKeys,
|
|
323
|
+
listFailed,
|
|
324
|
+
});
|
|
325
|
+
if (!register) return { registered: false };
|
|
255
326
|
try {
|
|
256
327
|
const label = `bongos ${process.platform}`.slice(0, 80);
|
|
257
328
|
const res = await api.box.postBoxSshKey({ body: { public_key: pubKey, label } });
|
|
@@ -364,7 +435,7 @@ function writeSshAlias({ hostname, keyPath, log = console.log }) {
|
|
|
364
435
|
}
|
|
365
436
|
}
|
|
366
437
|
|
|
367
|
-
function pinKnownHosts({ hostname, hostKeys, log = console.log }) {
|
|
438
|
+
function pinKnownHosts({ hostname, hostKeys, ip = null, log = console.log }) {
|
|
368
439
|
if (!hostKeys) return; // box hasn't reported keys yet; accept-new covers first connect
|
|
369
440
|
const khPath = path.join(sshDir(), 'known_hosts');
|
|
370
441
|
let existing = '';
|
|
@@ -373,7 +444,9 @@ function pinKnownHosts({ hostname, hostKeys, log = console.log }) {
|
|
|
373
444
|
} catch (_) {
|
|
374
445
|
/* none yet */
|
|
375
446
|
}
|
|
376
|
-
|
|
447
|
+
// DNS is repointed on every wake, so an old line keyed by the PREVIOUS box's IP
|
|
448
|
+
// is stale too — pass it so the upsert drops it instead of leaving it to match.
|
|
449
|
+
const next = buildKnownHostsUpsert(existing, hostname, hostKeys, ip);
|
|
377
450
|
if (next !== existing) {
|
|
378
451
|
fs.mkdirSync(sshDir(), { recursive: true });
|
|
379
452
|
fs.writeFileSync(khPath, next, { mode: 0o600 });
|
|
@@ -392,11 +465,26 @@ async function prepareConnection({ log = console.log } = {}) {
|
|
|
392
465
|
const { keyPath, pubKey, created } = ensureSshKey({ log });
|
|
393
466
|
await ensureKeyRegistered({ created, keyPath, pubKey, log });
|
|
394
467
|
const box = await ensureBoxActive({ log });
|
|
395
|
-
pinKnownHosts({ hostname: box.hostname, hostKeys: box.host_keys, log });
|
|
468
|
+
pinKnownHosts({ hostname: box.hostname, hostKeys: box.host_keys, ip: box.ip, log });
|
|
396
469
|
writeSshAlias({ hostname: box.hostname, keyPath, log });
|
|
397
470
|
return { box, keyPath };
|
|
398
471
|
}
|
|
399
472
|
|
|
473
|
+
// Re-scope /workspace to the builder's active claim over a one-shot ssh, BEFORE
|
|
474
|
+
// `bongos code` opens the VS Code window (BV1.R101). Best-effort: a failure just
|
|
475
|
+
// means the folder holds its last-synced scope — the builder can /builder-sync
|
|
476
|
+
// inside the VS Code terminal — so it never blocks the open. Returns true on a
|
|
477
|
+
// clean run. Mirrors shell.js's ssh args (explicit key + host-key policy).
|
|
478
|
+
function resyncBoxOverSsh({ host, keyPath, log = console.log }) {
|
|
479
|
+
const args = [
|
|
480
|
+
'-i', keyPath, '-o', 'IdentitiesOnly=yes', '-o', 'StrictHostKeyChecking=accept-new',
|
|
481
|
+
`${BOX_USER}@${host}`, buildRemoteResyncCommand(),
|
|
482
|
+
];
|
|
483
|
+
log(` Scoping ${WORKSPACE} to your active claim…`);
|
|
484
|
+
const r = spawnSync('ssh', args, { stdio: 'inherit' });
|
|
485
|
+
return !!r && !r.error && (r.status === 0 || r.status == null);
|
|
486
|
+
}
|
|
487
|
+
|
|
400
488
|
// First-run marker (per box hostname). /login enrollment persists on the box, so
|
|
401
489
|
// we only force the interactive claude handoff the first time we connect to a
|
|
402
490
|
// given box from this laptop.
|
|
@@ -430,7 +518,11 @@ module.exports = {
|
|
|
430
518
|
buildSshAliasBlock,
|
|
431
519
|
upsertSshConfigText,
|
|
432
520
|
buildKnownHostsUpsert,
|
|
521
|
+
knownHostsLineNamesAny,
|
|
522
|
+
decideKeyRegistration,
|
|
433
523
|
buildRemoteShellCommand,
|
|
524
|
+
buildRemoteResyncCommand, // BV1.R101 — one-shot re-scope for `bongos code`
|
|
525
|
+
RESYNC_STEP,
|
|
434
526
|
remoteAuthority,
|
|
435
527
|
parseEnsure,
|
|
436
528
|
// toolchain / paths
|
|
@@ -446,6 +538,7 @@ module.exports = {
|
|
|
446
538
|
writeSshAlias,
|
|
447
539
|
pinKnownHosts,
|
|
448
540
|
prepareConnection,
|
|
541
|
+
resyncBoxOverSsh, // BV1.R101 — re-scope /workspace before `bongos code` opens
|
|
449
542
|
// first-run login handoff
|
|
450
543
|
loginMarkerPath,
|
|
451
544
|
isFirstLogin,
|