test_provenance_unit.py
python
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439
feat: K1-P1 complete — agent provenance, build-verification…
Sonnet 4.6
patch
54 days ago
| 1 | """Unit tests for Track P / P1 provenance envelope (§P0.8).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import pytest |
| 6 | |
| 7 | from adapters.config import load_config |
| 8 | from adapters.errors import ConfigError |
| 9 | from tests.support import ( |
| 10 | FIXTURES, |
| 11 | generate_ed25519_keypair, |
| 12 | seed_honesty_repo, |
| 13 | sign_entry_hash, |
| 14 | write_config, |
| 15 | ) |
| 16 | from tools.honesty.canonical import compute_entry_hash |
| 17 | from tools.honesty.ed25519_util import verify_ed25519_signature |
| 18 | from tools.honesty.genesis import GENESIS_PREV |
| 19 | from tools.honesty.provenance import validate_provenance |
| 20 | from tools.honesty.validate import EntryValidationError, validate_append_body |
| 21 | |
| 22 | |
| 23 | def test_provenance_required_fields() -> None: |
| 24 | with pytest.raises(EntryValidationError) as exc: |
| 25 | validate_provenance({"agent_id": "a"}) |
| 26 | assert exc.value.exit_code == 2 |
| 27 | |
| 28 | |
| 29 | def test_provenance_strict_keys() -> None: |
| 30 | with pytest.raises(EntryValidationError) as exc: |
| 31 | validate_provenance({"agent_id": "a", "model_id": "m", "extra": True}) |
| 32 | assert exc.value.exit_code == 2 |
| 33 | |
| 34 | |
| 35 | def test_provenance_sig_pubkey_pairing() -> None: |
| 36 | with pytest.raises(EntryValidationError) as exc: |
| 37 | validate_provenance({"agent_id": "a", "model_id": "m", "sig": "ed25519:AA=="}) |
| 38 | assert exc.value.exit_code == 2 |
| 39 | |
| 40 | |
| 41 | def test_compute_entry_hash_excludes_provenance_sig() -> None: |
| 42 | private_key, pubkey = generate_ed25519_keypair() |
| 43 | body = { |
| 44 | "v": 1, |
| 45 | "kind": "verdict", |
| 46 | "ts": "2026-01-01T00:00:00Z", |
| 47 | "prev_hash": GENESIS_PREV, |
| 48 | "provenance": { |
| 49 | "agent_id": "cursor-agent", |
| 50 | "model_id": "gpt-5.6", |
| 51 | "pubkey": pubkey, |
| 52 | }, |
| 53 | } |
| 54 | unsigned_hash = compute_entry_hash(body) |
| 55 | body["provenance"]["sig"] = sign_entry_hash(private_key, unsigned_hash) |
| 56 | assert compute_entry_hash(body) == unsigned_hash |
| 57 | |
| 58 | |
| 59 | def test_legacy_unsigned_entry_hash_unchanged() -> None: |
| 60 | body = { |
| 61 | "v": 1, |
| 62 | "kind": "hook_check", |
| 63 | "ts": "2026-01-01T00:00:00Z", |
| 64 | "prev_hash": GENESIS_PREV, |
| 65 | "actor_role": "overseer", |
| 66 | "actor_session_id": "legacy", |
| 67 | "hook": "handoff", |
| 68 | "ok": True, |
| 69 | } |
| 70 | expected = "0044b277fdbebdf824a0dd6005dc5630a9fe4d65f5b01d029cdd7028f6122d4e" |
| 71 | # Pin the legacy hash so regressions are caught if canonical rules drift. |
| 72 | assert compute_entry_hash(body) == expected |
| 73 | |
| 74 | |
| 75 | def test_ed25519_sign_verify_round_trip() -> None: |
| 76 | private_key, pubkey = generate_ed25519_keypair() |
| 77 | entry_hash = "abc123" * 10 + "abcd" |
| 78 | sig = sign_entry_hash(private_key, entry_hash) |
| 79 | assert verify_ed25519_signature(pubkey_token=pubkey, entry_hash_hex=entry_hash, sig_token=sig) |
| 80 | |
| 81 | |
| 82 | def test_genesis_rejects_provenance() -> None: |
| 83 | with pytest.raises(EntryValidationError) as exc: |
| 84 | validate_append_body( |
| 85 | kind="genesis", |
| 86 | body={"provenance": {"agent_id": "a", "model_id": "m"}}, |
| 87 | ) |
| 88 | assert exc.value.exit_code == 2 |
| 89 | |
| 90 | |
| 91 | def test_soft_provenance_allowed_on_verdict() -> None: |
| 92 | merged = validate_append_body( |
| 93 | kind="verdict", |
| 94 | body={ |
| 95 | "actor_role": "verifier", |
| 96 | "actor_session_id": "v1", |
| 97 | "artifact_sha256": "aa" * 32, |
| 98 | "passed": True, |
| 99 | "evidence": {"reexecuted": ["verify-step:x"]}, |
| 100 | "provenance": {"agent_id": "cursor-agent", "model_id": "gpt-5.6"}, |
| 101 | }, |
| 102 | ) |
| 103 | assert merged["provenance"]["agent_id"] == "cursor-agent" |
| 104 | |
| 105 | |
| 106 | def test_git_only_require_agent_signature_config_error_26(repo_root) -> None: |
| 107 | seed_honesty_repo(repo_root) |
| 108 | cfg = repo_root / ".overseer" / "config.yaml" |
| 109 | import yaml |
| 110 | |
| 111 | data = yaml.safe_load(cfg.read_text(encoding="utf-8")) |
| 112 | data["honesty"]["require_agent_signature"] = True |
| 113 | cfg.write_text(yaml.safe_dump(data), encoding="utf-8") |
| 114 | with pytest.raises(ConfigError) as exc: |
| 115 | load_config(cfg) |
| 116 | assert exc.value.exit_code == 26 |
| 117 | |
| 118 | |
| 119 | def test_muse_regime_allows_require_agent_signature(tmp_path) -> None: |
| 120 | write_config(tmp_path, "config-muse-git-mirror.yaml") |
| 121 | cfg = tmp_path / ".overseer" / "config.yaml" |
| 122 | import yaml |
| 123 | |
| 124 | data = yaml.safe_load(cfg.read_text(encoding="utf-8")) |
| 125 | data.setdefault("honesty", {})["enabled"] = True |
| 126 | data["honesty"]["ledger"] = ".overseer/honesty/VERDICT-LEDGER.jsonl" |
| 127 | data["honesty"]["require_agent_signature"] = True |
| 128 | cfg.write_text(yaml.safe_dump(data), encoding="utf-8") |
| 129 | config = load_config(cfg) |
| 130 | assert config.honesty.require_agent_signature is True |
File History
1 commit
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439
feat: K1-P1 complete — agent provenance, build-verification…
Sonnet 4.6
patch
54 days ago