test_honesty_k10.py python
223 lines 8.0 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago
1 """Unit tests for K10 honesty config, ledger, and status helpers."""
2
3 from __future__ import annotations
4
5 import json
6 from pathlib import Path
7
8 import pytest
9 import yaml
10
11 from adapters.config import load_config
12 from adapters.errors import ConfigError
13 from tests.support import HONESTY, honesty_artifact_hash, load_honesty_config, seed_honesty_repo
14 from tools.honesty.canonical import canonical_json, compute_entry_hash
15 from tools.honesty.genesis import GENESIS_PREV, build_genesis_entry
16 from tools.honesty.ledger import append_entry, verify_chain
17 from tools.honesty.ledger_io import parse_jsonl_text, split_jsonl_lines
18 from tools.honesty.status import HonestyStatusOptions, run_honesty_status
19 from tools.honesty.types import LedgerAppendOptions
20 from tools.honesty.validate import EntryValidationError, validate_append_body
21
22
23 def test_genesis_prev_constant() -> None:
24 assert GENESIS_PREV == "368646f427571067ec853ef0c3d4cce9ecfa3fb3e003dd1252cd5d02f111e513"
25
26
27 def test_canonical_json_sorted_keys() -> None:
28 assert canonical_json({"b": 1, "a": 2}) == '{"a":2,"b":1}'
29
30
31 def test_entry_hash_omits_entry_hash_key() -> None:
32 body = {"v": 1, "kind": "genesis", "ts": "2026-01-01T00:00:00Z", "prev_hash": GENESIS_PREV}
33 h1 = compute_entry_hash(body)
34 body["entry_hash"] = "deadbeef"
35 h2 = compute_entry_hash(body)
36 assert h1 == h2
37
38
39 def test_genesis_with_actors_refused() -> None:
40 with pytest.raises(EntryValidationError) as exc:
41 validate_append_body(
42 kind="genesis",
43 body={"actor_role": "owner", "actor_session_id": "x"},
44 )
45 assert exc.value.exit_code == 2
46
47
48 def test_kind_body_mismatch_refused() -> None:
49 with pytest.raises(EntryValidationError) as exc:
50 validate_append_body(kind="verdict", body={"kind": "genesis"})
51 assert exc.value.exit_code == 2
52
53
54 def test_approval_recorded_requires_owner() -> None:
55 with pytest.raises(EntryValidationError) as exc:
56 validate_append_body(
57 kind="approval_recorded",
58 body={
59 "actor_role": "verifier",
60 "actor_session_id": "s",
61 "artifact_sha256": "aa",
62 "bound_verdict_hash": "bb",
63 },
64 )
65 assert exc.value.exit_code == 23
66
67
68 def test_verdict_empty_evidence_refused() -> None:
69 with pytest.raises(EntryValidationError) as exc:
70 validate_append_body(
71 kind="verdict",
72 body={
73 "actor_role": "verifier",
74 "actor_session_id": "s",
75 "artifact_sha256": "aa",
76 "passed": True,
77 "evidence": {"reexecuted": []},
78 },
79 )
80 assert exc.value.exit_code == 24
81
82
83 def test_jsonl_trailing_empty_segment_ignored() -> None:
84 text = '{"a":1}\n{"b":2}\n'
85 assert split_jsonl_lines(text) == ['{"a":1}', '{"b":2}']
86 assert len(parse_jsonl_text(text)) == 2
87
88
89 def test_honesty_disabled_refuses_before_paths(repo_root: Path) -> None:
90 seed_honesty_repo(repo_root)
91 cfg_path = repo_root / ".overseer" / "config.yaml"
92 data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
93 data["honesty"]["enabled"] = False
94 data["modules"]["honesty"]["enabled"] = False
95 cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8")
96 config = load_config(cfg_path)
97 result = run_honesty_status(
98 config=config,
99 repo_root=repo_root,
100 options=HonestyStatusOptions(hook="board_done", artifact="artifacts/sample.txt"),
101 )
102 assert result.exit_code == 4
103
104 append = append_entry(
105 config=config,
106 repo_root=repo_root,
107 options=LedgerAppendOptions(kind="verdict", body={}),
108 )
109 assert append.exit_code == 4
110
111
112 def test_require_verdict_on_empty_raises(repo_root: Path) -> None:
113 seed_honesty_repo(repo_root)
114 cfg_path = repo_root / ".overseer" / "config.yaml"
115 data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
116 data["honesty"]["require_verdict_on"] = []
117 cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8")
118 with pytest.raises(ConfigError, match="must not be empty"):
119 load_config(cfg_path)
120
121
122 def test_require_verdict_on_unknown_raises(repo_root: Path) -> None:
123 seed_honesty_repo(repo_root)
124 cfg_path = repo_root / ".overseer" / "config.yaml"
125 data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
126 data["honesty"]["require_verdict_on"] = ["nope"]
127 cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8")
128 with pytest.raises(ConfigError, match="board_done"):
129 load_config(cfg_path)
130
131
132 def test_hook_not_in_allowlist_refuses(repo_root: Path) -> None:
133 config = load_honesty_config(repo_root)
134 cfg_path = repo_root / ".overseer" / "config.yaml"
135 data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
136 data["honesty"]["require_verdict_on"] = ["handoff"]
137 cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8")
138 config = load_config(cfg_path)
139 result = run_honesty_status(
140 config=config,
141 repo_root=repo_root,
142 options=HonestyStatusOptions(hook="board_done", artifact="artifacts/sample.txt"),
143 )
144 assert result.exit_code == 4
145
146
147 def test_roles_file_missing_refuses(repo_root: Path) -> None:
148 config = load_honesty_config(repo_root)
149 cfg_path = repo_root / ".overseer" / "config.yaml"
150 data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
151 data["honesty"]["roles_file"] = "missing-roles.yaml"
152 cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8")
153 config = load_config(cfg_path)
154 result = run_honesty_status(
155 config=config,
156 repo_root=repo_root,
157 options=HonestyStatusOptions(hook="board_done", artifact="artifacts/sample.txt"),
158 )
159 assert result.exit_code == 4
160
161
162 def test_roles_file_readable_warns_enum_only(repo_root: Path) -> None:
163 config = load_honesty_config(repo_root)
164 roles = repo_root / "roles.yaml"
165 roles.write_text("custom: true\n", encoding="utf-8")
166 cfg_path = repo_root / ".overseer" / "config.yaml"
167 data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
168 data["honesty"]["roles_file"] = "roles.yaml"
169 cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8")
170 config = load_config(cfg_path)
171 result = run_honesty_status(
172 config=config,
173 repo_root=repo_root,
174 options=HonestyStatusOptions(hook="board_done", artifact="artifacts/sample.txt"),
175 )
176 assert result.exit_code == 20
177 assert "roles_file" in result.stderr_extra
178
179
180 def test_require_l1_evidence_prefix(repo_root: Path) -> None:
181 config = load_honesty_config(repo_root)
182 artifact_hash = honesty_artifact_hash(repo_root)
183 body = json.loads((HONESTY / "entries" / "verdict-pass.json").read_text(encoding="utf-8"))
184 body["artifact_sha256"] = artifact_hash
185 append_entry(
186 config=config,
187 repo_root=repo_root,
188 options=LedgerAppendOptions(kind="verdict", body=body),
189 )
190 result = run_honesty_status(
191 config=config,
192 repo_root=repo_root,
193 options=HonestyStatusOptions(hook="board_done", artifact="artifacts/sample.txt"),
194 )
195 assert result.exit_code == 0
196
197 cfg_path = repo_root / ".overseer" / "config.yaml"
198 data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
199 data["honesty"]["require_l1_evidence"] = "require"
200 cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8")
201 config = load_config(cfg_path)
202 body2 = body.copy()
203 body2["actor_session_id"] = "verifier-2"
204 body2["evidence"] = {"reexecuted": ["manual-check"], "notes": "no l1"}
205 append_entry(
206 config=config,
207 repo_root=repo_root,
208 options=LedgerAppendOptions(kind="verdict", body=body2),
209 )
210 result2 = run_honesty_status(
211 config=config,
212 repo_root=repo_root,
213 options=HonestyStatusOptions(hook="board_done", artifact="artifacts/sample.txt"),
214 )
215 assert result2.exit_code == 20
216
217
218 def test_verify_chain_detects_break() -> None:
219 genesis = build_genesis_entry("2026-01-01T00:00:00Z")
220 bad = dict(genesis)
221 bad["entry_hash"] = "0" * 64
222 assert verify_chain([genesis]) == 0
223 assert verify_chain([bad]) == 22
File History 2 commits
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439 feat: K1-P1 complete — agent provenance, build-verification… Sonnet 4.6 patch 52 days ago