@bongos/core 1.19.1073 → 1.19.1075
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 +404 -549
- package/.claude/skills/feedback/SKILL.md +2 -2
- package/bin/bongos.js +1 -3
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -60
- package/clients/bongos-client/index.cjs +4 -60
- package/clients/bongos-client/index.d.ts +5 -90
- package/clients/bongos-client/index.mjs +4 -60
- package/config/branding.neutral.json +1 -5
- package/config/modules.neutral.json +2 -3
- package/config/scheduled-routines.json +0 -2
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
- package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
- package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
- package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
- package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
- package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +175 -1665
- package/docs/api-reference.md +9 -41
- package/docs/architecture.md +16 -1
- package/docs/branding-contract.md +0 -3
- package/docs/copy-inventory.md +454 -479
- package/docs/copy-registry.json +746 -978
- package/docs/file-map.md +5 -34
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +13 -1
- package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
- package/docs/onboarding/diagrams/README.md +3 -3
- package/docs/onboarding/diagrams/assertions.json +2 -22
- package/docs/onboarding/primer.md +9 -28
- package/docs/page-inventory.json +4 -1
- package/docs/page-readings.json +1032 -1077
- package/docs/recipes/render-pilot.md +29 -10
- package/migrations/core_257_module_store_registry.sql +87 -0
- package/migrations/core_258_drop_dev_box_tables.sql +55 -0
- package/modules/agents/module.json +1 -0
- package/modules/autonomy/module.json +1 -0
- package/modules/builder-settings/module.json +1 -0
- package/modules/builder-settings/render-prefs.js +4 -5
- package/modules/copy-desk/docx.js +78 -5
- package/modules/copy-desk/module.json +1 -0
- package/modules/copy-desk/routes/copy-desk.js +10 -10
- package/modules/copy-desk/tests/copy_docx.mjs +107 -1
- package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
- package/modules/discord/module.json +1 -0
- package/modules/discord/ship-broadcast.js +3 -3
- package/modules/economy/credits.js +2 -2
- package/modules/economy/module.json +1 -0
- package/modules/economy/routes/credits.js +1 -2
- package/modules/government/catalog.js +13 -17
- package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
- package/modules/government/module.json +1 -0
- package/modules/government/protected-surfaces.json +3 -11
- package/modules/government/resolver.js +3 -46
- package/modules/government/routes/government.js +0 -2
- package/modules/government/session-scopes.js +43 -83
- package/modules/government/session-scopes.json +8 -18
- package/modules/grading/module.json +1 -0
- package/modules/hall-ui/module.json +1 -0
- package/modules/hall-ui/public/board-lib.js +2 -2
- package/modules/hall-ui/public/builders.js +29 -40
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/collab.js +1 -1
- package/modules/hall-ui/public/diagrams.html +3 -3
- package/modules/hall-ui/public/dom-utils.js +1 -1
- package/modules/hall-ui/public/gate.js +2 -2
- package/modules/hall-ui/public/gate.states.json +1 -1
- package/modules/hall-ui/public/hall-render.js +43 -79
- package/modules/hall-ui/public/index.html +5 -1
- package/modules/hall-ui/public/modules.js +1 -0
- package/modules/hall-ui/public/oversight.css +5 -5
- package/modules/hall-ui/public/palette.js +0 -11
- package/modules/hall-ui/public/primer.js +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings-sessions.js +1 -1
- package/modules/hall-ui/public/settings.css +0 -1
- package/modules/hall-ui/public/settings.html +4 -33
- package/modules/hall-ui/public/settings.js +3 -33
- package/modules/hall-ui/public/settings.states.json +2 -2
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +10 -13
- package/modules/hall-ui/public/task.html +4 -1
- package/modules/hall-ui/public/task.js +36 -1
- package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
- package/modules/hall-ui/public/tweak-editor.css +16 -0
- package/modules/hall-ui/public/tweak-editor.html +26 -3
- package/modules/hall-ui/public/tweak-editor.js +32 -1
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/hall-ui/records/tweak-editor.md +1 -0
- package/modules/ideas/module.json +1 -0
- package/modules/ideas/projection.js +1 -1
- package/modules/lifecycle/db-claim-reads.js +1 -58
- package/modules/lifecycle/db-ship.js +4 -9
- package/modules/lifecycle/db.js +0 -4
- package/modules/lifecycle/github-push.js +12 -14
- package/modules/lifecycle/kickoff-checklist.js +0 -4
- package/modules/lifecycle/lifecycle.js +0 -8
- package/modules/lifecycle/module.json +1 -0
- package/modules/lifecycle/publish-reconciler.js +10 -10
- package/modules/lifecycle/routes/tasks.js +13 -15
- package/modules/lifecycle/ship-card.js +1 -1
- package/modules/lifecycle/task-visuals.js +2 -2
- package/modules/memory/module.json +1 -0
- package/modules/memory/routes/memory.js +2 -2
- package/modules/npm-release/module.json +1 -0
- package/modules/onboarding/module.json +1 -0
- package/modules/onboarding/onboarding-state.js +17 -36
- package/modules/onboarding/routes/access-requests.js +3 -3
- package/modules/platform-identity/module.json +1 -0
- package/modules/platform-identity/platform-identity.js +13 -5
- package/modules/platform-identity/routes/sso.js +5 -4
- package/modules/platform-identity/tests/platform-identity.mjs +17 -3
- package/modules/provisioning/capacity.js +1 -1
- package/modules/provisioning/module.json +1 -0
- package/modules/provisioning/provisioning.js +6 -5
- package/modules/provisioning/routes/provisioning.js +4 -4
- package/modules/provisioning/starter-bundles.js +5 -25
- package/modules/public-landing/module.json +1 -0
- package/modules/public-landing/public/assets/cosmos.css +1 -1
- package/modules/public-landing/public/contact.html +1 -1
- package/modules/public-landing/public/privacy.html +1 -1
- package/modules/public-landing/public/projects.html +101 -32
- package/modules/public-landing/public/projects.probes.json +2 -2
- package/modules/public-landing/public/projects.states.json +4 -3
- package/modules/public-landing/public/terms.html +1 -1
- package/modules/security/module.json +1 -0
- package/modules/sessions/db.js +1 -1
- package/modules/sessions/module.json +1 -0
- package/modules/specialities/module.json +1 -0
- package/modules/status-ui/module.json +1 -0
- package/modules/ui-design/module.json +1 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +84 -0
- package/scripts/gds/artifact-format.js +203 -0
- package/scripts/gds/artifact-staleness.js +2 -2
- package/scripts/gds/audit-authorship.sh +1 -13
- package/scripts/gds/backfill-task-rewards.js +5 -5
- package/scripts/gds/claim.js +0 -15
- package/scripts/gds/claude-materialize.js +2 -2
- package/scripts/gds/cli-lib.js +15 -15
- package/scripts/gds/codemod-rename-src-gds.js +1 -1
- package/scripts/gds/context-pack.js +5 -7
- package/scripts/gds/diagram-facts.js +12 -23
- package/scripts/gds/do-api.js +13 -80
- package/scripts/gds/doc-cli-guard.js +2 -0
- package/scripts/gds/doctor.js +4 -4
- package/scripts/gds/feedback-latest.js +139 -9
- package/scripts/gds/fitness-checks-identity.js +2 -7
- package/scripts/gds/fitness-checks-packaging.js +4 -4
- package/scripts/gds/fitness-checks-write-validation.js +4 -0
- package/scripts/gds/fitness-ratchets.js +5 -5
- package/scripts/gds/fitness.js +10 -10
- package/scripts/gds/gen-api-client.js +4 -4
- package/scripts/gds/gen-api-docs.js +1 -1
- package/scripts/gds/gen-diagrams.js +49 -71
- package/scripts/gds/gen-repo-map.js +13 -14
- package/scripts/gds/gen-session-index.js +8 -8
- package/scripts/gds/http-api-client.js +12 -19
- package/scripts/gds/init.js +1 -1
- package/scripts/gds/leak-scan-allowlist.js +1 -1
- package/scripts/gds/lint-infra-exec.js +1 -1
- package/scripts/gds/local-preview-lib.js +8 -39
- package/scripts/gds/local-preview.js +2 -15
- package/scripts/gds/migration-namespace.js +3 -3
- package/scripts/gds/module-artifact.js +212 -0
- package/scripts/gds/module.js +95 -6
- package/scripts/gds/package-core.js +5 -116
- package/scripts/gds/plain-cards.js +1 -1
- package/scripts/gds/provision-config.js +2 -2
- package/scripts/gds/provision-net.js +2 -3
- package/scripts/gds/provision.js +9 -9
- package/scripts/gds/publish-manifest.js +1 -2
- package/scripts/gds/push-path-brief.js +10 -10
- package/scripts/gds/regen-instance-docs.js +1 -1
- package/scripts/gds/rename-history-check.js +1 -1
- package/scripts/gds/run-unit-tests.js +2 -16
- package/scripts/gds/sandbox-stage.js +39 -341
- package/scripts/gds/session-digest-build.js +2 -2
- package/scripts/gds/ship-deploy-target.js +13 -13
- package/scripts/gds/ship-flow.js +7 -7
- package/scripts/gds/ship-land.js +9 -10
- package/scripts/gds/ship-merge.js +4 -4
- package/scripts/gds/ship-preflight-steps.js +4 -4
- package/scripts/gds/ship-regen.js +15 -49
- package/scripts/gds/ship.js +13 -33
- package/scripts/gds/skill-preflight.js +9 -8
- package/scripts/gds/start.js +0 -15
- package/scripts/hall-preview/README.md +5 -5
- package/scripts/hall-preview/server.js +6 -6
- package/scripts/render-diagrams.sh +1 -1
- package/src/bongos/api-errors.js +1 -1
- package/src/bongos/api-prefix.js +4 -4
- package/src/bongos/auth-github.js +2 -3
- package/src/bongos/auth.js +51 -126
- package/src/bongos/db-kernel.js +2 -3
- package/src/bongos/db.js +5 -28
- package/src/bongos/module-scope-map.js +14 -29
- package/src/bongos/module-store.js +141 -0
- package/src/bongos/platform-visibility-gate.js +1 -1
- package/src/bongos/route-rank-check.js +3 -16
- package/src/bongos/routes/auth.js +0 -116
- package/src/bongos/routes/builders.js +5 -15
- package/src/bongos/routes/instance.js +8 -14
- package/src/bongos/routes/me.js +7 -23
- package/src/bongos/routes/modules.js +83 -0
- package/src/bongos/routes/security.js +3 -3
- package/src/bongos/routes.js +7 -2
- package/src/bongos/serve-internal.js +5 -6
- package/src/bongos-downloads.js +1 -6
- package/src/branding.js +2 -2
- package/src/build-info.js +4 -6
- package/src/instance-config.js +2 -2
- package/src/module-api.js +13 -6
- package/src/module-loader/catalog.js +3 -0
- package/src/module-loader/loader.js +4 -4
- package/src/module-loader/manifest-schema.js +16 -8
- package/src/module-seams.js +2 -3
- package/src/modules.js +42 -14
- package/tests/agents_spend_guard.mjs +1 -1
- package/tests/api_alias_caller_ratchet.mjs +1 -1
- package/tests/api_alias_exceptions.mjs +1 -1
- package/tests/api_client.mjs +1 -16
- package/tests/api_path_404.mjs +1 -1
- package/tests/auth_page_gate.mjs +10 -10
- package/tests/backfill_task_rewards.mjs +1 -1
- package/tests/canonical_profile_url.mjs +1 -3
- package/tests/claim_action.mjs +4 -1
- package/tests/claim_from_task_and_home.mjs +127 -0
- package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
- package/tests/cli_sessions.mjs +1 -1
- package/tests/consumer_layout_boot.mjs +2 -0
- package/tests/context_pack.mjs +13 -13
- package/tests/copy_desk_page_docx.mjs +33 -2
- package/tests/copy_inventory.mjs +7 -7
- package/tests/core_upgrade_runner.mjs +6 -6
- package/tests/credit_grant.mjs +2 -2
- package/tests/criterion_suggest.mjs +1 -1
- package/tests/deploy_divergence_line.mjs +11 -11
- package/tests/design_tokens_sync.mjs +3 -2
- package/tests/diagram_facts_offset.mjs +1 -1
- package/tests/do_api.mjs +106 -0
- package/tests/feedback_latest.mjs +122 -0
- package/tests/fitness.mjs +44 -42
- package/tests/fitness_ratchets.mjs +2 -2
- package/tests/gate_approvals.mjs +0 -1
- package/tests/github_push_land_proof.mjs +1 -1
- package/tests/go_live.mjs +3 -3
- package/tests/goal_advisory.mjs +3 -3
- package/tests/goal_suggest.mjs +3 -3
- package/tests/government_abuse_matrix.mjs +21 -19
- package/tests/government_ownership_scope.mjs +33 -35
- package/tests/government_parity.mjs +2 -2
- package/tests/government_protected_surfaces.mjs +56 -37
- package/tests/government_require_permission.mjs +3 -3
- package/tests/government_seed.mjs +37 -4
- package/tests/government_session_scope.mjs +92 -366
- package/tests/hall_audit.mjs +1 -1
- package/tests/hall_error_envelope.mjs +4 -0
- package/tests/hall_landing_boot.mjs +4 -1
- package/tests/hall_palette.mjs +1 -16
- package/tests/hall_record_world.mjs +6 -0
- package/tests/hall_tweak_editor.mjs +52 -1
- package/tests/helpers.mjs +1 -1
- package/tests/host_topology_skips.mjs +3 -4
- package/tests/html_comment_nesting.mjs +137 -0
- package/tests/infra_exec_paths.mjs +9 -10
- package/tests/init.mjs +4 -5
- package/tests/instance_manifest.mjs +18 -25
- package/tests/kickoff_checklist.mjs +1 -1
- package/tests/lib_sh_resolution.mjs +0 -221
- package/tests/local_preview.mjs +0 -12
- package/tests/migration_namespace.mjs +6 -6
- package/tests/module-scope-map.mjs +9 -9
- package/tests/module_api.mjs +2 -2
- package/tests/module_catalog.mjs +1 -1
- package/tests/module_cli.mjs +20 -20
- package/tests/module_contributions.mjs +6 -6
- package/tests/module_loader.mjs +6 -6
- package/tests/module_manifest.mjs +26 -17
- package/tests/module_route_rank.mjs +2 -2
- package/tests/module_store_publish.mjs +307 -0
- package/tests/module_store_publish_route.mjs +121 -0
- package/tests/module_store_registry_migration.mjs +70 -0
- package/tests/modules.mjs +56 -26
- package/tests/modules_route_wiring.mjs +1 -1
- package/tests/onboarding_route_signals.mjs +21 -31
- package/tests/onboarding_state.mjs +35 -49
- package/tests/paste_token_session_store.mjs +1 -1
- package/tests/permission_path.mjs +0 -3
- package/tests/platform_boot.mjs +1 -2
- package/tests/platform_visibility_gate.mjs +1 -1
- package/tests/profile_route.mjs +1 -6
- package/tests/project_modules_ui.mjs +17 -17
- package/tests/projects_hub.mjs +5 -5
- package/tests/projects_hub_app_step.mjs +138 -0
- package/tests/projects_hub_module_picker.mjs +77 -76
- package/tests/projects_hub_pre_uat.mjs +4 -5
- package/tests/provision.mjs +7 -7
- package/tests/provisioning_capacity.mjs +1 -1
- package/tests/provisioning_recommendations.mjs +2 -1
- package/tests/provisioning_starter_bundles.mjs +0 -22
- package/tests/publish_branch_route.mjs +11 -19
- package/tests/publish_manifest.mjs +2 -3
- package/tests/publish_reconciler.mjs +8 -8
- package/tests/push_path_brief.mjs +10 -9
- package/tests/rank_tier_single_source.mjs +1 -13
- package/tests/regrade_eligibility.mjs +1 -1
- package/tests/rename_history_restraint.mjs +1 -1
- package/tests/repo_map.mjs +12 -13
- package/tests/runner_drift.mjs +3 -21
- package/tests/sandbox_stage.mjs +57 -390
- package/tests/seam_wiring_guard.mjs +11 -11
- package/tests/search_isolation.mjs +3 -3
- package/tests/session_records.mjs +1 -1
- package/tests/session_rename_fallback.mjs +1 -22
- package/tests/session_start_freshness.mjs +3 -3
- package/tests/session_token_hash_db.mjs +1 -1
- package/tests/ship_card.mjs +1 -1
- package/tests/ship_ci_deploy.mjs +17 -17
- package/tests/ship_error_shape.mjs +1 -1
- package/tests/ship_premerge.mjs +1 -1
- package/tests/ship_resume.mjs +1 -1
- package/tests/skill_preflight.mjs +2 -2
- package/tests/skip_is_not_pass.mjs +1 -1
- package/tests/task_visual_slots.mjs +31 -0
- package/tests/terms_acceptance.mjs +2 -2
- package/tests/upgrade.mjs +4 -4
- package/tests/watch_sealed_floor.mjs +0 -1
- package/tests/wizard_draft_resume.mjs +2 -2
- package/tests/wizard_intent_resume.mjs +21 -10
- package/tests/wizard_preselect_why.mjs +18 -18
- package/docs/onboarding/browser-terminal-guide.md +0 -73
- package/docs/recipes/managed-settings-remote-control.md +0 -416
- package/modules/dev-box/CLAUDE.md +0 -15
- package/modules/dev-box/box-access.js +0 -623
- package/modules/dev-box/box-credential.js +0 -94
- package/modules/dev-box/box-onboard.js +0 -486
- package/modules/dev-box/boxes.js +0 -1038
- package/modules/dev-box/db.js +0 -39
- package/modules/dev-box/module.json +0 -19
- package/modules/dev-box/routes/box.js +0 -1223
- package/scripts/gds/box-auth-check.js +0 -160
- package/scripts/gds/box-infra.js +0 -321
- package/scripts/gds/box-sync.js +0 -216
- package/scripts/gds/box.js +0 -1332
- package/scripts/gds/cf-tunnel.js +0 -558
- package/scripts/gds/smoke-box.sh +0 -150
- package/scripts/gds/tree-preflight.js +0 -241
- package/src/bongos/app-pair.js +0 -172
- package/tests/app_pair.mjs +0 -168
- package/tests/box_access.mjs +0 -578
- package/tests/box_auth_check.mjs +0 -114
- package/tests/box_code_staleness.mjs +0 -129
- package/tests/box_connect_e2e.mjs +0 -196
- package/tests/box_cost.mjs +0 -70
- package/tests/box_credential.mjs +0 -125
- package/tests/box_dns_repoint.mjs +0 -153
- package/tests/box_env.mjs +0 -64
- package/tests/box_host_keys.mjs +0 -126
- package/tests/box_infra_resolve.mjs +0 -246
- package/tests/box_onboard.mjs +0 -317
- package/tests/box_scope_session.mjs +0 -119
- package/tests/box_sweep_docs.mjs +0 -119
- package/tests/box_sync.mjs +0 -55
- package/tests/box_sync_scope_report.mjs +0 -126
- package/tests/box_task_scope.mjs +0 -169
- package/tests/box_task_scope_pause.mjs +0 -66
- package/tests/box_terminal_ssrf.mjs +0 -96
- package/tests/boxes.mjs +0 -1585
- package/tests/cf_ruleset_preflight.mjs +0 -142
- package/tests/cf_tunnel.mjs +0 -496
- package/tests/docs_scrubber_damage.mjs +0 -111
- package/tests/module_scope_active_claims.mjs +0 -94
- package/tests/ship_api_client_pathspec.mjs +0 -100
- package/tests/tree_preflight.mjs +0 -243
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
// scripts/gds/push-path-brief.js — say out loud, at session start, that this machine
|
|
5
5
|
// cannot push (task 1003259 / idea 1000838).
|
|
6
6
|
//
|
|
7
|
-
// THE GAP. On 2026-08-24 a
|
|
8
|
-
// concluded "no push credential exists", and asked the owner to
|
|
9
|
-
// PAT and store it in ~/.git-credentials
|
|
10
|
-
// server-mediated publish path (ADR 0055) exists to avoid. The agent was not being
|
|
11
|
-
// careless:
|
|
12
|
-
// token
|
|
7
|
+
// THE GAP. On 2026-08-24 an agent on a credential-less checkout hit auth failures on
|
|
8
|
+
// every raw git probe, concluded "no push credential exists", and asked the owner to
|
|
9
|
+
// mint a fine-grained PAT and store it in ~/.git-credentials — the exact anti-pattern
|
|
10
|
+
// the server-mediated publish path (ADR 0055) exists to avoid. The agent was not being
|
|
11
|
+
// careless: on that checkout every direct GitHub probe failed BY DESIGN (the
|
|
12
|
+
// clone token was one-shot and scrubbed from .git/config after each fetch — ADR 0031 §6),
|
|
13
13
|
// so "missing credential" is the only story the local evidence supports, and the true
|
|
14
14
|
// state at the time (branch already pushed, PR open, CI red) was invisible to it.
|
|
15
|
-
// Nothing the
|
|
15
|
+
// Nothing the session loads says the model out loud. This says it out loud.
|
|
16
16
|
//
|
|
17
17
|
// SO: the third SessionStart truth-teller, beside deploy-divergence.js and
|
|
18
18
|
// strand-watch.js. On the server-publish path (pushVia() 'server' — no authenticated
|
|
19
|
-
// gh, i.e.
|
|
19
|
+
// gh, i.e. any credential-less checkout) it prints ONE line stating the
|
|
20
20
|
// credential model and where the truth about published work lives
|
|
21
21
|
// (GET /api/bongos/tasks/:id/publish-status). On a laptop that pushes locally
|
|
22
22
|
// (pushVia() 'local') it prints NOTHING — a line that fires on every healthy laptop
|
|
@@ -58,7 +58,7 @@ function briefLine(via) {
|
|
|
58
58
|
|
|
59
59
|
// Lazy require so a broken or partial checkout degrades to silence, not a stack
|
|
60
60
|
// trace injected into the session. pushVia() honors <PREFIX>_PUSH_VIA_SERVER, else
|
|
61
|
-
// probes for an authenticated gh (absent on
|
|
61
|
+
// probes for an authenticated gh (absent on a credential-less checkout → 'server' instantly).
|
|
62
62
|
function detectVia() {
|
|
63
63
|
return require('./ship-deploy-target.js').pushVia();
|
|
64
64
|
}
|
|
@@ -70,7 +70,7 @@ function report(via) {
|
|
|
70
70
|
console.log(' Publish path');
|
|
71
71
|
console.log(' ──────────────────────────────────────────────────────────');
|
|
72
72
|
if (via === 'server') {
|
|
73
|
-
console.log(' server-mediated (no authenticated gh — a
|
|
73
|
+
console.log(' server-mediated (no authenticated gh — a credential-less checkout).');
|
|
74
74
|
console.log(` ${BRIEF}`);
|
|
75
75
|
for (const line of plainCardLines('no_push_credential')) console.log(line);
|
|
76
76
|
} else if (via === 'local') {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// regen-instance-docs.js — regenerate a STANDALONE instance's live navigation docs (task 1002314).
|
|
4
4
|
//
|
|
5
5
|
// THE GAP. On the monorepo, the gitignored whole-file navigation docs are regenerated at deploy by
|
|
6
|
-
// scripts/deploy/deploy-prod.sh
|
|
6
|
+
// scripts/deploy/deploy-prod.sh (ADR 0110 §2/§3). STANDALONE instances
|
|
7
7
|
// (ADR 0108 — their own lean repo with @bongos/core vendored) never got that step: their bespoke
|
|
8
8
|
// pull-deploy regenerated NOTHING, so a fresh instance shipped with an empty docs/ and nothing kept
|
|
9
9
|
// the session-log index live. This script is the STANDALONE analogue of deploy-prod.sh's regen block
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
// notices if a blanket find-replace rewrites them. This does.
|
|
46
46
|
//
|
|
47
47
|
// 4. ALIAS MOUNTS — '/api/gds' is a PERMANENT alias (src/bongos/api-prefix.js
|
|
48
|
-
// says never to remove it: shipped
|
|
48
|
+
// says never to remove it: shipped binaries and live instances
|
|
49
49
|
// call it and cannot be force-updated). Asserted against the real exported
|
|
50
50
|
// mount list. The DISPATCH half — that the alias is a live mount and so
|
|
51
51
|
// gets no corrective 404 hint — is already covered by
|
|
@@ -93,7 +93,7 @@ const INTEGRATION = new Set([
|
|
|
93
93
|
// DB-free route test can only assert the shape of.
|
|
94
94
|
'community_leaderboard_db',
|
|
95
95
|
'asset_version', 'auth_flow_slice1', 'backfill_sessions', 'bfg', 'bfg_write',
|
|
96
|
-
'
|
|
96
|
+
'cli_token_reissue',
|
|
97
97
|
// consumer_layout_boot builds the core .tgz, `npm install`s it, and boots a live
|
|
98
98
|
// platform-server (task 2142), then installs the package from its own shipped lockfile,
|
|
99
99
|
// migrates brand-new EMPTY databases and serves a database-backed route from one (task
|
|
@@ -398,20 +398,6 @@ function discoverModuleTests({ modulesDir = MODULES_DIR, isEnabled = isModuleEna
|
|
|
398
398
|
// above, the real repair is for the test to establish the state it needs instead of
|
|
399
399
|
// inheriting whatever ran before it.
|
|
400
400
|
//
|
|
401
|
-
// THE THREE DEV-BOX TESTS ARE PARKED, NOT DELETED, and that reverses a first pass
|
|
402
|
-
// of this task. The owner ruled that dev-box work can go, and boxes / box_onboard /
|
|
403
|
-
// box_connect_e2e were duly deleted — but modules/dev-box/ is still present, still
|
|
404
|
-
// mounted and unmodified by that change, and those files carry the only regression
|
|
405
|
-
// coverage for LIVE guards: the setBoxState column allowlist (SQL injection), the
|
|
406
|
-
// rank/authz floors, the spoofed X-Forwarded-Host that must never reach the served
|
|
407
|
-
// connect.sh, and the authorized_keys command= / embedded-newline rejections.
|
|
408
|
-
// Deleting a security regression test for code that still runs is not retirement,
|
|
409
|
-
// it is removing the alarm and leaving the door. modules/dev-box/CLAUDE.md also
|
|
410
|
-
// still names boxes + box_onboard as that module's required suite, and
|
|
411
|
-
// scripts/gds/smoke-box.sh still invokes all three by path — deleting the files
|
|
412
|
-
// turned that script into an immediate MODULE_NOT_FOUND. They come out when the
|
|
413
|
-
// module does, in the change that removes it.
|
|
414
|
-
//
|
|
415
401
|
// consumer_layout_boot LEFT THIS LIST under task 1002863. Its "missing core tarball" was
|
|
416
402
|
// package-core's fail-closed leak gate reading the lane's throwaway database password (the
|
|
417
403
|
// word postgres, present in hundreds of core files) as a leaked credential and refusing to
|
|
@@ -421,7 +407,7 @@ function discoverModuleTests({ modulesDir = MODULES_DIR, isEnabled = isModuleEna
|
|
|
421
407
|
// now says out loud.
|
|
422
408
|
const INTEGRATION_PARKED = new Set([
|
|
423
409
|
'acclaim_feed_evidence_db',
|
|
424
|
-
'bfg', 'bfg_write',
|
|
410
|
+
'bfg', 'bfg_write',
|
|
425
411
|
'claim_gate_open_blocker', 'claim_gate_rebase',
|
|
426
412
|
'clawback_confirmed_backlog', 'copy_desk_flags_db',
|
|
427
413
|
'government_board_membership_db', 'handle_machinery', 'hub_profile_leak',
|
|
@@ -4,21 +4,17 @@
|
|
|
4
4
|
// REVIEWED in a browser before /builder-ship pushes it toward the main game.
|
|
5
5
|
// (#927, ADR 0046 — the sandbox-first review step.)
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// the tree changed since the last staging, the builder is asked to stage +
|
|
17
|
-
// review again before the claim resolves. On approval (a TTY 'y' or an
|
|
18
|
-
// --approved re-run on a fresh stage) the gate returns a durable self-approval
|
|
19
|
-
// record that ship.js persists on the task (#1048) — box AND local alike.
|
|
7
|
+
// The preview is LOCAL (task 1056): a builder on a full clone runs the ADR 0052
|
|
8
|
+
// game-only entry privately at http://localhost:<port> via
|
|
9
|
+
// scripts/gds/local-preview.js, and "staging" (re)starts that process to pick up
|
|
10
|
+
// the current working tree, health-checks it, records a marker of exactly WHAT
|
|
11
|
+
// tree state was staged, and hands back the URL. ship.js consults the marker
|
|
12
|
+
// through runShipGate(): if the tree changed since the last staging, the builder
|
|
13
|
+
// is asked to stage + review again before the claim resolves. On approval (a TTY
|
|
14
|
+
// 'y' or an --approved re-run on a fresh stage) the gate returns a durable
|
|
15
|
+
// self-approval record that ship.js persists on the task (#1048).
|
|
20
16
|
//
|
|
21
|
-
// Usage (a
|
|
17
|
+
// Usage (a checkout with no game has no sandbox; the script says so):
|
|
22
18
|
// node scripts/gds/sandbox-stage.js # stage + print the review URL
|
|
23
19
|
// node scripts/gds/sandbox-stage.js status # preview state + is the marker current?
|
|
24
20
|
// node scripts/gds/sandbox-stage.js url # just print the sandbox URL
|
|
@@ -31,13 +27,10 @@ const path = require('node:path');
|
|
|
31
27
|
const readline = require('node:readline');
|
|
32
28
|
const localPreview = require('./local-preview-lib');
|
|
33
29
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
const BOX_ENV_PATH = '/etc/otb/box.env';
|
|
37
|
-
const PREVIEW_WRAPPER = '/usr/local/bin/box-preview';
|
|
38
|
-
const PREVIEW_UNIT = 'box-game-preview.service';
|
|
30
|
+
// The default health URL waitHealthy probes when a caller supplies none; the
|
|
31
|
+
// local context always passes its own (localPreview.healthUrl).
|
|
39
32
|
const HEALTH_URL = 'http://127.0.0.1:3000/';
|
|
40
|
-
const HEALTH_TIMEOUT_MS = 90_000; // first start
|
|
33
|
+
const HEALTH_TIMEOUT_MS = 90_000; // a cold first start can be slow
|
|
41
34
|
const HEALTH_POLL_MS = 2_000;
|
|
42
35
|
|
|
43
36
|
// Game surfaces the sandbox can actually show. The sandbox host serves the
|
|
@@ -47,17 +40,10 @@ const HEALTH_POLL_MS = 2_000;
|
|
|
47
40
|
// The GAME CLIENT directories the sandbox renders. These MOVED in the module
|
|
48
41
|
// carve (ADR 0083, BV1.R49 / task 1418): public/ -> modules/game/public/,
|
|
49
42
|
// src/world/ -> modules/game/world/, src/rooms/ -> modules/game/rooms/. The
|
|
50
|
-
// pre-carve spelling sat here matching NOTHING
|
|
51
|
-
//
|
|
52
|
-
// saw 'synced previewable surfaces', and got a sandbox serving whatever client
|
|
53
|
-
// was already on /workspace (task 1002062; found when a new /studio page 404'd).
|
|
43
|
+
// pre-carve spelling sat here matching NOTHING for 69 days (task 1002062), so
|
|
44
|
+
// tests/sandbox_stage.mjs pins the list against the real tree.
|
|
54
45
|
const PREVIEWABLE_DIRS = ['modules/game/public', 'modules/game/world', 'modules/game/rooms'];
|
|
55
|
-
// The
|
|
56
|
-
// is served as /x. pickVerifyTarget maps between the two, so it must be exactly
|
|
57
|
-
// one dir — hence its own constant rather than PREVIEWABLE_DIRS[0].
|
|
58
|
-
const PREVIEWABLE_STATIC_DIR = 'modules/game/public';
|
|
59
|
-
// The entrypoints that render the game. A sparse box (STARTER_SPARSE_PATHS) may
|
|
60
|
-
// legitimately lack these, which is why a miss here is not on its own an error.
|
|
46
|
+
// The entrypoints that render the game.
|
|
61
47
|
const PREVIEWABLE_ENTRYPOINTS = ['server.js', 'src/preview-server.js'];
|
|
62
48
|
// DERIVED, all three of them. They used to be three hand-maintained copies of the
|
|
63
49
|
// same list, and the carve updated none of them; deriving removes the drift class
|
|
@@ -66,40 +52,6 @@ const PREVIEWABLE_PREFIXES = PREVIEWABLE_DIRS.map((d) => `${d}/`);
|
|
|
66
52
|
const PREVIEWABLE_FILES = new Set(PREVIEWABLE_ENTRYPOINTS);
|
|
67
53
|
const PREVIEWABLE_PATHSPECS = [...PREVIEWABLE_DIRS, ...PREVIEWABLE_ENTRYPOINTS];
|
|
68
54
|
|
|
69
|
-
// On a box, box-game-preview.service has WorkingDirectory=/workspace and serves
|
|
70
|
-
// /workspace/public — the MAIN checkout. A Claude session, though, runs in a git
|
|
71
|
-
// WORKTREE under /workspace/.claude/worktrees/<name>, so its edits never touch
|
|
72
|
-
// /workspace and the sandbox re-served stale code (the #1068 "I don't feel them"
|
|
73
|
-
// bug). We sync the previewable surfaces from the session's branch into this
|
|
74
|
-
// served root before restarting. Kept as a constant so tests can point it at a
|
|
75
|
-
// temp dir.
|
|
76
|
-
const BOX_SERVED_ROOT = '/workspace';
|
|
77
|
-
|
|
78
|
-
function isDevBox(deps = {}) {
|
|
79
|
-
const f = deps.fs || fs;
|
|
80
|
-
return f.existsSync(deps.boxEnvPath || BOX_ENV_PATH);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Parse BOX_PREVIEW_HOSTNAME out of box.env text. Returns the hostname string
|
|
84
|
-
// or null (a quick-tunnel box has no preview hostname — ADR 0044 §Consequences).
|
|
85
|
-
function readPreviewHostname(envText) {
|
|
86
|
-
const m = /^\s*(?:export\s+)?BOX_PREVIEW_HOSTNAME=(.*)$/m.exec(String(envText || ''));
|
|
87
|
-
if (!m) return null;
|
|
88
|
-
const v = m[1].trim().replace(/^["']|["']$/g, '').trim();
|
|
89
|
-
return v || null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function previewUrl(deps = {}) {
|
|
93
|
-
const f = deps.fs || fs;
|
|
94
|
-
const p = deps.boxEnvPath || BOX_ENV_PATH;
|
|
95
|
-
try {
|
|
96
|
-
const host = readPreviewHostname(f.readFileSync(p, 'utf8'));
|
|
97
|
-
return host ? `https://${host}` : null;
|
|
98
|
-
} catch {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
55
|
function isPreviewableFile(file) {
|
|
104
56
|
const fp = String(file || '').replace(/\\/g, '/');
|
|
105
57
|
if (PREVIEWABLE_FILES.has(fp)) return true;
|
|
@@ -188,34 +140,17 @@ function writeMarker(marker, deps = {}) {
|
|
|
188
140
|
// review URL. The agent relays the URL, the human reviews, and
|
|
189
141
|
// the re-run passes on the now-fresh marker. Never hangs on
|
|
190
142
|
// stdin; never silently skips the review.
|
|
191
|
-
// gateActive = there IS a sandbox here to review on (a
|
|
192
|
-
// preview). The decision is identical for both; only HOW we stage + the URL differ.
|
|
143
|
+
// gateActive = there IS a sandbox here to review on (a local game preview).
|
|
193
144
|
function decideShipGate({ gateActive, previewUrl: url, previewable, stagedFresh, tty, skipStage }) {
|
|
194
145
|
if (skipStage) return { action: 'skip', reason: '--skip-stage' };
|
|
195
|
-
if (!gateActive) return { action: 'skip', reason: 'no sandbox here (
|
|
146
|
+
if (!gateActive) return { action: 'skip', reason: 'no sandbox here (no local game preview)' };
|
|
196
147
|
if (!previewable) return { action: 'skip', reason: 'no previewable game surfaces in the diff' };
|
|
197
|
-
if (!url) return { action: 'skip', reason: 'preview has no URL (
|
|
148
|
+
if (!url) return { action: 'skip', reason: 'preview has no URL (local preview unavailable)' };
|
|
198
149
|
if (stagedFresh) return { action: 'pass', reason: 'working tree already staged' };
|
|
199
150
|
return tty ? { action: 'review', reason: 'stage + review before ship' }
|
|
200
151
|
: { action: 'stage_abort', reason: 'non-interactive: stage, then pause for review' };
|
|
201
152
|
}
|
|
202
153
|
|
|
203
|
-
function restartPreview(deps = {}) {
|
|
204
|
-
const run = deps.execFileSync || execFileSync;
|
|
205
|
-
const f = deps.fs || fs;
|
|
206
|
-
// The baked wrapper is the expected path on every box (cloud-init +
|
|
207
|
-
// box-source-fetch self-heal bake it to /usr/local/bin, and it handles its
|
|
208
|
-
// own sudo). The raw systemctl branch is only a degraded fallback.
|
|
209
|
-
if (f.existsSync(deps.previewWrapper || PREVIEW_WRAPPER)) {
|
|
210
|
-
run(deps.previewWrapper || PREVIEW_WRAPPER, ['restart'], { stdio: 'inherit' });
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
const needSudo = typeof process.getuid === 'function' && process.getuid() !== 0;
|
|
214
|
-
const cmd = needSudo ? 'sudo' : 'systemctl';
|
|
215
|
-
const argv = needSudo ? ['-n', 'systemctl', 'restart', PREVIEW_UNIT] : ['restart', PREVIEW_UNIT];
|
|
216
|
-
run(cmd, argv, { stdio: 'inherit' });
|
|
217
|
-
}
|
|
218
|
-
|
|
219
154
|
// task 1002607: the discriminator probe URL — same origin as the health URL, at
|
|
220
155
|
// the API healthz path only the FULL server mounts. Pure + exported for tests.
|
|
221
156
|
function apiHealthzProbeUrl(healthUrl) {
|
|
@@ -224,9 +159,9 @@ function apiHealthzProbeUrl(healthUrl) {
|
|
|
224
159
|
|
|
225
160
|
// task 1002607: is the thing answering the health URL actually the MAIN server?
|
|
226
161
|
// The game-only preview entry (ADR 0052) mounts no /api/bongos, so a 200 from the
|
|
227
|
-
// API healthz proves the answerer is the full server —
|
|
228
|
-
//
|
|
229
|
-
//
|
|
162
|
+
// API healthz proves the answerer is the full server — which can hold the very
|
|
163
|
+
// port the preview was asked to bind — not the preview. A probe error/404 means
|
|
164
|
+
// "not the main server", which is the expected preview answer.
|
|
230
165
|
async function answeredByMainServer(healthUrl, fetchImpl) {
|
|
231
166
|
const probe = apiHealthzProbeUrl(healthUrl);
|
|
232
167
|
if (!probe) return false;
|
|
@@ -268,187 +203,13 @@ async function waitHealthy(deps = {}) {
|
|
|
268
203
|
return { ok: false, reason: lastErr };
|
|
269
204
|
}
|
|
270
205
|
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const cwd = deps.cwd || process.cwd();
|
|
277
|
-
try {
|
|
278
|
-
return run('git', ['rev-parse', '--show-toplevel'], { cwd, encoding: 'utf8' }).trim() || null;
|
|
279
|
-
} catch {
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Box-only (#1068 gap 1): materialize the session worktree's previewable game
|
|
285
|
-
// surfaces into the served checkout (/workspace) so the box preview shows the
|
|
286
|
-
// SESSION's edits, not stale main. The served root and the worktree share one
|
|
287
|
-
// object store (linked worktree), so `git -C /workspace checkout <branch> -- <paths>`
|
|
288
|
-
// brings the branch's committed content for exactly the previewable pathspecs.
|
|
289
|
-
// ship.js preflight already proved the worktree is clean (committed == working
|
|
290
|
-
// tree), so committed content is what the builder means. No-op when the session
|
|
291
|
-
// checkout IS the served root (a box where the builder edits /workspace directly,
|
|
292
|
-
// or a laptop). Returns { ok, synced, reason?, branch? }.
|
|
293
|
-
// Canonicalize a path through symlinks for a same-directory comparison. Needed
|
|
294
|
-
// because `git rev-parse --show-toplevel` (sessionCheckoutRoot) returns the REAL
|
|
295
|
-
// path, while servedRoot may be a symlink — on macOS `/tmp` -> `/private/var`
|
|
296
|
-
// (idea #278), or any symlinked served root. path.resolve does NOT resolve
|
|
297
|
-
// symlinks, so the raw comparison false-negatives "same dir" and the code would
|
|
298
|
-
// wrongly try to git-sync a checkout onto itself. Falls back to path.resolve when
|
|
299
|
-
// the path doesn't exist yet (realpathSync throws ENOENT).
|
|
300
|
-
// On Windows the two sides arrive in different spellings of the same directory:
|
|
301
|
-
// `git rev-parse --show-toplevel` answers with forward slashes and the LONG filename,
|
|
302
|
-
// while a configured servedRoot may be backslashed and 8.3-short (C:\Users\RUNNER~1\…).
|
|
303
|
-
// path.resolve folds the separators and realpathSync.native — unlike realpathSync —
|
|
304
|
-
// expands 8.3, so the comparison is about directories rather than renderings of them.
|
|
305
|
-
// Without both, the same-dir check false-negatived and the box git-synced a checkout
|
|
306
|
-
// onto itself, the very thing this function exists to avoid (task 1003529).
|
|
307
|
-
function realPathOr(p) {
|
|
308
|
-
try { return fs.realpathSync.native(path.resolve(p)); } catch { return path.resolve(p); }
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function syncWorktreeToServed(deps = {}) {
|
|
312
|
-
const run = deps.execFileSync || execFileSync;
|
|
313
|
-
const servedRoot = deps.servedRoot || BOX_SERVED_ROOT;
|
|
314
|
-
const sessionRoot = sessionCheckoutRoot(deps);
|
|
315
|
-
if (!sessionRoot || realPathOr(sessionRoot) === realPathOr(servedRoot)) {
|
|
316
|
-
return { ok: true, synced: false, reason: 'session checkout is the served checkout' };
|
|
317
|
-
}
|
|
318
|
-
let branch;
|
|
319
|
-
try {
|
|
320
|
-
branch = run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: sessionRoot, encoding: 'utf8' }).trim();
|
|
321
|
-
} catch {
|
|
322
|
-
branch = null;
|
|
323
|
-
}
|
|
324
|
-
if (!branch || branch === 'HEAD') {
|
|
325
|
-
return { ok: false, synced: false, reason: 'could not resolve the session branch (detached HEAD?)' };
|
|
326
|
-
}
|
|
327
|
-
// Check out each previewable pathspec INDEPENDENTLY: `git checkout <ref> -- a b c`
|
|
328
|
-
// fails ENTIRELY if any single pathspec is absent from the ref, and a sparse
|
|
329
|
-
// box (STARTER_SPARSE_PATHS) may legitimately lack server.js / src/preview-server.js.
|
|
330
|
-
// Per-pathspec lets us sync what exists and skip what doesn't; verifyServedFresh
|
|
331
|
-
// is the safety net that catches a sync that didn't actually land. First restore
|
|
332
|
-
// each path from the served checkout's own HEAD (undo any prior ship's leftovers),
|
|
333
|
-
// then overlay the session branch's content.
|
|
334
|
-
let synced = false;
|
|
335
|
-
let lastErr = null;
|
|
336
|
-
// task 1002062: this collapsed to a single boolean, so a run that synced ONLY
|
|
337
|
-
// the entrypoints and missed every client directory reported plain success.
|
|
338
|
-
// Track them by name — the caller warns on a missed DIRECTORY, which is the
|
|
339
|
-
// shape a stale pathspec list takes.
|
|
340
|
-
const missed = [];
|
|
341
|
-
for (const ps of PREVIEWABLE_PATHSPECS) {
|
|
342
|
-
try {
|
|
343
|
-
run('git', ['-C', servedRoot, 'checkout', '--', ps], { stdio: 'ignore' });
|
|
344
|
-
} catch {
|
|
345
|
-
/* pathspec absent in the served HEAD — nothing to restore, fine */
|
|
346
|
-
}
|
|
347
|
-
try {
|
|
348
|
-
run('git', ['-C', servedRoot, 'checkout', branch, '--', ps], { stdio: 'ignore' });
|
|
349
|
-
synced = true;
|
|
350
|
-
} catch (e) {
|
|
351
|
-
lastErr = e; // pathspec absent in the branch (sparse/missing) — skip it
|
|
352
|
-
missed.push(ps);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
if (!synced) {
|
|
356
|
-
return { ok: false, synced: false, missed, reason: `git checkout ${branch} -- <previewable> in ${servedRoot} synced nothing${lastErr ? ` (${lastErr.message})` : ''}` };
|
|
357
|
-
}
|
|
358
|
-
// A missed ENTRYPOINT is normal on a sparse box. A missed client DIRECTORY means
|
|
359
|
-
// the sandbox is about to serve a client this stage never touched.
|
|
360
|
-
const missedDirs = missed.filter((m) => PREVIEWABLE_DIRS.includes(m));
|
|
361
|
-
return { ok: true, synced: true, branch, missed, missedDirs };
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Pick the smallest cheap-to-compare changed previewable static file to verify,
|
|
365
|
-
// and the URL path the preview serves it at (public/game/x.js -> /game/x.js).
|
|
366
|
-
// Returns { relPath, urlPath } or null when there's no served static change.
|
|
367
|
-
function pickVerifyTarget(deps = {}) {
|
|
368
|
-
const run = deps.execFileSync || execFileSync;
|
|
369
|
-
const cwd = deps.cwd || process.cwd();
|
|
370
|
-
const f = deps.fs || fs;
|
|
371
|
-
try {
|
|
372
|
-
// task 1002062: this hardcoded 'public' three times, independently of the
|
|
373
|
-
// constants above, so it kept the pre-carve spelling even after they moved.
|
|
374
|
-
// Derived from PREVIEWABLE_STATIC_DIR now, so there is one place to change.
|
|
375
|
-
const staticDir = deps.staticDir || PREVIEWABLE_STATIC_DIR;
|
|
376
|
-
const prefix = `${staticDir}/`;
|
|
377
|
-
const out = run('git', ['status', '--porcelain', '--', staticDir], { cwd, encoding: 'utf8' });
|
|
378
|
-
let chosen = out
|
|
379
|
-
.split('\n')
|
|
380
|
-
.map((line) => line.slice(3).replace(/^"|"$/g, '').trim())
|
|
381
|
-
.find((rel) => rel && rel.startsWith(prefix) && f.existsSync(path.join(cwd, rel)));
|
|
382
|
-
if (!chosen) {
|
|
383
|
-
// committed-only change: fall back to the diff vs the frozen origin/main
|
|
384
|
-
const diff = run('git', ['diff', '--name-only', 'origin/main...HEAD', '--', staticDir], { cwd, encoding: 'utf8' });
|
|
385
|
-
chosen = diff
|
|
386
|
-
.split('\n')
|
|
387
|
-
.map((s) => s.trim())
|
|
388
|
-
.find((p) => p.startsWith(prefix) && f.existsSync(path.join(cwd, p)));
|
|
389
|
-
}
|
|
390
|
-
if (!chosen) return null;
|
|
391
|
-
return { relPath: chosen, urlPath: '/' + chosen.slice(prefix.length) };
|
|
392
|
-
} catch {
|
|
393
|
-
return null;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Box-only (#1068 gap 1b): after staging, fetch one changed static file from the
|
|
398
|
-
// RUNNING preview over loopback (bypasses Cloudflare + browser cache, so it
|
|
399
|
-
// proves the SERVER is fresh) and confirm it byte-matches the worktree. Returns
|
|
400
|
-
// { ok, verified?|skipped?, reason? }. A mismatch means the sandbox is serving
|
|
401
|
-
// stale code despite the restart — fail loudly.
|
|
402
|
-
async function verifyServedFresh(deps = {}) {
|
|
403
|
-
const fetchImpl = deps.fetchImpl || fetch;
|
|
404
|
-
const f = deps.fs || fs;
|
|
405
|
-
const cwd = deps.cwd || process.cwd();
|
|
406
|
-
const base = (deps.healthUrl || HEALTH_URL).replace(/\/$/, '');
|
|
407
|
-
const target = pickVerifyTarget(deps);
|
|
408
|
-
if (!target) return { ok: true, skipped: true, reason: `no served static change to verify (e.g. only ${PREVIEWABLE_DIRS.filter((d) => d !== PREVIEWABLE_STATIC_DIR).join(' or ')} changed)` };
|
|
409
|
-
let want;
|
|
410
|
-
try {
|
|
411
|
-
want = f.readFileSync(path.join(cwd, target.relPath), 'utf8');
|
|
412
|
-
} catch {
|
|
413
|
-
return { ok: true, skipped: true, reason: 'could not read the worktree file to compare' };
|
|
414
|
-
}
|
|
415
|
-
try {
|
|
416
|
-
const res = await fetchImpl(`${base}${target.urlPath}`, {
|
|
417
|
-
headers: { 'Cache-Control': 'no-cache' },
|
|
418
|
-
signal: AbortSignal.timeout(5_000),
|
|
419
|
-
});
|
|
420
|
-
if (res.status >= 400) return { ok: false, reason: `preview returned HTTP ${res.status} for ${target.urlPath}` };
|
|
421
|
-
const got = await res.text();
|
|
422
|
-
if (got === want) return { ok: true, verified: target.relPath };
|
|
423
|
-
return { ok: false, reason: `served ${target.urlPath} does NOT match your worktree ${target.relPath} — the sandbox is serving stale code` };
|
|
424
|
-
} catch (e) {
|
|
425
|
-
return { ok: false, reason: `could not fetch ${target.urlPath} from the running preview to verify freshness: ${e.message}` };
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Which sandbox does this machine have? A box (ADR 0044) is keyed by ANY box
|
|
430
|
-
// marker (localPreview.onBox: box.env, the box-preview wrapper, or the
|
|
431
|
-
// box-game-preview.service unit — broader than box.env alone so a box with a
|
|
432
|
-
// lost/unreadable box.env is STILL routed to the box branch, never to localhost
|
|
433
|
-
// — task 1443); a full LOCAL clone (task 1056) is keyed by the presence of the
|
|
434
|
-
// game-only entry (src/preview-server.js). Returns a uniform context
|
|
435
|
-
// { kind, url, healthUrl, restart(deps) } — or null when there is NO sandbox
|
|
436
|
-
// here (a partial clone with no game). On a box the url comes from box.env's
|
|
437
|
-
// BOX_PREVIEW_HOSTNAME; if that's missing the url is null and stage() returns a
|
|
438
|
-
// clear "no sandbox hostname" message — but it is NEVER a localhost URL the
|
|
439
|
-
// builder's browser can't reach. deps.previewContext short-circuits for
|
|
440
|
-
// hermetic tests. The box branch is checked FIRST: a box also has the game
|
|
441
|
-
// entry, so order is what keeps onBox from being shadowed by hasGameEntry.
|
|
206
|
+
// Which sandbox does this machine have? A full LOCAL clone (task 1056) is keyed by
|
|
207
|
+
// the presence of the game-only entry (src/preview-server.js). Returns a uniform
|
|
208
|
+
// context { kind, url, healthUrl, restart(deps) } — or null when there is NO
|
|
209
|
+
// sandbox here (a checkout with no game). deps.previewContext short-circuits for
|
|
210
|
+
// hermetic tests.
|
|
442
211
|
function resolvePreviewContext(deps = {}) {
|
|
443
212
|
if (deps.previewContext !== undefined) return deps.previewContext;
|
|
444
|
-
if (localPreview.onBox(deps)) {
|
|
445
|
-
return {
|
|
446
|
-
kind: 'box',
|
|
447
|
-
url: previewUrl(deps),
|
|
448
|
-
healthUrl: HEALTH_URL,
|
|
449
|
-
restart: (d) => restartPreview(d),
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
213
|
if (localPreview.hasGameEntry(deps)) {
|
|
453
214
|
return {
|
|
454
215
|
kind: 'local',
|
|
@@ -466,37 +227,9 @@ function resolvePreviewContext(deps = {}) {
|
|
|
466
227
|
// answer, write the staged marker. Returns { ok, url, kind, reason? }.
|
|
467
228
|
async function stage({ taskId } = {}, deps = {}) {
|
|
468
229
|
const ctx = resolvePreviewContext(deps);
|
|
469
|
-
if (!ctx) return { ok: false, reason: 'no sandbox here (
|
|
470
|
-
if (!ctx.url) {
|
|
471
|
-
|
|
472
|
-
? 'no sandbox hostname on this box — box.env has no BOX_PREVIEW_HOSTNAME (a quick-tunnel box, or box.env is missing/unreadable). Run `box-preview status` for your URL, or ask the operator to re-provision with BOX_NAMED_TUNNEL=1 (ADR 0044). NOT a localhost link — that is unreachable from your browser on a box.'
|
|
473
|
-
: 'local preview URL unavailable' };
|
|
474
|
-
}
|
|
475
|
-
console.log(ctx.kind === 'local'
|
|
476
|
-
? 'Staging your working tree on your local sandbox (starting the game preview)...'
|
|
477
|
-
: 'Staging your working tree on the sandbox (syncing your worktree, then restarting the game preview)...');
|
|
478
|
-
// #1068 gap 1: a box serves /workspace, not the session worktree. Sync the
|
|
479
|
-
// previewable surfaces from the branch into /workspace BEFORE the restart so
|
|
480
|
-
// the preview picks up the SESSION's edits. hard:true → the ship gate aborts
|
|
481
|
-
// (a stale sandbox review is worse than no review). Local already serves the
|
|
482
|
-
// worktree, so this is box-only.
|
|
483
|
-
if (ctx.kind === 'box') {
|
|
484
|
-
const sync = (deps.syncWorktreeToServed || syncWorktreeToServed)(deps);
|
|
485
|
-
if (!sync.ok) {
|
|
486
|
-
return { ok: false, hard: true, reason: `could not sync your worktree to the served checkout (${sync.reason}); the sandbox would show stale ${deps.servedRoot || BOX_SERVED_ROOT} code` };
|
|
487
|
-
}
|
|
488
|
-
if (sync.synced) console.log(` synced previewable surfaces from ${sync.branch} into ${deps.servedRoot || BOX_SERVED_ROOT}`);
|
|
489
|
-
// task 1002062 — the tell that went unsaid for 69 days. Staging "succeeded"
|
|
490
|
-
// while every game-client directory matched nothing, so the sandbox served a
|
|
491
|
-
// client the stage never touched and the only hint was a freshness-verify skip.
|
|
492
|
-
if (sync.missedDirs && sync.missedDirs.length) {
|
|
493
|
-
console.warn(
|
|
494
|
-
` ⚠ these previewable directories matched NOTHING in ${sync.branch}: ${sync.missedDirs.join(', ')}`
|
|
495
|
-
+ ' — the sandbox will serve whatever client is already on the served checkout.'
|
|
496
|
-
+ ' If this repo carved or renamed them, PREVIEWABLE_DIRS in sandbox-stage.js is stale.'
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
230
|
+
if (!ctx) return { ok: false, reason: 'no sandbox here (this checkout has no local game preview)' };
|
|
231
|
+
if (!ctx.url) return { ok: false, reason: 'local preview URL unavailable' };
|
|
232
|
+
console.log('Staging your working tree on your local sandbox (starting the game preview)...');
|
|
500
233
|
try {
|
|
501
234
|
ctx.restart(deps);
|
|
502
235
|
} catch (e) {
|
|
@@ -504,19 +237,7 @@ async function stage({ taskId } = {}, deps = {}) {
|
|
|
504
237
|
}
|
|
505
238
|
const health = await waitHealthy({ ...deps, healthUrl: ctx.healthUrl });
|
|
506
239
|
if (!health.ok) {
|
|
507
|
-
|
|
508
|
-
return { ok: false, reason: `preview did not come up healthy (${health.reason}) — check '${where}'` };
|
|
509
|
-
}
|
|
510
|
-
// #1068 gap 1b: confirm the restarted box preview is actually serving the
|
|
511
|
-
// synced bytes (loopback fetch, cache-bypassed). A mismatch is a hard fail —
|
|
512
|
-
// claiming "staged" when the sandbox shows stale code is the bug we're killing.
|
|
513
|
-
if (ctx.kind === 'box') {
|
|
514
|
-
const v = await (deps.verifyServedFresh || verifyServedFresh)({ ...deps, healthUrl: ctx.healthUrl });
|
|
515
|
-
if (!v.ok) {
|
|
516
|
-
return { ok: false, hard: true, reason: `staged but FAILED freshness verification: ${v.reason}` };
|
|
517
|
-
}
|
|
518
|
-
if (v.verified) console.log(` verified the sandbox is serving your edits (${v.verified} matches)`);
|
|
519
|
-
else if (v.skipped) console.log(` freshness verify skipped (${v.reason})`);
|
|
240
|
+
return { ok: false, reason: `preview did not come up healthy (${health.reason}) — check 'node scripts/gds/local-preview.js logs'` };
|
|
520
241
|
}
|
|
521
242
|
writeMarker({
|
|
522
243
|
stamp: computeTreeStamp(deps),
|
|
@@ -544,8 +265,8 @@ function promptYesNo(question) {
|
|
|
544
265
|
// to present the URL as its own callout in chat. Keep the URL ALONE on its own
|
|
545
266
|
// line, framed by a rule, so it stands out from the surrounding log noise and
|
|
546
267
|
// stays clickable. This is the moment the change is "ready to review", so the
|
|
547
|
-
// link has to be impossible to miss.
|
|
548
|
-
//
|
|
268
|
+
// link has to be impossible to miss. The URL (http://localhost:<port>) is
|
|
269
|
+
// passed in.
|
|
549
270
|
function printReviewBlock(url, taskId) {
|
|
550
271
|
const bar = '─'.repeat(64);
|
|
551
272
|
console.log('');
|
|
@@ -563,8 +284,7 @@ function printReviewBlock(url, taskId) {
|
|
|
563
284
|
|
|
564
285
|
// Build the recorded-approval payload ship.js threads into the resolve call
|
|
565
286
|
// (#1048). The server stamps `by`/`at` authoritatively — we only supply the
|
|
566
|
-
// content stamp of what was reviewed, the sandbox URL
|
|
567
|
-
// it was approved.
|
|
287
|
+
// content stamp of what was reviewed, the sandbox URL, and HOW it was approved.
|
|
568
288
|
function approvalRecord(method, url, deps) {
|
|
569
289
|
return { stamp: computeTreeStamp(deps), url, method };
|
|
570
290
|
}
|
|
@@ -573,8 +293,7 @@ function approvalRecord(method, url, deps) {
|
|
|
573
293
|
// Returns { ok: true } to proceed or { ok: false, reason } to abort the ship
|
|
574
294
|
// with the claim still active so the builder can iterate. On a recorded
|
|
575
295
|
// approval it also returns { sandboxReview } so ship.js can persist the
|
|
576
|
-
// self-approval as a durable fact on the task (#1048)
|
|
577
|
-
// clone (task 1056).
|
|
296
|
+
// self-approval as a durable fact on the task (#1048).
|
|
578
297
|
//
|
|
579
298
|
// `approved` (the --approved flag) records the builder's sign-off, but ONLY
|
|
580
299
|
// when the current game state is already staged-fresh (decideShipGate → 'pass')
|
|
@@ -616,18 +335,7 @@ async function runShipGate({ taskId, files, skipStage, tty, approved }, deps = {
|
|
|
616
335
|
// Reuse the already-resolved context so we don't resolve it twice.
|
|
617
336
|
const staged = await stage({ taskId }, { ...deps, previewContext: ctx });
|
|
618
337
|
if (!staged.ok) {
|
|
619
|
-
//
|
|
620
|
-
// or the preview verifiably served stale code) means a review here would be
|
|
621
|
-
// a LIE — abort with the claim intact so the builder fixes it.
|
|
622
|
-
if (staged.hard) {
|
|
623
|
-
return {
|
|
624
|
-
ok: false,
|
|
625
|
-
review: true,
|
|
626
|
-
url: staged.url,
|
|
627
|
-
reason: `sandbox staging could not be trusted (${staged.reason}) — fix it, then re-run /builder-ship (or --skip-stage to bypass the review). Your claim is still active`,
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
// task 1002607 (§4.4 station 9): a SOFT failure (the preview didn't come up)
|
|
338
|
+
// task 1002607 (§4.4 station 9): a staging failure (the preview didn't come up)
|
|
631
339
|
// used to WARN one stderr line and return ok — the review gate silently
|
|
632
340
|
// turned itself off and the ship went on as if the review had happened.
|
|
633
341
|
// Fail closed like the smoke gate (task 1002605): abort with the claim
|
|
@@ -672,7 +380,7 @@ async function runShipGate({ taskId, files, skipStage, tty, approved }, deps = {
|
|
|
672
380
|
// (the ADR 0052 game-only server); the core repo does not ship one. Name what is
|
|
673
381
|
// actually missing and both real causes. Exported so the wording is pinned.
|
|
674
382
|
const NO_SANDBOX_MSG =
|
|
675
|
-
'No sandbox here: this
|
|
383
|
+
'No sandbox here: this checkout has no game preview entry (src/preview-server.js — the ADR 0052 game-only server).';
|
|
676
384
|
const NO_SANDBOX_DETAIL =
|
|
677
385
|
'That entry file comes from an instance\'s product layer. This repo does not ship one, and a partial/sparse clone of an instance that has one also lacks it — nothing here can be staged or previewed.';
|
|
678
386
|
|
|
@@ -682,7 +390,7 @@ async function cliMain() {
|
|
|
682
390
|
const NO_SANDBOX = NO_SANDBOX_MSG;
|
|
683
391
|
|
|
684
392
|
if (cmd === 'url') {
|
|
685
|
-
console.log(ctx && ctx.url ? ctx.url : '(no sandbox here —
|
|
393
|
+
console.log(ctx && ctx.url ? ctx.url : '(no sandbox here — no local game preview in this checkout)');
|
|
686
394
|
return;
|
|
687
395
|
}
|
|
688
396
|
if (cmd === 'status') {
|
|
@@ -691,7 +399,7 @@ async function cliMain() {
|
|
|
691
399
|
const stamp = computeTreeStamp();
|
|
692
400
|
const fresh = Boolean(stamp && marker && marker.stamp === stamp);
|
|
693
401
|
console.log(`sandbox kind: ${ctx.kind}`);
|
|
694
|
-
console.log(`sandbox URL: ${ctx.url || '(none
|
|
402
|
+
console.log(`sandbox URL: ${ctx.url || '(none)'}`);
|
|
695
403
|
if (ctx.kind === 'local') {
|
|
696
404
|
console.log(`local preview: ${localPreview.isRunning() ? `running (pid ${localPreview.readPid()})` : 'stopped — staging will start it'}`);
|
|
697
405
|
}
|
|
@@ -723,9 +431,6 @@ async function cliMain() {
|
|
|
723
431
|
}
|
|
724
432
|
|
|
725
433
|
module.exports = {
|
|
726
|
-
isDevBox,
|
|
727
|
-
readPreviewHostname,
|
|
728
|
-
previewUrl,
|
|
729
434
|
isPreviewableFile,
|
|
730
435
|
anyPreviewable,
|
|
731
436
|
computeTreeStamp,
|
|
@@ -737,16 +442,9 @@ module.exports = {
|
|
|
737
442
|
resolvePreviewContext,
|
|
738
443
|
stage,
|
|
739
444
|
runShipGate,
|
|
740
|
-
BOX_ENV_PATH,
|
|
741
|
-
BOX_SERVED_ROOT,
|
|
742
445
|
PREVIEWABLE_PATHSPECS,
|
|
743
446
|
PREVIEWABLE_DIRS,
|
|
744
447
|
PREVIEWABLE_ENTRYPOINTS,
|
|
745
|
-
PREVIEWABLE_STATIC_DIR,
|
|
746
|
-
sessionCheckoutRoot,
|
|
747
|
-
syncWorktreeToServed,
|
|
748
|
-
pickVerifyTarget,
|
|
749
|
-
verifyServedFresh,
|
|
750
448
|
// task 1002607: the health truth table + the honest no-sandbox wording, pinned.
|
|
751
449
|
waitHealthy,
|
|
752
450
|
apiHealthzProbeUrl,
|