Phase GSW-FIX — Governance-sync write-path order-of-operations
Status: Reviewed → pass (GSW-r3). GSW-FIX-a is spec-only and now frozen; no product
code lands in this phase. GSW-FIX-b (Auto) is cleared to build mechanically against this contract.
phase: GSW-FIX
outputs:
- id: gsw-fix-governance-sync-write-path
path: docs/archive/phases/PHASE-GSW-FIX-GOVERNANCE-SYNC-WRITE-PATH.md
frozen: true
frozen_inputs:
- id: phase-9a5-commit-strategy
path: docs/archive/phases/PHASE-9A-5-GOVERNANCE-HYGIENE-AGENT-OUTLINE.md
- id: governance-hygiene-engine
path: tools/governance_hygiene/engine.py
- id: muse-git-mirror-adapter
path: adapters/muse_git_mirror/adapter.py
- id: muse-only-adapter
path: adapters/muse_only/adapter.py
- id: git-only-adapter
path: adapters/git_only/adapter.py
- id: adapter-base
path: adapters/base.py
- id: governance-sync-cli
path: cli/commands/governance_sync.py
- id: kit-spec-freeze-policy
path: docs/OVERSEER-KIT-SPEC.md#6
- id: test-tiers
path: policy/test-tiers.yaml
- id: decision-tiers
path: policy/tiers.yaml
- id: gfg-freshness-gate
path: docs/archive/phases/PHASE-GFG-GOVERNANCE-FRESHNESS-GATE.md
review_stamp:
reviewed_at: '2026-07-31T14:13:22Z'
verdict: pass
reviewer_mode: agent
reviewer_model: thinking-high
reviewer_provider: local
kit_version: 0.1.0
artifact_digest: sha256:63cfd1767a566418bec6a98abbe04690be5e3049aa0dc9a75ced8838dba0a532
Downstream edge: GSW-FIX-b treats this document as ground truth without re-deriving it
(SPEC §6 mandatory reviewed freeze). It closes the permanent gap exposed live on 2026-07-31 while
dogfooding PMHF land-b: ok governance-sync --write on muse+git-mirror always fails because
_apply_plan writes doc patches before branch setup, then commit_feature runs a Muse
checkout that refuses dirty tracked files, and fail-closed rollback restores docs but strands
git on the sync branch.
Review record (§6.2): every freeze-review finding MUST cite file+line per SPEC §6; uncited
findings are invalid and are discarded. Fixes during the loop are Tier 1 (feature branch); merge to
main is Tier 3 and is never part of this loop.
| Round | Reviewer | Verdict | Resolution |
|---|---|---|---|
| GSW-r1 | Freeze-review loop (checklist + thinking, thinking-high) |
findings | R1-M1 realign-after-branch-switch underspecified vs today’s original-branch realign (realign.py / adapter.realign --branch muse main); R1-M2 marker-before-commit contradicts GFG §GFG.5.3 “Must not stamp on mid-apply failure”. Fixed in-doc. |
| GSW-r2 | Freeze-review loop (checklist + thinking, thinking-high) |
findings | R2-M1 non-goal “Changing … GFG marker semantics” contradicted §GSW.3.4 mid-apply stamp amendment; GFG missing from frozen_inputs. Fixed in-doc. |
| GSW-r3 | Freeze-review loop (checklist + thinking, thinking-high) |
pass | Checklist gate clean (0 findings). Semantic re-read: R1-M1/R1-M2/R2-M1 RESOLVED; order capture → realign → ensure branch → write → commit → marker → push; dual-HEAD + rollback + three-regime dirty-tree --write matrix present; no security/irreversible/real_money/gates_tier3 escalation (Tier-1 CLI fix; merge remains Tier 3). Stamp written by ok review --freeze. |
§GSW.0 — Simple summary
At the end of a work session, a helper is supposed to update two living notes and save them on a side branch. Today, on the Muse+Git setup, that helper first edits the notes (making the tree dirty), then asks Muse to switch to the side branch. Muse refuses to switch while those edits are uncommitted. The helper then undoes the note edits but leaves Git sitting on the side branch it just created — so the operator’s tree is stranded in the wrong place, and the sync always fails.
This phase freezes the permanent fix: keep any history realign on the original branch, then set
up the side branch (both histories under muse+git-mirror), then write the notes, then
commit, then stamp the sync marker. If anything fails after a branch switch, rollback must
restore the original branch (and marker) on every failure path — not only the note bytes.
Technical summary: amend tools/governance_hygiene/engine.py _apply_plan to
capture → realign → ensure feature branch → write docs → commit → marker → push; amend
Muse-regime commit_feature with already-on-branch short-circuit and dirty-carry checkout
(--autoshelf / --merge) only as a secondary adapter guard; restore original branch + marker on
mid-apply failure. Freeze a three-regime matrix and a seven-tier regression suite that includes
dirty-tree --write on all regimes — the exact coverage gap that let this ship.
§GSW.1 — Verified incident (do not redesign around folklore)
Live overseer-kit dogfood during PMHF land-b (2026-07-31), regime muse+git-mirror:
| Fact | Evidence |
|---|---|
ok governance-sync --write exits 2 |
Operator live run; docs restored; no feature commit |
| Doc patches written before branch setup | tools/governance_hygiene/engine.py _apply_plan lines 450–451 write handover/roadmap, then lines 498–513 call _ensure_feature_branch, then lines 515–519 call adapter.commit_feature |
| Muse refuses dirty tracked checkout | adapters/muse_git_mirror/adapter.py commit_feature line 185: self._muse("checkout", branch) with no --merge / --autoshelf; Muse 0.2.x exit 1 on dirty tree without those flags |
| Git switch happens first and is not rolled back | _ensure_feature_branch lines 656–668 run git checkout -b / git checkout under non-muse-only; on later commit_feature failure, lines 521–535 restore doc bytes only — no git/muse branch restore |
| Observed stranded git branch | feat/governance-sync-2026-07-31 after failed write |
| Existing write e2e is git-only + mocked clean tree | tests/e2e/test_governance_sync_cycle.py seeds config-git-only.yaml and mocks porcelain clean; no muse+git-mirror dirty-tree --write case |
Root cause (two coupled defects):
- Order-of-operations: write-then-checkout is incompatible with Muse’s default dirty-tree refusal (unlike git, which carries uncommitted changes across checkout).
- Incomplete rollback: branch identity changed by
_ensure_feature_branchis not restored on failure, violating 9A-5 §7 “working tree left clean / failure reported with tree untouched” intent for operator branch posture.
§GSW.2 — Scope
In scope (GSW-FIX-a freezes; GSW-FIX-b implements):
- Frozen apply-path order-of-operations (§GSW.3), including
_apply_planmarker-after-commit (§GSW.3.4). - Frozen branch-identity capture + restore-on-failure contract (§GSW.4).
- Regime matrix for
muse+git-mirror,muse-only,git-only(§GSW.5). - Adapter /
commit_featuredirty-tree contract (§GSW.6). - Explicit non-goals (§GSW.7).
- Security / privacy checklist (§GSW.8).
- Auto deliverables (§GSW.9).
- Seven-tier regression matrix including dirty-tree
--writeon all regimes (§GSW.10). - Definition of Done + hard stops (§GSW.11–§GSW.12).
Out of scope (explicit non-goals):
| Non-goal | Why rejected |
|---|---|
| Redesign of freeze review or build-verification | Incident is write-path order + rollback only |
| Changing D1/D2/D3, realign guard, NEXT regen, or GFG stamp sites beyond §GSW.3.4 | Compose with 9A-5 / GS-PASTE / GFG / PMHF; only _apply_plan mid-apply marker timing is amended |
| New CLI subcommand or exit-code renumbering | Surface stays ok governance-sync [--write]; exit 2 on VCS failure remains |
Silent commits / merges to main, staging push, live flips |
Tier 3 unchanged |
| Requiring live MuseHub for baseline green | Fixtures + injectable runners; no live bridge export on the kit dev tree |
Making --force Muse checkout the primary fix |
Discards / overwrites risk; rejected |
| Reopening GFG dry-run / fully_aligned stamp sites | Only _apply_plan mid-apply stamp timing is amended (§GSW.3.4); other GFG stamp sites stay |
| Moving realign to after feature-branch switch | Unproven; freeze keeps realign on the original branch (§GSW.3.1 step B) |
| GSW-FIX-b Auto implementation in the Thinking phase | SD-3 split |
§GSW.3 — HOW (frozen order-of-operations)
Decision: branch setup BEFORE doc writes (primary). Dirty-carry Muse checkout
(muse checkout --autoshelf or muse checkout --merge) is an allowed secondary adapter guard
only when a checkout is still required while the tree is dirty — it must not preserve today’s
engine order of “write docs, then ensure branch.”
§GSW.3.1 — Apply / --write sequence (frozen)
Inside _apply_plan (or equivalent helper extracted by Auto), the successful path MUST be:
| Step | Action | Notes |
|---|---|---|
| A | Capture original_branch_state |
Regime-specific — see §GSW.4.1 |
| B | Realign guard (unchanged semantics) | Existing execute_realign_guard runs on the original branch (today’s timing). Must not move to after feature-branch switch — muse bridge git-import targets muse main via --branch, but running it after a sync-branch switch + dirty docs is an unproven reorder; freeze preserves original-branch realign. On failure → zero doc writes, zero feature-branch switch (nothing to roll back beyond reporting). |
| C | Ensure feature branch exists and current HEAD(s) are on it | _ensure_feature_branch amended per §GSW.5; must complete before any handover/roadmap patch write |
| D | Write handover + roadmap patch bytes | Existing atomic_write_text pair |
| E | adapter.commit_feature(...) |
Must succeed with dirty docs already on the feature branch (§GSW.6) |
| F | Sync marker write when D1+D2 aligned | Only after successful commit — see §GSW.3.4 (GFG §GFG.5.3 mid-apply rule) |
| G | Feature-branch push (regime-appropriate; unchanged Tier-1 rule) | Existing _push_feature_branch |
Forbidden order (today’s bug): any path that writes handover/roadmap patches before step C completes successfully.
Also forbidden: writing the GFG sync marker before step E succeeds (see §GSW.3.4).
§GSW.3.2 — Dry-run (unchanged)
--dry-run (default) still plans patches and reports would-commit branch; it writes no doc patches,
creates no branch, performs no realign apply, creates no commit. GFG dry-run marker carve-out
(§GFG.5.3 fully_aligned / D1+D2-aligned dry-run paths) is unchanged. GSW-FIX does not alter
dry-run inertness for handover/roadmap bytes.
§GSW.3.3 — Narrow 9A-5 §7 amendment
9A-5 §7 (“Never write partial state… working tree is left clean, or the failure is reported with the tree untouched”) is amended for branch posture:
- On mid-apply failure after step C (feature-branch switch), restored state MUST include:
- original handover bytes,
- original roadmap bytes,
- prior marker bytes / absent per §GSW.3.4,
- original branch identity per §GSW.4.
- “Tree untouched” means operator-visible branch + doc bytes + marker posture, not “leave the operator on a newly created sync branch after a failed apply.”
No other 9A-5 §4 / §5 / §6 redesign.
§GSW.3.4 — Narrow GFG marker amendment (mid-apply)
GFG §GFG.5.3 already freezes: Must not stamp on mid-apply failure. Today’s
_apply_plan writes the marker before _ensure_feature_branch / commit_feature
(tools/governance_hygiene/engine.py lines 490–519), which can leave
.overseer/last_governance_sync stamped after a failed commit while docs were rolled back.
Frozen amendment for the _apply_plan success path only:
- Capture prior marker bytes (or “absent”) at the start of
_apply_planwhen a stamp might occur. - Write/refresh the enriched marker only after
commit_featurereturns success (step E). - On any mid-apply failure before that success: do not leave a newly written stamp — restore prior marker bytes if this run overwrote them, or leave absent if it was absent.
- Fully_aligned early-return and dry-run D1+D2 stamp sites from GFG remain as frozen in GFG
(out of GSW-FIX reorder scope except that
_apply_planmust obey the mid-apply rule).
§GSW.4 — Branch capture + rollback (frozen)
§GSW.4.1 — original_branch_state (capture at step A, before any mutation)
| Regime | Captured fields (minimum) |
|---|---|
git-only |
git_branch = git rev-parse --abbrev-ref HEAD |
muse-only |
muse_branch = muse rev-parse --abbrev-ref HEAD |
muse+git-mirror |
both git_branch and muse_branch |
Capture failures (unreadable HEAD) → fail closed exit 2 with the exact failing command; zero
doc writes; zero branch switches.
§GSW.4.2 — Restore on every failure path after step C
Any failure after step C begins (feature-branch ensure / write / commit) that returns a
non-success GovernanceSyncResult MUST, in this order:
- Restore handover + roadmap original bytes (existing behavior).
- Restore marker per §GSW.3.4 (no new stamp left behind).
- Restore branch identity:
git-only:git checkout <git_branch>(or equivalent) when current git HEAD ≠ captured.muse-only:muse checkout <muse_branch>when current muse HEAD ≠ captured.muse+git-mirror: restore both; if one restore fails, report the exact failing command and still attempt the other (best-effort dual restore), exit non-zero.
- Not leave the operator on
plan.feature_branchafter a failed apply.
Step C itself fails (cannot create/switch to feature branch): no doc writes have occurred yet
(per §GSW.3.1); if a partial switch occurred, still restore original_branch_state.
Step B (realign) fails: no feature-branch switch and no doc writes — report and exit; no branch restore required.
Success path: operator remains on plan.feature_branch (intentional); no restore.
§GSW.4.3 — Rollback must not use --force as the default restore
Preferred rollback checkout order: restore doc bytes first (§GSW.4.2 step 1) so the tree is often
clean, then restore branch with a normal checkout. If the tree is still dirty, Muse restore may use
the same dirty-carry flags as §GSW.6 (--autoshelf or --merge). --force is forbidden as the
default rollback mechanism (data-loss risk).
§GSW.5 — Regime matrix (frozen)
| Regime | Step C must place on feature branch | Step E commit substrate | Rollback restores |
|---|---|---|---|
git-only |
Git HEAD | Git (adapters/git_only) |
git_branch |
muse-only |
Muse HEAD | Muse (adapters/muse_only) |
muse_branch |
muse+git-mirror |
Git HEAD and Muse HEAD | Muse commit via adapters/muse_git_mirror (canonical); Git feature branch already matches for push |
both git_branch and muse_branch |
§GSW.5.1 — muse+git-mirror dual-HEAD rule (frozen)
Today _ensure_feature_branch switches git only (engine.py lines 656–668) while Muse moves
later inside commit_feature (adapter.py line 185). That split is the live failure window.
Frozen rule: under muse+git-mirror, step C is incomplete until both of the following are
true before step D (doc writes):
git rev-parse --abbrev-ref HEAD==plan.feature_branchmuse rev-parse --abbrev-ref HEAD==plan.feature_branch
Auto may implement dual ensure inside _ensure_feature_branch, or split helpers, as long as the
pre-write invariant holds.
§GSW.5.2 — Feature-branch create semantics
| Regime | Create if missing | Switch if exists |
|---|---|---|
git-only |
git checkout -b <branch> |
git checkout <branch> |
muse-only |
muse checkout -b <branch> |
muse checkout <branch> (dirty-carry if needed — §GSW.6) |
muse+git-mirror |
create/switch on both sides | both sides; names MUST match plan.feature_branch |
Protected-branch refusal (main / configured main) remains adapter-enforced (9A-5 §6).
§GSW.6 — commit_feature dirty-tree contract (frozen)
§GSW.6.1 — Already-on-branch short-circuit (required)
For all three adapters, commit_feature MUST NOT fail solely because handover/roadmap paths are
dirty when HEAD is already branch:
- Probe current branch (
rev-parse --abbrev-ref HEADor existing equivalent). - If current ==
branch, skip checkout; proceed to add/commit. - If current ≠
branch, perform checkout per §GSW.6.2.
This matches the post-§GSW.3 world (engine already switched) and hardens against double-checkout.
§GSW.6.2 — Checkout when not on branch (Muse regimes)
If a Muse checkout is still required and the working tree has uncommitted tracked changes:
| Option | Verdict |
|---|---|
Bare muse checkout <branch> (today) |
FORBIDDEN as the sole behavior — live defect |
muse checkout --autoshelf <branch> |
ALLOWED secondary guard |
muse checkout --merge <branch> |
ALLOWED secondary guard |
muse checkout --force <branch> |
FORBIDDEN as default |
Auto picks one allowed secondary guard and covers it in tests; documenting the choice in the implementation commit message / change-log is enough (no Tier-2 schema change).
§GSW.6.3 — Git adapter
Git already carries dirty changes across checkout. Keep refuse-protected-branch + path validation. Still implement §GSW.6.1 short-circuit for symmetry and fewer spurious checkouts.
§GSW.7 — Non-goals (echo)
- No freeze/BV redesign.
- No change to dry-run default inertness.
- No docs-only auto-open PR to
main(SD-11). - No
muse bridge git-exporton the kit dev tree (SD-14). - No consumer re-init requirement for DONE.
§GSW.8 — Security / privacy checklist
- No secrets in commit messages, rollback logs, or fixtures.
- Shell-safe quoting for branch names via existing
quote_arg/ adapter argv lists — branch names from config patterns remain data, never interpolated unsafely. - Fail closed on unreadable HEAD / failed checkout / failed commit — exit
2with exact command. - Least privilege:
git-onlynever invokes Muse;muse-onlynever invokes git/gh. - No
--forcecheckout as default (data loss). - No writes to protected branches.
§GSW.9 — Auto deliverables (GSW-FIX-b)
| Change | Location |
|---|---|
Reorder _apply_plan: capture → realign (original branch) → ensure feature branch (regime matrix) → write docs → commit → marker (D1+D2) → push |
tools/governance_hygiene/engine.py |
Dual-HEAD ensure for muse+git-mirror before writes |
engine.py _ensure_feature_branch (or helper) |
Rollback restores docs + marker + original_branch_state on every post-switch failure path |
engine.py |
commit_feature already-on-branch short-circuit |
adapters/{git_only,muse_only,muse_git_mirror}/adapter.py |
| Muse dirty-carry checkout when switch still required | adapters/muse_only/adapter.py, adapters/muse_git_mirror/adapter.py |
| Seven-tier tests per §GSW.10 | tests/{unit,integration,e2e,stress,data_integrity,performance,security}/ |
ROADMAP + HANDOVER close together; /build-verification-review → pass before DONE |
governance docs |
Exit codes: unchanged (0 success; 2 VCS/apply failure; other existing codes retain meaning).
§GSW.10 — Seven-tier test matrix (GSW-FIX-b)
| Tier | Frozen case |
|---|---|
| unit | (1) _ensure_feature_branch / apply-plan ordering helper: given a spy, assert no doc write precedes successful branch ensure; assert realign is invoked before branch ensure. (2) commit_feature already-on-branch skips checkout (all three adapters). (3) Muse adapter off-branch + dirty → uses --autoshelf or --merge, never bare checkout-only. (4) Rollback helper restores captured branch field(s). (5) Marker write is not invoked when commit fails. |
| integration | Injected-runner --write on each regime config fixture (git-only, muse-only, muse+git-mirror): simulate dirty handover/roadmap after branch ensure; commit succeeds; call log shows realign (or skip) → branch ensure → writes → commit → marker. |
| e2e | Dirty-tree --write on all three regimes (mandatory gap close): fixture starts on main (or regime main), apply path creates/switches to sync feature branch, patches docs, commits; main untouched; PR URL print rules unchanged for git regimes; muse-only never invokes git. |
| stress | Repeated apply with induced commit failure after branch switch (N≥20): every failure restores original branch; no strand accumulation of feat/governance-sync-* as current HEAD. |
| data-integrity | Induced failure after branch switch + after doc write: docs bytes == originals; current branch == captured original; no feature commit; marker absent or restored to prior bytes (GFG mid-apply rule); second successful --write then produces exactly one feature-branch commit bundling handover+roadmap and may stamp marker only after that success when D1+D2 aligned. |
| performance | Reordered apply path on kit-sized docs stays within existing governance-sync performance bound (no extra unbounded VCS log scans). |
| security | (1) Branch name with shell metacharacters cannot break quoting (fail closed or safe argv). (2) git-only write path call log contains zero muse argv. (3) muse-only write path call log contains zero git/gh argv. (4) No --force checkout in default success/rollback paths. |
Coverage gate (frozen): GSW-FIX-b is incomplete if dirty-tree --write exists only for
git-only. All three regimes are required.
§GSW.11 — Definition of Done (GSW-FIX-b)
- [ ]
_apply_planorder: realign on original branch → ensure feature branch → write docs → commit → marker (§GSW.3.1) - [ ] Feature branch ensure (regime matrix) before handover/roadmap writes
- [ ]
muse+git-mirrordual-HEAD on feature branch before writes (§GSW.5.1) - [ ] Rollback restores docs + marker + original branch on every failure path after switch (§GSW.4)
- [ ]
commit_featurealready-on-branch short-circuit + Muse dirty-carry when needed (§GSW.6) - [ ] Seven-tier §GSW.10 green, including dirty-tree
--writeon all three regimes - [ ]
/build-verification-review→passbefore ROADMAP DONE - [ ]
docs/ROADMAP.md+docs/OVERSEER-HANDOVER.mdupdated together (SD-17) - [ ] No consumer re-init; no feature→GitHub-
mainwithout Tier 3; no secrets
§GSW.12 — Hard stops
- No GSW-FIX-b Auto implementation during GSW-FIX-a
- No merge to
main/ staging push / live posture flips without Tier 3 - No live
muse bridge git-exporton the kit dev tree - No
--forceas default checkout/rollback - No freeze/BV redesign
§GSW.13 — Cross-references
docs/archive/phases/PHASE-9A-5-GOVERNANCE-HYGIENE-AGENT-OUTLINE.md— §6 commit strategy, §7 error handlingtools/governance_hygiene/engine.py—_apply_plan,_ensure_feature_branchadapters/muse_git_mirror/adapter.py—commit_featureMuse checkoutadapters/muse_only/adapter.py/adapters/git_only/adapter.py— peercommit_featuretests/e2e/test_governance_sync_cycle.py— current git-only write coverage gapdocs/OVERSEER-KIT-SPEC.md§6 — freeze review policypolicy/test-tiers.yaml— seven-tier contract
Freeze-review findings ledger
| ID | Severity | Category | Citation | Message |
|---|---|---|---|---|
| R1-M1 | MAJOR | consistency | docs/archive/phases/PHASE-GSW-FIX-GOVERNANCE-SYNC-WRITE-PATH.md §GSW.3.1 (pre-fix); tools/governance_hygiene/realign.py:40-98; adapters/muse_git_mirror/adapter.py:147-158 |
First draft placed realign after feature-branch switch. That reorders realign relative to today’s original-branch timing without proving dirty feature-branch + git-import --branch <muse_main> safety. Freeze must keep realign before feature-branch ensure. |
| R1-M2 | MAJOR | consistency | docs/archive/phases/PHASE-GFG-GOVERNANCE-FRESHNESS-GATE.md §GFG.5.3 (“Must not stamp on mid-apply failure”); tools/governance_hygiene/engine.py:490-535 |
First draft kept marker write before commit. On commit failure, docs roll back but marker can remain — contradicts GFG mid-apply rule. Freeze must stamp marker only after successful commit_feature in _apply_plan. |
| R2-M1 | MAJOR | consistency | docs/archive/phases/PHASE-GSW-FIX-GOVERNANCE-SYNC-WRITE-PATH.md §GSW.2 non-goals (pre-fix) vs §GSW.3.4; freeze YAML frozen_inputs |
Non-goal claimed no GFG marker semantic change while §GSW.3.4 amends _apply_plan stamp timing; GFG was not listed as a frozen input despite the amendment edge. |