@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
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/box-sync.js — BV1.R96 (goal 1000051): "sync now".
|
|
3
|
+
//
|
|
4
|
+
// Re-run the box's source-fetch ON DEMAND so a freshly-claimed task's code
|
|
5
|
+
// materializes within seconds, instead of waiting for the ~10-min cron (which
|
|
6
|
+
// stays the backstop). Because the box's fetch calls GET /box/source-access —
|
|
7
|
+
// which since BV1.R94 returns the sparse set for the builder's ACTIVE CLAIM
|
|
8
|
+
// (task/module scope, ADR 0148) — re-running it RE-SCOPES the box to whatever the
|
|
9
|
+
// builder just claimed. So `/builder-claim N` then `box-sync` = the box holds
|
|
10
|
+
// exactly task N's module code, seconds later.
|
|
11
|
+
//
|
|
12
|
+
// Runs ON THE BOX, where infra/box-source-fetch.sh lives (it is in the box's BASE
|
|
13
|
+
// sparse set). OFF a box there is no fetch script — the source IS the local
|
|
14
|
+
// checkout — so it says so plainly and exits non-zero rather than failing opaquely.
|
|
15
|
+
//
|
|
16
|
+
// Usage: node scripts/gds/box-sync.js # re-fetch now
|
|
17
|
+
// node scripts/gds/box-sync.js --dry-run # print what it WOULD run
|
|
18
|
+
|
|
19
|
+
const fs = require('node:fs');
|
|
20
|
+
const path = require('node:path');
|
|
21
|
+
const { spawnSync } = require('node:child_process');
|
|
22
|
+
|
|
23
|
+
// Reuse box.js's instance-root-first infra resolver (ADR 0150 / R98) so this
|
|
24
|
+
// locates infra/ the SAME way provisioning does — the box's fetch script lives
|
|
25
|
+
// beside the cloud-init template it validates on.
|
|
26
|
+
const { resolveBoxInfraDir } = require('./box.js');
|
|
27
|
+
|
|
28
|
+
const FETCH_SCRIPT = 'box-source-fetch.sh';
|
|
29
|
+
|
|
30
|
+
// Absolute path to the box source-fetch script, or null when it can't be found
|
|
31
|
+
// (i.e. not on a box). Pure: opts thread through to resolveBoxInfraDir for tests.
|
|
32
|
+
function resolveFetchScript(opts = {}) {
|
|
33
|
+
const infraDir = resolveBoxInfraDir(opts);
|
|
34
|
+
if (!infraDir) return null;
|
|
35
|
+
const p = path.join(infraDir, FETCH_SCRIPT);
|
|
36
|
+
try { return fs.existsSync(p) ? p : null; } catch { return null; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function main(argv = process.argv.slice(2)) {
|
|
40
|
+
const dryRun = argv.includes('--dry-run') || argv.includes('-n');
|
|
41
|
+
const script = resolveFetchScript();
|
|
42
|
+
if (!script) {
|
|
43
|
+
console.error(
|
|
44
|
+
`sync: no infra/${FETCH_SCRIPT} found. This runs ON a dev box (where the box `
|
|
45
|
+
+ `source-fetch lives). Off a box your source IS your local checkout — nothing to sync.`,
|
|
46
|
+
);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
if (dryRun) {
|
|
50
|
+
console.log(`sync [dry-run]: would run bash ${script}`);
|
|
51
|
+
console.log(' → re-fetches the sparse checkout for your ACTIVE claim (re-scopes in place).');
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
console.log(`sync: re-running ${script} — re-scoping the box to your active claim…`);
|
|
55
|
+
const r = spawnSync('bash', [script], { stdio: 'inherit' });
|
|
56
|
+
if (r.error) {
|
|
57
|
+
console.error(`sync: could not run the fetch (${r.error.message})`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
if (r.status === 0) console.log('sync: done — your box now holds the code for your active claim.');
|
|
61
|
+
process.exit(r.status == null ? 1 : r.status);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (require.main === module) main();
|
|
65
|
+
|
|
66
|
+
module.exports = { resolveFetchScript, FETCH_SCRIPT };
|
package/scripts/gds/box.js
CHANGED
|
@@ -46,6 +46,55 @@ const MAX_USERDATA_BYTES = 64 * 1024;
|
|
|
46
46
|
|
|
47
47
|
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
48
48
|
|
|
49
|
+
// The box cloud-init (infra/builder-box-cloud-init.yaml + its ~9 gz+b64-inlined
|
|
50
|
+
// scripts) is INSTANCE content, deliberately NOT shipped in @bongos/core (the R89
|
|
51
|
+
// packaging split). On a VENDORED instance box.js runs from
|
|
52
|
+
// node_modules/@bongos/core, so REPO_ROOT/infra does not exist — and the box then
|
|
53
|
+
// boots BARE (no clone, no toolchain, no terminal). So resolve infra/ from the
|
|
54
|
+
// INSTANCE root, riding ADR 0108's resolvers, instead of assuming it sits beside
|
|
55
|
+
// box.js. ADR 0150 (BV1.R97/R98).
|
|
56
|
+
const instanceConfig = require(path.join(REPO_ROOT, 'src/instance-config'));
|
|
57
|
+
const BOX_INFRA_TEMPLATE = 'builder-box-cloud-init.yaml';
|
|
58
|
+
|
|
59
|
+
// Ordered candidate infra/ dirs — first that actually holds the cloud-init
|
|
60
|
+
// template wins. Pure (env + repoRoot injected) so it is unit-testable without
|
|
61
|
+
// touching the real filesystem layout.
|
|
62
|
+
function boxInfraCandidates({ env = process.env, repoRoot = REPO_ROOT } = {}) {
|
|
63
|
+
const out = [];
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
const add = (d) => { if (d && !seen.has(d)) { seen.add(d); out.push(d); } };
|
|
66
|
+
// 1. explicit override: <PREFIX>_BOX_INFRA_DIR (operator escape hatch).
|
|
67
|
+
const override = instanceConfig.resolveEnv('BOX_INFRA_DIR', { env });
|
|
68
|
+
if (override) add(path.resolve(override));
|
|
69
|
+
// 2. the instance root via an EXPLICIT pointer (<PREFIX>_INSTANCE_ROOT env or a
|
|
70
|
+
// bongos.config.json {"instanceRoot":"."}) — where a vendored instance keeps infra/.
|
|
71
|
+
const inst = instanceConfig.resolveInstanceRootExplicit({ env });
|
|
72
|
+
if (inst) add(path.join(inst, 'infra'));
|
|
73
|
+
// 3. REPO_ROOT/infra — the dev single-checkout (and the ADR 0144 node_modules workaround).
|
|
74
|
+
add(path.join(repoRoot, 'infra'));
|
|
75
|
+
// 4. walk up out of node_modules from the core root, so a vendored
|
|
76
|
+
// …/node_modules/@bongos/core still finds its host instance's infra/ with no pointer set.
|
|
77
|
+
let dir = repoRoot;
|
|
78
|
+
for (let i = 0; i < 6; i += 1) {
|
|
79
|
+
const parent = path.dirname(dir);
|
|
80
|
+
if (parent === dir) break;
|
|
81
|
+
dir = parent;
|
|
82
|
+
add(path.join(dir, 'infra'));
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The resolved infra/ dir (first candidate holding the template), or null when the
|
|
88
|
+
// box cloud-init cannot be located anywhere — the caller MUST fail loud on null
|
|
89
|
+
// rather than provision a box with empty user-data (ADR 0150).
|
|
90
|
+
function resolveBoxInfraDir(opts = {}) {
|
|
91
|
+
for (const c of boxInfraCandidates(opts)) {
|
|
92
|
+
try { if (fs.existsSync(path.join(c, BOX_INFRA_TEMPLATE))) return c; }
|
|
93
|
+
catch { /* unreadable candidate → try the next */ }
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
49
98
|
const CONFIG = {
|
|
50
99
|
floor: process.env.BOX_PROVISION_MIN_RANK || 'xenos',
|
|
51
100
|
region: process.env.BOX_REGION || 'nyc3',
|
|
@@ -103,6 +152,44 @@ function makeCf(apply) {
|
|
|
103
152
|
});
|
|
104
153
|
}
|
|
105
154
|
|
|
155
|
+
// SECURITY PREFLIGHT (task 1002511, ADR 0053 / SR#11). The edge cookie-strip
|
|
156
|
+
// Transform Rule is applied BEST-EFFORT deep inside ensureNamedTunnel, so a
|
|
157
|
+
// CLOUDFLARE_API_TOKEN missing the zone "Transform Rules" edit scope fails as one
|
|
158
|
+
// buried log line — and the apex session cookie keeps reaching the box subdomains.
|
|
159
|
+
// This probe runs BEFORE any DigitalOcean spend (dry-run included; a read-only
|
|
160
|
+
// lookup running for real in a plan is the same precedent as the drift reconcile's
|
|
161
|
+
// live droplet listing) and shouts the exact fix.
|
|
162
|
+
//
|
|
163
|
+
// HONESTY: the probe is a GET, so it can only DISPROVE the scope. A readable phase
|
|
164
|
+
// does NOT mean the rule can be written — only the PUT inside ensureNamedTunnel
|
|
165
|
+
// proves that. Never print this as a confirmed control. Never blocks provisioning:
|
|
166
|
+
// the rule is defense-in-depth and a box without it still works.
|
|
167
|
+
async function preflightCookieStripScope() {
|
|
168
|
+
const { cfTunnel } = loadDeps();
|
|
169
|
+
let scope;
|
|
170
|
+
try {
|
|
171
|
+
scope = await cfTunnel.checkRulesetScope({
|
|
172
|
+
zone: CF.zone, phase: cfTunnel.COOKIE_STRIP_PHASE, token: CF.apiToken,
|
|
173
|
+
});
|
|
174
|
+
} catch (e) {
|
|
175
|
+
scope = { readable: false, reason: `ruleset preflight crashed: ${e.message}`, guidance: '' };
|
|
176
|
+
}
|
|
177
|
+
if (scope.readable) {
|
|
178
|
+
console.log(` cookie-strip ruleset is readable on ${CF.zone} — edit scope stays UNPROVEN until the rule is applied (ADR 0053)`);
|
|
179
|
+
return scope;
|
|
180
|
+
}
|
|
181
|
+
console.log('');
|
|
182
|
+
console.log(' ⚠⚠ SECURITY (ADR 0053 / SR#11) — the edge cookie-strip rule cannot be confirmed');
|
|
183
|
+
console.log(` ${scope.reason}`);
|
|
184
|
+
console.log(` IMPACT: the apex session cookie (Domain=.${CF.zone}) is sent by the browser to`);
|
|
185
|
+
console.log(` term-*.${CF.terminalZone} and sandbox-*.${CF.terminalZone} — hosts OUTSIDE the`);
|
|
186
|
+
console.log(' ADR 0016 trust boundary — until that Transform Rule is actually in place.');
|
|
187
|
+
if (scope.guidance) console.log(` FIX: ${scope.guidance}`);
|
|
188
|
+
console.log(' Provisioning CONTINUES — this is defense-in-depth, never a provisioning gate.');
|
|
189
|
+
console.log('');
|
|
190
|
+
return scope;
|
|
191
|
+
}
|
|
192
|
+
|
|
106
193
|
function numEnv(name, dflt) {
|
|
107
194
|
const v = process.env[name];
|
|
108
195
|
if (v == null || v === '') return dflt;
|
|
@@ -130,8 +217,8 @@ function loadDeps() {
|
|
|
130
217
|
// eslint-disable-next-line global-require
|
|
131
218
|
const { DoApi } = require(path.join(REPO_ROOT, 'scripts/gds/do-api'));
|
|
132
219
|
// eslint-disable-next-line global-require
|
|
133
|
-
const
|
|
134
|
-
_deps = { pool, boxes, boxAccess, boxOnboard, DoApi, CfTunnelApi };
|
|
220
|
+
const cfTunnel = require(path.join(REPO_ROOT, 'scripts/gds/cf-tunnel'));
|
|
221
|
+
_deps = { pool, boxes, boxAccess, boxOnboard, DoApi, CfTunnelApi: cfTunnel.CfTunnelApi, cfTunnel };
|
|
135
222
|
return _deps;
|
|
136
223
|
}
|
|
137
224
|
|
|
@@ -254,8 +341,18 @@ function runDnsHookBlackhole(hostname, hook = CONFIG.dnsHook) {
|
|
|
254
341
|
// for the builder being provisioned (#685) — base64'd here and baked in so the
|
|
255
342
|
// FIRST SSH login works before the box has a GDS token. An empty/absent block
|
|
256
343
|
// renders an empty managed block (a keyless box; the operator is warned upstream).
|
|
257
|
-
function readCloudInit({ authorizedKeysBlock = '', boxEnv = '', gdsSession = '' } = {}) {
|
|
258
|
-
|
|
344
|
+
function readCloudInit({ authorizedKeysBlock = '', boxEnv = '', gdsSession = '', infraDir = resolveBoxInfraDir() } = {}) {
|
|
345
|
+
// Fail loud, never provision a BARE box (ADR 0150). No infra/ located → there is
|
|
346
|
+
// no cloud-init to render, so THROW here rather than the old silent `return null`
|
|
347
|
+
// that let cmdProvision ship empty user-data (a box that boots as bare Ubuntu).
|
|
348
|
+
if (!infraDir) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
`box cloud-init not found: no infra/${BOX_INFRA_TEMPLATE} in any candidate location `
|
|
351
|
+
+ `(set <PREFIX>_BOX_INFRA_DIR or <PREFIX>_INSTANCE_ROOT, or ship infra/). A box must `
|
|
352
|
+
+ `never be provisioned without user-data — it would boot bare (ADR 0150).`,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
const p = path.join(infraDir, BOX_INFRA_TEMPLATE);
|
|
259
356
|
// gzip+base64 a repo file into the cloud-init write_files (`encoding: gz+b64`).
|
|
260
357
|
// EVERY inlined infra file goes through this: cloud-init gunzips it back to the
|
|
261
358
|
// exact bytes on the box, so it is byte-identical to plain b64 but ~2-3x smaller,
|
|
@@ -263,54 +360,61 @@ function readCloudInit({ authorizedKeysBlock = '', boxEnv = '', gdsSession = ''
|
|
|
263
360
|
// -fetch growth in task 1314 had pushed the all-b64-but-source-fetch render to
|
|
264
361
|
// 67 KB — gz+b64-ing the rest brings it back to ~51 KB, task 1321). Only the
|
|
265
362
|
// dynamic runtime values (authorized_keys, box.env, gds-session) stay plain b64
|
|
266
|
-
// below — they are minted per-provision, not read from infra/.
|
|
267
|
-
//
|
|
363
|
+
// below — they are minted per-provision, not read from infra/. A missing inlined
|
|
364
|
+
// file is COLLECTED and fails loud after the render (ADR 0150) — a half-wired box
|
|
365
|
+
// (that component silently never boots) is a bug, not something to warn past.
|
|
366
|
+
const missingInfra = [];
|
|
268
367
|
const fileGzB64 = (name) => {
|
|
269
368
|
try {
|
|
270
|
-
return zlib.gzipSync(fs.readFileSync(path.join(
|
|
271
|
-
} catch
|
|
272
|
-
|
|
369
|
+
return zlib.gzipSync(fs.readFileSync(path.join(infraDir, name))).toString('base64');
|
|
370
|
+
} catch {
|
|
371
|
+
missingInfra.push(name);
|
|
273
372
|
return '';
|
|
274
373
|
}
|
|
275
374
|
};
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
375
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
376
|
+
// Inject the rank-gated source-fetch (#600) + authorized-keys-fetch (#685)
|
|
377
|
+
// scripts as gz+b64 (a single YAML-safe token — no indentation/escaping
|
|
378
|
+
// pitfalls; cloud-init gunzips encoding: gz+b64 back to the exact file), plus
|
|
379
|
+
// the baked authorized_keys block. ONE canonical copy of each lives in infra/.
|
|
380
|
+
const rendered = raw
|
|
381
|
+
.replace(/__REPO_URL__/g, process.env.BOX_REPO_URL || '')
|
|
382
|
+
.replace(/__BOX_SOURCE_FETCH_B64__/g, fileGzB64('box-source-fetch.sh'))
|
|
383
|
+
.replace(/__BOX_AUTHKEYS_FETCH_B64__/g, fileGzB64('box-authorized-keys-fetch.sh'))
|
|
384
|
+
.replace(/__BUILDER_AUTHORIZED_KEYS_B64__/g, Buffer.from(authorizedKeysBlock || '', 'utf8').toString('base64'))
|
|
385
|
+
// /etc/otb/box.env — empty by default (quick tunnel); the named-tunnel
|
|
386
|
+
// connector token + stable hostname when BOX_NAMED_TUNNEL is on (#771).
|
|
387
|
+
.replace(/__BOX_ENV_B64__/g, Buffer.from(boxEnv || '', 'utf8').toString('base64'))
|
|
388
|
+
// /root/.config/otb/gds-session.json — the builder's GDS session, minted at
|
|
389
|
+
// provision so box-source-fetch (#600) can authenticate on FIRST boot and
|
|
390
|
+
// clone the rank-scoped repo. Without it the box never clones (the #818
|
|
391
|
+
// chicken-and-egg: the session normally comes from /builder-setup, which
|
|
392
|
+
// lives in the un-cloned repo).
|
|
393
|
+
.replace(/__GDS_SESSION_B64__/g, Buffer.from(gdsSession || '', 'utf8').toString('base64'))
|
|
394
|
+
// #774: the browser-terminal units, inlined so they start at FIRST BOOT
|
|
395
|
+
// (independent of the repo clone). ONE canonical copy lives in infra/.
|
|
396
|
+
.replace(/__BOX_TERMINAL_SERVICE_B64__/g, fileGzB64('box-terminal.service'))
|
|
397
|
+
// #917: credential-drift watcher pair — restarts ttyd when the credential
|
|
398
|
+
// file lands/changes after the service started (the wake/first-boot race).
|
|
399
|
+
.replace(/__BOX_TERMINAL_CREDSYNC_SERVICE_B64__/g, fileGzB64('box-terminal-credsync.service'))
|
|
400
|
+
.replace(/__BOX_TERMINAL_CREDENTIAL_PATH_B64__/g, fileGzB64('box-terminal-credential.path'))
|
|
401
|
+
.replace(/__CLOUDFLARED_SERVICE_B64__/g, fileGzB64('cloudflared.service'))
|
|
402
|
+
.replace(/__CLOUDFLARED_RUN_B64__/g, fileGzB64('cloudflared-run.sh'))
|
|
403
|
+
// #893: the `close-box` teardown command, present at first boot so a bare
|
|
404
|
+
// browser-only box has it before any repo clone (box-source-fetch refreshes
|
|
405
|
+
// it on existing boxes). ONE canonical copy lives in infra/close-box.sh.
|
|
406
|
+
.replace(/__CLOSE_BOX_B64__/g, fileGzB64('close-box.sh'))
|
|
407
|
+
// task 986: the browser-terminal auto-launcher — runs `claude` on first
|
|
408
|
+
// connect so the builder skips the "type claude" step. box-source-fetch
|
|
409
|
+
// refreshes it on existing boxes. ONE canonical copy in infra/otb-box-launch.sh.
|
|
410
|
+
.replace(/__OTB_BOX_LAUNCH_B64__/g, fileGzB64('otb-box-launch.sh'));
|
|
411
|
+
if (missingInfra.length) {
|
|
412
|
+
throw new Error(
|
|
413
|
+
`box cloud-init is incomplete under ${infraDir}: missing ${missingInfra.join(', ')} `
|
|
414
|
+
+ `— a box would boot half-wired (ADR 0150). Fix the instance infra/ before provisioning.`,
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
return rendered;
|
|
314
418
|
}
|
|
315
419
|
|
|
316
420
|
// ---- provision -------------------------------------------------------------
|
|
@@ -357,6 +461,20 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
|
|
|
357
461
|
console.log(` region=${CONFIG.region} size=${CONFIG.size} image=${CONFIG.baseImage} host=${hostname}`);
|
|
358
462
|
console.log(` baked SSH keys: ${sshKeys.length}${sshKeys.length === 0 ? ' (⚠ builder cannot SSH in until a key is registered — see `box.js onboard`)' : ''}`);
|
|
359
463
|
|
|
464
|
+
// ADR 0150 — resolve the box cloud-init source up front (dry-run AND apply) and
|
|
465
|
+
// FAIL LOUD if it can't be located. A missing infra/ means empty user-data, i.e.
|
|
466
|
+
// a box that boots BARE. Aborting here (before any session/tunnel is minted)
|
|
467
|
+
// surfaces the packaging bug in the PLAN, not as a silent bare box hours later.
|
|
468
|
+
const infraDir = resolveBoxInfraDir();
|
|
469
|
+
if (!infraDir) {
|
|
470
|
+
return fail(
|
|
471
|
+
`box cloud-init source not found — no infra/${BOX_INFRA_TEMPLATE} in any candidate location `
|
|
472
|
+
+ `(<PREFIX>_BOX_INFRA_DIR, <PREFIX>_INSTANCE_ROOT / bongos.config.json, ${path.join(REPO_ROOT, 'infra')}, or a node_modules ancestor). `
|
|
473
|
+
+ `A box would boot BARE — refusing to provision. Ship infra/ or point the instance root at it (ADR 0150).`,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
console.log(` cloud-init source: ${infraDir}`);
|
|
477
|
+
|
|
360
478
|
// Named-tunnel automation (#771, ADR 0038 Part B). OPT-IN: off → the box uses
|
|
361
479
|
// the tokenless quick tunnel (#760 default) and box.env is left empty. On but
|
|
362
480
|
// misconfigured → fail loud now (an operator who turned it on meant it).
|
|
@@ -366,6 +484,9 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
|
|
|
366
484
|
const termHost = namedTunnelEnabled() ? `term-${builder.github_login}.${CF.terminalZone}` : null;
|
|
367
485
|
if (namedTunnelEnabled()) {
|
|
368
486
|
console.log(` named tunnel: ${termHost} (Cloudflare zone ${CF.zone})`);
|
|
487
|
+
// Before ANY droplet spend, and on the dry-run path too, so `provision <login>`
|
|
488
|
+
// with no --apply tells the operator the truth for free (task 1002511).
|
|
489
|
+
await preflightCookieStripScope();
|
|
369
490
|
} else {
|
|
370
491
|
console.log(' browser terminal: quick tunnel (tokenless trycloudflare.com — set BOX_NAMED_TUNNEL=1 for a stable hostname)');
|
|
371
492
|
}
|
|
@@ -399,7 +520,7 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
|
|
|
399
520
|
let terminalCredential = null;
|
|
400
521
|
if (namedTunnelEnabled()) {
|
|
401
522
|
try {
|
|
402
|
-
const { token, hostname: th, previewHostname: ph } = await makeCf(true).ensureNamedTunnel({
|
|
523
|
+
const { token, hostname: th, previewHostname: ph, edgeRules } = await makeCf(true).ensureNamedTunnel({
|
|
403
524
|
login: builder.github_login, zone: CF.zone, terminalZone: CF.terminalZone,
|
|
404
525
|
});
|
|
405
526
|
terminalHostname = th;
|
|
@@ -411,6 +532,18 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
|
|
|
411
532
|
boxEnv = `# /etc/otb/box.env — baked by box.js provision (#771/#774, ADR 0038 named tunnel)\nBOX_TUNNEL_TOKEN=${token}\nBOX_TERMINAL_HOSTNAME=${th}\nBOX_TERMINAL_CREDENTIAL=${terminalCredential}\nBOX_PREVIEW_HOSTNAME=${ph}\n`;
|
|
412
533
|
console.log(` ✓ named tunnel ready — ${th}`);
|
|
413
534
|
console.log(` ✓ live game preview — https://${ph} (ADR 0044)`);
|
|
535
|
+
// The rule PUT is the ONLY thing that proves edit scope. Report its real
|
|
536
|
+
// outcome next to the ✓ lines so a silent miss lands in the summary rather
|
|
537
|
+
// than mid-log (task 1002511).
|
|
538
|
+
if (edgeRules && !edgeRules.cookieStrip.applied) {
|
|
539
|
+
console.log(` ⚠⚠ SECURITY (ADR 0053 / SR#11): the edge cookie-strip Transform Rule was NOT applied${edgeRules.cookieStrip.error ? ` — ${edgeRules.cookieStrip.error}` : ''}`);
|
|
540
|
+
console.log(` Platform cookies for .${CF.zone} can reach ${th} and ${ph} until it is.`);
|
|
541
|
+
console.log(' Widen CLOUDFLARE_API_TOKEN with zone "Transform Rules → Edit", or apply the rule');
|
|
542
|
+
console.log(' once by hand — see docs/recipes/builder-box-lifecycle.md.');
|
|
543
|
+
}
|
|
544
|
+
if (edgeRules && !edgeRules.cacheBypass.applied) {
|
|
545
|
+
console.log(` ⚠ the sandbox cache-bypass Cache Rule was NOT applied${edgeRules.cacheBypass.error ? ` — ${edgeRules.cacheBypass.error}` : ''} (stale preview risk, #1065)`);
|
|
546
|
+
}
|
|
414
547
|
} catch (e) {
|
|
415
548
|
await boxes.setBoxState(pool, builder.id, 'error', { error_note: `named tunnel: ${e.message}` });
|
|
416
549
|
return fail(`named tunnel setup failed: ${e.message}`);
|
|
@@ -443,15 +576,29 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
|
|
|
443
576
|
const gdsSession = JSON.stringify({
|
|
444
577
|
token: sessionToken,
|
|
445
578
|
builder: { id: builder.id, github_login: builder.github_login, rank: builder.rank },
|
|
446
|
-
|
|
579
|
+
// The instance's OWN public origin (task 1002428) — a hardcoded founder host
|
|
580
|
+
// here would point every newly provisioned box at the wrong instance.
|
|
581
|
+
api_base: process.env.GDS_PUBLIC_ORIGIN || instanceConfig.defaultApiBase(),
|
|
447
582
|
}, null, 2);
|
|
448
583
|
|
|
449
584
|
const doApi = makeDo(apply);
|
|
450
585
|
// Render + size-check the cloud-init BEFORE touching DO (#903). DO rejects
|
|
451
586
|
// user-data over 64 KiB with an opaque error; fail fast with an actionable one
|
|
452
587
|
// so an over-budget cloud-init is caught here, not as a cryptic API 4xx.
|
|
453
|
-
|
|
588
|
+
let userData;
|
|
589
|
+
try {
|
|
590
|
+
userData = readCloudInit({ authorizedKeysBlock, boxEnv, gdsSession, infraDir });
|
|
591
|
+
} catch (e) {
|
|
592
|
+
await boxes.setBoxState(pool, builder.id, 'error', { error_note: `cloud-init: ${e.message}`.slice(0, 480) });
|
|
593
|
+
return fail(e.message);
|
|
594
|
+
}
|
|
454
595
|
const userDataBytes = userData ? Buffer.byteLength(userData, 'utf8') : 0;
|
|
596
|
+
// Belt-and-braces (ADR 0150): readCloudInit throws above, but NEVER hand DO empty
|
|
597
|
+
// user-data — that IS the bare-box failure this fix closes.
|
|
598
|
+
if (!userDataBytes) {
|
|
599
|
+
await boxes.setBoxState(pool, builder.id, 'error', { error_note: 'cloud-init render produced empty user-data' });
|
|
600
|
+
return fail('cloud-init render produced EMPTY user-data — a box would boot bare (ADR 0150); refusing to provision.');
|
|
601
|
+
}
|
|
455
602
|
if (userDataBytes > MAX_USERDATA_BYTES) {
|
|
456
603
|
await boxes.setBoxState(pool, builder.id, 'error', {
|
|
457
604
|
error_note: `cloud-init ${userDataBytes}B > ${MAX_USERDATA_BYTES}B`,
|
|
@@ -461,6 +608,14 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
|
|
|
461
608
|
await boxes.setBoxState(pool, builder.id, 'provisioning', {
|
|
462
609
|
scope, provisioned_rank: builder.rank, region: CONFIG.region, size_slug: CONFIG.size,
|
|
463
610
|
droplet_name: name, hostname, error_note: null,
|
|
611
|
+
// The droplet about to boot mints FRESH host keys, so anything the row still
|
|
612
|
+
// carries describes a box that is gone. deprovisionOne clears these on teardown
|
|
613
|
+
// (idea 310), but a provision over a row that never went through teardown — an
|
|
614
|
+
// errored/parked box, a re-provision after a manual DO delete — kept serving the
|
|
615
|
+
// predecessor's identity via GET /box/me, and the connect path pinned it: "Host
|
|
616
|
+
// denied (verification failed)". Clear here too; the new box re-reports via
|
|
617
|
+
// POST /box/host-keys, and until then the client falls back to a live keyscan.
|
|
618
|
+
host_keys: null, host_keys_at: null,
|
|
464
619
|
});
|
|
465
620
|
try {
|
|
466
621
|
const { droplet } = await doApi.createDroplet({
|
|
@@ -522,8 +677,21 @@ async function cmdProvision(pool, boxes, boxOnboard, ref, { apply }) {
|
|
|
522
677
|
// box can't fetch its own authorized_keys until the builder has a token ON the
|
|
523
678
|
// box (chicken-and-egg), onboard REQUIRES at least one registered key by default;
|
|
524
679
|
// --allow-no-keys overrides for an operator who knows they'll re-provision later.
|
|
680
|
+
// The instance's product name, for the Claude Desktop environment label the
|
|
681
|
+
// connect instructions name (task 1002428). Neutral fallback, never a foreign brand.
|
|
682
|
+
function boxEnvLabel() {
|
|
683
|
+
try {
|
|
684
|
+
const id = require(path.join(REPO_ROOT, 'src/branding')).branding().identity || {};
|
|
685
|
+
if (id.productName) return id.productName;
|
|
686
|
+
} catch (_) { /* fall through */ }
|
|
687
|
+
return 'Cloud Bongos';
|
|
688
|
+
}
|
|
689
|
+
|
|
525
690
|
function gdsBase() {
|
|
526
|
-
|
|
691
|
+
// Session-aware, via the single source of truth (task 1002427/1002428) — the
|
|
692
|
+
// operator's signed-in instance, not a hardcoded founder host. <PREFIX>_API_BASE
|
|
693
|
+
// (incl. legacy GDS_API_BASE) still wins, so existing overrides are unchanged.
|
|
694
|
+
return String(instanceConfig.resolveApiBase()).replace(/\/+$/, '');
|
|
527
695
|
}
|
|
528
696
|
|
|
529
697
|
function printWelcomePacket(login, scope) {
|
|
@@ -540,7 +708,7 @@ function printWelcomePacket(login, scope) {
|
|
|
540
708
|
console.log(` Windows: irm ${base}/api/gds/box/connect.ps1 | iex`);
|
|
541
709
|
console.log(` (guide: ${base}/api/gds/box/connect )`);
|
|
542
710
|
console.log('');
|
|
543
|
-
console.log(
|
|
711
|
+
console.log(`Then: restart Claude Desktop → Code → Environment → "${boxEnvLabel()} Dev Box".`);
|
|
544
712
|
console.log('');
|
|
545
713
|
console.log('On a Chromebook / browser-only device (no Claude Desktop)? Skip the above —');
|
|
546
714
|
console.log('your box serves its own terminal in a browser tab (ADR 0038). Once it is');
|
|
@@ -1204,6 +1372,10 @@ Named browser-terminal tunnel (#771, ADR 0038 — OPT-IN, default OFF):
|
|
|
1204
1372
|
connector token; deprovision tears it down). Off → the
|
|
1205
1373
|
zero-secret quick tunnel (trycloudflare.com).
|
|
1206
1374
|
CLOUDFLARE_API_TOKEN token with Account:Cloudflare Tunnel:Edit + Zone:DNS:Edit
|
|
1375
|
+
+ Zone:Transform Rules:Edit (the ADR 0053 cookie-strip —
|
|
1376
|
+
without it platform cookies reach the box subdomains)
|
|
1377
|
+
+ Zone:Cache Rules:Edit (the sandbox cache bypass, #1065).
|
|
1378
|
+
provision read-probes the ruleset scope before spending.
|
|
1207
1379
|
CLOUDFLARE_ACCOUNT_ID the Cloudflare account the tunnels live in
|
|
1208
1380
|
CLOUDFLARE_ZONE DNS zone (default ${CF.zone})
|
|
1209
1381
|
BOX_TERMINAL_ZONE hostname suffix (default ${CF.terminalZone})`);
|
|
@@ -1255,4 +1427,4 @@ async function main() {
|
|
|
1255
1427
|
|
|
1256
1428
|
if (require.main === module) main();
|
|
1257
1429
|
|
|
1258
|
-
module.exports = { cmdProvision, deprovisionOne, deleteWithRetry, cmdReconcileDrift, runDnsHook, runDnsHookBlackhole, markErrorUnlessDestroyed };
|
|
1430
|
+
module.exports = { cmdProvision, deprovisionOne, deleteWithRetry, cmdReconcileDrift, runDnsHook, runDnsHookBlackhole, markErrorUnlessDestroyed, resolveBoxInfraDir, boxInfraCandidates, readCloudInit, BOX_INFRA_TEMPLATE };
|