test_governance_freshness_security.py python
119 lines 5.0 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 15 hours ago
1 """Security: no secrets in marker; muse-only skips git/gh; fail-closed (§GFG.9 security)."""
2
3 from __future__ import annotations
4
5 import json
6 from pathlib import Path
7 from unittest.mock import MagicMock
8
9 from adapters.errors import ReadError
10 from adapters.types import AnchorResult, HeadResult, StatusResult
11 from tests.support import FIXTURES, load_fixture_config, write_config
12 from tools.governance_freshness import check_governance_freshness
13
14
15 def test_marker_and_payload_have_no_secrets(tmp_path: Path) -> None:
16 write_config(tmp_path, "config-git-only.yaml")
17 (tmp_path / ".overseer" / "version.lock").write_text(
18 "lock_version: 1\nkit_version: 0.1.0\nconfig_version: 1\n"
19 "footprint_digest: sha256:" + ("0" * 64) + "\n"
20 "installed_at: \"2026-01-01T00:00:00Z\"\nsynced_at: \"2026-01-01T00:00:00Z\"\n"
21 "footprint: []\n",
22 encoding="utf-8",
23 )
24 docs = tmp_path / "docs"
25 docs.mkdir(parents=True, exist_ok=True)
26 (docs / "OVERSEER-HANDOVER.md").write_text(
27 "| GitHub `main` | `cafebabe` |\n", encoding="utf-8"
28 )
29 (docs / "ROADMAP.md").write_text("## Build queue\n\n", encoding="utf-8")
30 (tmp_path / ".overseer" / "last_governance_sync").write_text(
31 "2026-07-28T00:00:00Z\nr1=cafebabe\nr3=cafebabe\n", encoding="utf-8"
32 )
33 adapter = MagicMock()
34 adapter.status.return_value = StatusResult(
35 regime="git-only", dirty=False, branch="main", muse_dirty=None, git_dirty=False
36 )
37 adapter.read_head.return_value = HeadResult(sha="cafebabe", kind="git")
38 adapter.read_canonical_anchor.return_value = AnchorResult(
39 anchor_sha="cafebabe", source="origin/main"
40 )
41 config = load_fixture_config(tmp_path, "config-git-only.yaml")
42 report = check_governance_freshness(config, tmp_path, adapter=adapter)
43 blob = json.dumps(
44 {
45 "state": report.state,
46 "message": report.message,
47 "remediation": report.remediation,
48 "marker_r1": report.marker_r1,
49 "actual_r1": report.actual_r1,
50 }
51 )
52 for banned in ("sk-", "API_KEY", "password", "BEGIN PRIVATE"):
53 assert banned not in blob
54 assert report.remediation is None or report.remediation.startswith("ok ")
55
56
57 def test_muse_only_never_calls_git_or_gh(tmp_path: Path) -> None:
58 write_config(tmp_path, "config-muse-only.yaml")
59 (tmp_path / ".overseer" / "version.lock").write_text(
60 "lock_version: 1\nkit_version: 0.1.0\nconfig_version: 1\n"
61 "footprint_digest: sha256:" + ("0" * 64) + "\n"
62 "installed_at: \"2026-01-01T00:00:00Z\"\nsynced_at: \"2026-01-01T00:00:00Z\"\n"
63 "footprint: []\n",
64 encoding="utf-8",
65 )
66 docs = tmp_path / "docs"
67 docs.mkdir(parents=True, exist_ok=True)
68 (docs / "OVERSEER-HANDOVER.md").write_text("handover\n", encoding="utf-8")
69 (docs / "ROADMAP.md").write_text("## Build queue\n\n", encoding="utf-8")
70 (tmp_path / ".overseer" / "last_governance_sync").write_text(
71 "2026-07-28T00:00:00Z\nr1=\nr3=cafebabe\n", encoding="utf-8"
72 )
73 adapter = MagicMock()
74 adapter.status.return_value = StatusResult(
75 regime="muse-only", dirty=False, branch="main", muse_dirty=False, git_dirty=None
76 )
77 adapter.read_head.return_value = HeadResult(sha="cafebabe", kind="muse")
78 adapter.read_canonical_anchor.return_value = AnchorResult(
79 anchor_sha="cafebabe", source="muse:main"
80 )
81 runner = MagicMock()
82 config = load_fixture_config(tmp_path, "config-muse-only.yaml")
83 report = check_governance_freshness(config, tmp_path, adapter=adapter, runner=runner)
84 assert report.ok
85 for call in runner.run.call_args_list:
86 cmd = call.args[0] if call.args else ""
87 assert not cmd.startswith("git ")
88 assert not cmd.startswith("gh ")
89
90
91 def test_unreadable_fails_closed_not_optimistic_ok(tmp_path: Path) -> None:
92 write_config(tmp_path, "config-git-only.yaml")
93 (tmp_path / ".overseer" / "version.lock").write_text(
94 "lock_version: 1\nkit_version: 0.1.0\nconfig_version: 1\n"
95 "footprint_digest: sha256:" + ("0" * 64) + "\n"
96 "installed_at: \"2026-01-01T00:00:00Z\"\nsynced_at: \"2026-01-01T00:00:00Z\"\n"
97 "footprint: []\n",
98 encoding="utf-8",
99 )
100 adapter = MagicMock()
101 adapter.status.return_value = ReadError("git status", "denied")
102 config = load_fixture_config(tmp_path, "config-git-only.yaml")
103 report = check_governance_freshness(config, tmp_path, adapter=adapter)
104 assert report.state == "unreadable"
105 assert not report.ok
106
107
108 def test_automation_template_cannot_merge_or_push() -> None:
109 template = (
110 FIXTURES.parent.parent
111 / "cursor"
112 / "automations"
113 / "governance-sync-session-end.json"
114 )
115 raw = template.read_text(encoding="utf-8")
116 assert "governance-sync --dry-run" in raw
117 assert "push" not in raw.lower() or "no main merge/push" in raw.lower()
118 assert "merge" not in raw.lower() or "no main merge" in raw.lower()
119 assert '"command": "ok governance-sync --dry-run"' in raw
File History 1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 15 hours ago