test_checkpoints_k9b.py
python
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