test_land_closeout_integrity.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago
| 1 | """Data integrity: land-closeout never writes; CI template never applies to main |
| 2 | (§PMHF.10 data-integrity).""" |
| 3 | |
| 4 | from __future__ import annotations |
| 5 | |
| 6 | import json |
| 7 | from pathlib import Path |
| 8 | |
| 9 | from cli.kit_root import kit_root |
| 10 | from tests.support import ( |
| 11 | KIT_ROOT, |
| 12 | git_status_runner, |
| 13 | make_runner, |
| 14 | ok, |
| 15 | run_cli, |
| 16 | seed_land_repo, |
| 17 | ) |
| 18 | |
| 19 | CI_TEMPLATE = KIT_ROOT / "templates" / "ci" / "governance-closeout-github-actions.yml" |
| 20 | |
| 21 | |
| 22 | def _doc_bytes(tmp_path: Path) -> dict[str, bytes]: |
| 23 | docs = tmp_path / "docs" |
| 24 | return {p.name: p.read_bytes() for p in sorted(docs.glob("*.md"))} |
| 25 | |
| 26 | |
| 27 | def test_status_and_land_closeout_never_write_docs(tmp_path: Path, capsys) -> None: |
| 28 | seed_land_repo(tmp_path, claim="deadbeef") # drifted → both surfaces report failure |
| 29 | before = _doc_bytes(tmp_path) |
| 30 | marker_before = (tmp_path / ".overseer" / "last_governance_sync").read_bytes() |
| 31 | |
| 32 | assert ( |
| 33 | run_cli( |
| 34 | ["status", "--json", "--exit-code"], |
| 35 | cwd=tmp_path, |
| 36 | runner=git_status_runner(tip="cafebabe"), |
| 37 | json_mode=True, |
| 38 | ) |
| 39 | == 2 |
| 40 | ) |
| 41 | assert ( |
| 42 | run_cli( |
| 43 | ["land-closeout", "--json"], |
| 44 | cwd=tmp_path, |
| 45 | runner=git_status_runner(tip="cafebabe"), |
| 46 | json_mode=True, |
| 47 | ) |
| 48 | == 2 |
| 49 | ) |
| 50 | capsys.readouterr() |
| 51 | |
| 52 | assert _doc_bytes(tmp_path) == before |
| 53 | assert (tmp_path / ".overseer" / "last_governance_sync").read_bytes() == marker_before |
| 54 | |
| 55 | |
| 56 | def test_land_closeout_idempotent(tmp_path: Path, capsys) -> None: |
| 57 | seed_land_repo(tmp_path, claim="deadbeef") |
| 58 | outputs = [] |
| 59 | for _ in range(2): |
| 60 | capsys.readouterr() |
| 61 | code = run_cli( |
| 62 | ["land-closeout", "--json"], |
| 63 | cwd=tmp_path, |
| 64 | runner=git_status_runner(tip="cafebabe"), |
| 65 | json_mode=True, |
| 66 | ) |
| 67 | assert code == 2 |
| 68 | outputs.append(json.loads(capsys.readouterr().out)) |
| 69 | assert outputs[0] == outputs[1] |
| 70 | |
| 71 | |
| 72 | def test_ci_template_has_no_apply_to_main_step() -> None: |
| 73 | text = CI_TEMPLATE.read_text(encoding="utf-8") |
| 74 | active_lines = [ |
| 75 | line for line in text.splitlines() if line.strip() and not line.strip().startswith("#") |
| 76 | ] |
| 77 | active = "\n".join(active_lines) |
| 78 | assert "git push" not in active # frozen ban: never pushes from the workflow |
| 79 | assert "--write" not in active # governance-sync stays dry-run in CI |
| 80 | assert "governance-sync --dry-run" in active |
| 81 | assert "land-closeout --probe-merged-pr" in active |
| 82 | assert "workflow_dispatch" in active |
| 83 | assert "contents: read" in active # read-only checkout — cannot write main |
| 84 | |
| 85 | |
| 86 | def test_dry_run_governance_sync_still_only_stamps_marker(tmp_path: Path) -> None: |
| 87 | # GFG carve-out unchanged: aligned dry-run stamps the local marker only. |
| 88 | seed_land_repo(tmp_path, claim="cafebabe", marker_tip=None) |
| 89 | before = _doc_bytes(tmp_path) |
| 90 | runner = make_runner( |
| 91 | { |
| 92 | "git rev-parse --abbrev-ref HEAD": ok("main"), |
| 93 | "git status --porcelain": ok(""), |
| 94 | "git rev-parse origin/main": ok("cafebabe"), |
| 95 | "gh pr list --state merged --limit 5 --json number,title,mergeCommit,mergedAt": ok( |
| 96 | "[]" |
| 97 | ), |
| 98 | "git remote get-url origin": ok("[email protected]:owner/repo.git"), |
| 99 | } |
| 100 | ) |
| 101 | code = run_cli(["governance-sync"], cwd=tmp_path, runner=runner, kit=kit_root()) |
| 102 | assert code == 0 |
| 103 | assert _doc_bytes(tmp_path) == before # no governance-doc writes on dry-run |
| 104 | marker = tmp_path / ".overseer" / "last_governance_sync" |
| 105 | assert marker.is_file() |
| 106 | assert "r1=cafebabe" in marker.read_text(encoding="utf-8") |
File History
1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago