test_checkpoints_k9b.py python
252 lines 9.3 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago
1 """Unit tests for K9b checkpoint config, schema, and orchestrator 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 CHECKPOINTS, load_checkpoint_config, seed_checkpoint_repo, write_config
14 from tools.checkpoints.advance import compute_advance
15 from tools.checkpoints.artifact_hash import parse_artifact_sha256
16 from tools.checkpoints.argv_builder import build_verify_argv
17 from tools.checkpoints.executor import ExecResult, RecordingScriptExecutor
18 from tools.checkpoints.ids import is_valid_step_id
19 from tools.checkpoints.orchestrator import VerifyStepOptions, run_verify_step
20 from tools.checkpoints.schema import CheckpointSchemaError, load_manifest, load_policy, resolve_template_steps
21
22
23 def test_step_id_regex() -> None:
24 assert is_valid_step_id("alpha")
25 assert is_valid_step_id("a1_b-2")
26 assert not is_valid_step_id("Alpha")
27 assert not is_valid_step_id("")
28
29
30 def test_artifact_sha256_trailing_newlines() -> None:
31 assert parse_artifact_sha256(b"line\nARTIFACT_SHA256=AbCdEf\n\n") == "abcdef"
32 assert parse_artifact_sha256(b"ARTIFACT_SHA256=ff00") == "ff00"
33 assert parse_artifact_sha256(b"no hash here") is None
34 assert parse_artifact_sha256(b"\xff\xfe") is None
35
36
37 def test_argv_builder_always_includes_policy() -> None:
38 argv = build_verify_argv(
39 verify_script="scripts/verify/verify_alpha.py",
40 manifest_rel="manifests/work-unit.yaml",
41 step_id="alpha",
42 policy_rel="policy/checkpoints.yaml",
43 )
44 assert argv == [
45 "scripts/verify/verify_alpha.py",
46 "--manifest",
47 "manifests/work-unit.yaml",
48 "--step",
49 "alpha",
50 "--policy",
51 "policy/checkpoints.yaml",
52 ]
53
54
55 def test_current_step_advance() -> None:
56 steps = ["alpha", "beta", "gamma"]
57 verified = {"alpha": True, "beta": False, "gamma": False}
58 assert compute_advance(steps, "alpha", verified, "alpha") == "beta"
59 verified["beta"] = True
60 assert compute_advance(steps, "beta", verified, "beta") == "gamma"
61 verified["gamma"] = True
62 assert compute_advance(steps, "gamma", verified, "gamma") == "gamma"
63
64
65 def test_checkpoints_config_mirror_mismatch_raises(repo_root: Path) -> None:
66 seed_checkpoint_repo(repo_root)
67 cfg_path = repo_root / ".overseer" / "config.yaml"
68 data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
69 data["modules"]["checkpoints"]["enabled"] = False
70 cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8")
71 with pytest.raises(ConfigError, match="must equal checkpoints.enabled"):
72 load_config(cfg_path)
73
74
75 def test_governance_disabled_raises(repo_root: Path) -> None:
76 seed_checkpoint_repo(repo_root)
77 cfg_path = repo_root / ".overseer" / "config.yaml"
78 data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
79 data["modules"]["governance"]["enabled"] = False
80 cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8")
81 with pytest.raises(ConfigError, match="governance.enabled cannot be false"):
82 load_config(cfg_path)
83
84
85 def test_extensions_well_formed_warn_only(repo_root: Path) -> None:
86 seed_checkpoint_repo(repo_root)
87 cfg_path = repo_root / ".overseer" / "config.yaml"
88 data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
89 data["extensions"] = [{"id": "future", "schema_version": 1, "config_path": "ext.yaml"}]
90 cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8")
91 cfg = load_config(cfg_path)
92 assert cfg.extensions
93 assert cfg.extension_warnings
94
95
96 def test_extensions_malformed_raises(repo_root: Path) -> None:
97 seed_checkpoint_repo(repo_root)
98 cfg_path = repo_root / ".overseer" / "config.yaml"
99 data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
100 data["extensions"] = [{"id": "x"}]
101 cfg_path.write_text(yaml.safe_dump(data), encoding="utf-8")
102 with pytest.raises(ConfigError, match="schema_version"):
103 load_config(cfg_path)
104
105
106 def test_policy_version_not_one_raises(repo_root: Path) -> None:
107 seed_checkpoint_repo(repo_root)
108 policy_path = repo_root / "policy" / "checkpoints.yaml"
109 data = yaml.safe_load(policy_path.read_text(encoding="utf-8"))
110 data["version"] = 2
111 policy_path.write_text(yaml.safe_dump(data), encoding="utf-8")
112 with pytest.raises(CheckpointSchemaError, match="version must be integer 1"):
113 load_policy(policy_path)
114
115
116 def test_unused_broken_template_not_validated_until_selected(repo_root: Path) -> None:
117 seed_checkpoint_repo(repo_root)
118 policy = load_policy(repo_root / "policy" / "checkpoints.yaml")
119 manifest = load_manifest(repo_root / "manifests" / "work-unit.yaml")
120 steps = resolve_template_steps(policy, manifest)
121 assert steps == ["alpha", "beta"]
122
123
124 def test_step_not_in_template_refused(repo_root: Path) -> None:
125 config = load_checkpoint_config(repo_root)
126 result = run_verify_step(
127 config=config,
128 repo_root=repo_root,
129 options=VerifyStepOptions(step_id="gamma", emit_json=True),
130 )
131 assert result.exit_code == 2
132
133
134 def test_current_step_not_in_template_refused(repo_root: Path) -> None:
135 seed_checkpoint_repo(repo_root)
136 manifest_path = repo_root / "manifests" / "work-unit.yaml"
137 data = yaml.safe_load(manifest_path.read_text(encoding="utf-8"))
138 data["current_step"] = "gamma"
139 manifest_path.write_text(yaml.safe_dump(data), encoding="utf-8")
140 config = load_config(repo_root / ".overseer" / "config.yaml")
141 result = run_verify_step(
142 config=config,
143 repo_root=repo_root,
144 options=VerifyStepOptions(step_id="alpha", emit_json=True),
145 )
146 assert result.exit_code == 2
147
148
149 def test_missing_manifest_step_refused(repo_root: Path) -> None:
150 seed_checkpoint_repo(repo_root)
151 manifest_path = repo_root / "manifests" / "work-unit.yaml"
152 data = yaml.safe_load(manifest_path.read_text(encoding="utf-8"))
153 del data["steps"]["beta"]
154 manifest_path.write_text(yaml.safe_dump(data), encoding="utf-8")
155 config = load_config(repo_root / ".overseer" / "config.yaml")
156 result = run_verify_step(
157 config=config,
158 repo_root=repo_root,
159 options=VerifyStepOptions(step_id="alpha", emit_json=True),
160 )
161 assert result.exit_code == 2
162
163
164 def test_checkpoints_disabled_refused_without_manifest(repo_root: Path) -> None:
165 write_config(repo_root, "config-git-only.yaml")
166 config = load_config(repo_root / ".overseer" / "config.yaml")
167 result = run_verify_step(
168 config=config,
169 repo_root=repo_root,
170 options=VerifyStepOptions(step_id="alpha", emit_json=True),
171 )
172 assert result.exit_code == 4
173
174
175 def test_dry_run_no_writes(repo_root: Path) -> None:
176 config = load_checkpoint_config(repo_root)
177 manifest_path = repo_root / "manifests" / "work-unit.yaml"
178 before = manifest_path.read_bytes()
179 result = run_verify_step(
180 config=config,
181 repo_root=repo_root,
182 options=VerifyStepOptions(step_id="alpha", dry_run=True, emit_json=True),
183 )
184 assert result.exit_code == 0
185 assert result.json_payload.dry_run is True
186 assert manifest_path.read_bytes() == before
187
188
189 def test_json_dry_run_echo_on_empty_through(repo_root: Path) -> None:
190 seed_checkpoint_repo(repo_root)
191 shutil_copy = CHECKPOINTS / "manifests" / "all-verified.yaml"
192 (repo_root / "manifests" / "work-unit.yaml").write_text(
193 shutil_copy.read_text(encoding="utf-8"),
194 encoding="utf-8",
195 )
196 config = load_config(repo_root / ".overseer" / "config.yaml")
197 result = run_verify_step(
198 config=config,
199 repo_root=repo_root,
200 options=VerifyStepOptions(through_current=True, dry_run=True, emit_json=True),
201 )
202 assert result.exit_code == 0
203 assert result.json_payload.dry_run is True
204 assert result.json_payload.steps == []
205
206
207 def test_orchestrator_missing_refused(repo_root: Path) -> None:
208 config = load_checkpoint_config(repo_root)
209 config_path = repo_root / ".overseer" / "config.yaml"
210 data = yaml.safe_load(config_path.read_text(encoding="utf-8"))
211 data["checkpoints"]["orchestrator"] = "scripts/missing-orchestrator.sh"
212 config_path.write_text(yaml.safe_dump(data), encoding="utf-8")
213 config = load_config(config_path)
214 result = run_verify_step(
215 config=config,
216 repo_root=repo_root,
217 options=VerifyStepOptions(step_id="alpha", emit_json=True),
218 )
219 assert result.exit_code == 4
220
221
222 def test_manifest_write_io_exit_5(repo_root: Path) -> None:
223 config = load_checkpoint_config(repo_root)
224 executor = RecordingScriptExecutor(
225 responses={
226 (
227 "scripts/verify/verify_alpha.py",
228 "--manifest",
229 "manifests/work-unit.yaml",
230 "--step",
231 "alpha",
232 "--policy",
233 "policy/checkpoints.yaml",
234 ): ExecResult(stdout=b"", stderr=b"", exit_code=0),
235 },
236 calls=[],
237 )
238
239 def fail_write(_path: Path, _text: str) -> None:
240 from cli.atomic import WriteFailure
241
242 raise WriteFailure(_path, OSError("disk full"))
243
244 result = run_verify_step(
245 config=config,
246 repo_root=repo_root,
247 options=VerifyStepOptions(step_id="alpha", emit_json=True),
248 executor=executor,
249 write_manifest=fail_write,
250 )
251 assert result.exit_code == 5
252 assert result.json_payload.error == "io"
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 53 days ago