Deploy guard: build_app_bundle refuses stale/unrecorded trees - #994
Conversation
…rded trees (#992) The 2026-09-10 deploy race: a local-tree deploy overwrote the recorded-main dist-app swap and a fixed crash returned — an unrecorded build served over a recorded fix. The deploy flow now enforces the doctrine (recorded main is canonical) at the moment of building: - Pre-flight: fetch origin; refuse (exit 1, named reason + remedy) when HEAD ≠ origin/main or the tree is dirty. Override via AMICODE_DEPLOY_OVERRIDE — the reason MUST be non-empty (empty/missing with the flag set refuses: no silent overrides) and is recorded. - Deploy manifest: every stage stamps dist-app/deploy.json {commit, branch, dirty, override_reason, built_at, deployed_by} so the served dist always traces to a recorded commit. - Known-fixes check at deploy time: the #964 known-fixed hunks (the #929 3-arg translate callback, the #832 diff_version shape + guarded delete, the #832 exportTrace locales) are asserted against the overlay before any build; a tree missing a recorded fix refuses with the named-remedy shape. NEVER overridable. deploy_guard.mjs mirrors the fixture list from test/overlay_known_fixes_964.test.ts (the source of record) and the test suite cross-checks the two lists cannot drift silently. The decision logic (evaluatePreflight, checkKnownFixes, buildDeployManifest, headRelation) lives in scripts/deploy_guard.mjs as pure functions, driven by both the script and test/deploy_guard.test.ts.
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…uilds are not deploys) The vsix-gate's packaging lane runs build:app from the PR MERGE REF — legitimately ahead of origin/main and shallow-cloned, so the #992 pre-flight refused and broke packaging. That lane is a packaging build, not a deploy: under CI the stale/dirty checks run advisory (printed, recorded as the manifest's override_reason with the merge ref + branch) while the #964 known-fixes check stays enforcing. Also unshallow-fetch before the ancestry probes when the clone is shallow, so the relation label is honest in both contexts.
|
Second commit Live incident worth flagging (pre-existing, #964 class): running the full extension suite ( |
Closes #992.
What
The 2026-09-10 deploy race — a local-tree deploy overwrote the recorded-main dist-app swap and the #987 crash returned — gets enforced at the moment of building:
packages/extension/scripts/build_app_bundle.mjs): fetch origin; refuse (exit 1, named reason + remedy) when HEAD ≠ origin/main (behind/diverged/ahead) or the tree is dirty. Override viaAMICODE_DEPLOY_OVERRIDE=<reason>: empty/missing reason with the flag set refuses (no silent overrides); a valid override proceeds but is recorded.dist-app/deploy.json—{commit, branch, dirty, override_reason, built_at, deployed_by}— so the served dist always traces to a recorded commit.deploy_guard.mjsmirrors the fixture list fromtest/overlay_known_fixes_964.test.ts(source of record); the test suite cross-checks the two lists can't drift silently.Shape
The decision logic (
evaluatePreflight,checkKnownFixes,buildDeployManifest,headRelation) lives inpackages/extension/scripts/deploy_guard.mjsas pure functions, driven by both the script andtest/deploy_guard.test.ts.Verification (all run live)
vitest run test/deploy_guard.test.ts test/overlay_known_fixes_964.test.ts→ 20/20 passed: refuse-on-behind, refuse-on-diverged, refuse-on-dirty, refuse-on-empty-override, refuse-on-whitespace-override, proceed-with-recorded-override, proceed-clean, manifest shape (override_reason serializes null), known-fixes pass + per-file refusals, fixture-list parity, headRelation mapping.AMICODE_DEPLOY_OVERRIDE=(empty) → exit 1, empty-override refusal addeddeploy.jsonNotes
AMICODE_DEPLOY_OVERRIDEwith the reason stamped in the serveddeploy.json— after this merges, the next deploy should run clean (no override).deploy.jsonbefore overwrite). Not in this slice.