test_gsw_dirty_tree_write_cycle.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
9 hours ago
| 1 | """E2E: dirty-tree ``governance-sync --write`` on ALL three regimes (§GSW.10 e2e). |
| 2 | |
| 3 | This is the frozen coverage-gap close: the live 2026-07-31 incident shipped |
| 4 | because dirty-tree ``--write`` existed only for ``git-only``. Each fixture |
| 5 | starts on ``main`` with a dirty working tree; the apply path must |
| 6 | create/switch to the sync feature branch, patch docs, and commit — |
| 7 | leaving ``main`` untouched and never using ``--force``. |
| 8 | """ |
| 9 | |
| 10 | from __future__ import annotations |
| 11 | |
| 12 | import io |
| 13 | from contextlib import redirect_stdout |
| 14 | from datetime import date |
| 15 | from pathlib import Path |
| 16 | |
| 17 | from cli.kit_root import kit_root |
| 18 | from tests.support import gsw_runner, run_cli, seed_gsw_repo |
| 19 | |
| 20 | |
| 21 | def _feature_branch() -> str: |
| 22 | return f"feat/governance-sync-{date.today().isoformat()}" |
| 23 | |
| 24 | |
| 25 | def test_git_only_dirty_tree_write(tmp_path: Path) -> None: |
| 26 | handover_path, roadmap_path = seed_gsw_repo(tmp_path, "git-only") |
| 27 | runner = gsw_runner(tmp_path, "git-only", git_dirty=True) |
| 28 | |
| 29 | buf = io.StringIO() |
| 30 | with redirect_stdout(buf): |
| 31 | code = run_cli( |
| 32 | ["governance-sync", "--write"], cwd=tmp_path, runner=runner, kit=kit_root() |
| 33 | ) |
| 34 | assert code == 0 |
| 35 | assert runner.git_branch == _feature_branch() |
| 36 | assert "main" in runner.git_branches # main still exists, untouched |
| 37 | assert "cafebabe" in handover_path.read_text(encoding="utf-8") |
| 38 | # PR URL print rules unchanged for git regimes (operator-gated, never auto-open). |
| 39 | assert "docs-only PR URL (operator-gated" in buf.getvalue() |
| 40 | push_calls = [c for c, _ in runner.calls if c.startswith("git push")] |
| 41 | assert push_calls and all(_feature_branch() in c for c in push_calls) |
| 42 | assert not any(c.rstrip().endswith(" main") for c in push_calls) |
| 43 | assert not any("--force" in c for c, _ in runner.calls) |
| 44 | |
| 45 | |
| 46 | def test_muse_only_dirty_tree_write_never_invokes_git(tmp_path: Path) -> None: |
| 47 | handover_path, _ = seed_gsw_repo(tmp_path, "muse-only") |
| 48 | original_handover = handover_path.read_text(encoding="utf-8") |
| 49 | # Feature branch pre-exists (same-day retry — the live incident shape): |
| 50 | # bare dirty checkout is refused, so the dirty-carry guard must engage. |
| 51 | runner = gsw_runner( |
| 52 | tmp_path, |
| 53 | "muse-only", |
| 54 | muse_dirty=True, |
| 55 | existing_muse_branches={_feature_branch()}, |
| 56 | ) |
| 57 | |
| 58 | buf = io.StringIO() |
| 59 | with redirect_stdout(buf): |
| 60 | code = run_cli( |
| 61 | ["governance-sync", "--write"], cwd=tmp_path, runner=runner, kit=kit_root() |
| 62 | ) |
| 63 | assert code == 0 |
| 64 | assert runner.muse_branch == _feature_branch() |
| 65 | assert handover_path.read_text(encoding="utf-8") != original_handover |
| 66 | assert any("--autoshelf" in c for c, _ in runner.calls) |
| 67 | # §GSW.8 least privilege: muse-only never invokes git/gh. |
| 68 | assert not any(c.startswith(("git ", "gh ")) for c, _ in runner.calls) |
| 69 | assert "docs-only PR URL" not in buf.getvalue() |
| 70 | assert not any("--force" in c for c, _ in runner.calls) |
| 71 | |
| 72 | |
| 73 | def test_muse_git_mirror_dirty_tree_write_closes_live_incident(tmp_path: Path) -> None: |
| 74 | """The exact live failure shape: muse+git-mirror, dirty tree, feature branch |
| 75 | already created by a prior failed run — the sync must now succeed.""" |
| 76 | handover_path, roadmap_path = seed_gsw_repo(tmp_path, "muse+git-mirror") |
| 77 | runner = gsw_runner( |
| 78 | tmp_path, |
| 79 | "muse+git-mirror", |
| 80 | git_dirty=True, |
| 81 | muse_dirty=True, |
| 82 | existing_git_branches={_feature_branch()}, |
| 83 | existing_muse_branches={_feature_branch()}, |
| 84 | ) |
| 85 | |
| 86 | buf = io.StringIO() |
| 87 | with redirect_stdout(buf): |
| 88 | code = run_cli( |
| 89 | ["governance-sync", "--write"], cwd=tmp_path, runner=runner, kit=kit_root() |
| 90 | ) |
| 91 | assert code == 0 |
| 92 | # Dual-HEAD success posture: both histories on the sync branch (§GSW.5.1). |
| 93 | assert runner.git_branch == _feature_branch() |
| 94 | assert runner.muse_branch == _feature_branch() |
| 95 | assert "main" in runner.git_branches and "main" in runner.muse_branches |
| 96 | assert "cafebabe" in handover_path.read_text(encoding="utf-8") |
| 97 | # Muse dirty-carry engaged instead of the live defect's bare-checkout failure. |
| 98 | assert any(c.startswith("muse") and "--autoshelf" in c for c, _ in runner.calls) |
| 99 | # Muse commit substrate; git push of the feature branch only. |
| 100 | assert any(c.startswith("muse") and " commit " in c for c, _ in runner.calls) |
| 101 | push_calls = [c for c, _ in runner.calls if c.startswith("git push")] |
| 102 | assert push_calls and all(_feature_branch() in c for c in push_calls) |
| 103 | assert "docs-only PR URL (operator-gated" in buf.getvalue() |
| 104 | assert not any("--force" in c for c, _ in runner.calls) |
File History
1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
9 hours ago