test_gsw_dirty_tree_write_cycle.py python
104 lines 4.5 KB
Raw
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