""" Fail-closed validator tests for the revision-2 canon lifecycle contract. Every negative case asserts BOTH rejection and a specific reason. The suite is designed to run from a clean checkout and leave no generated artifacts (__pycache__ is gitignored; nothing else is written). Loads the validator by file path because the project directory name (`slop-cannon`) contains a hyphen and is not importable as a package. """ import copy import importlib.util import json import sys import unittest from pathlib import Path CANON = Path(__file__).parents[1] / "projects" / "slop-cannon" / "canon.json" VALIDATOR_PATH = Path(__file__).parents[1] / "projects" / "slop-cannon" / "canon_validator.py" def _load_validator(): spec = importlib.util.spec_from_file_location("canon_validator", VALIDATOR_PATH) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod cv = _load_validator() def _valid_canon(): return json.loads(CANON.read_text()) def _valid_receipt(): return { "episode_id": "01-pilot", "form_id": "wizard", "action": "mutation", "power_delta": "Added lifecycle state tracking", "scar_delta": "Receipts now enforce structured provenance", "cost_delta": "Every drop requires a signed, bound receipt", "provenance": {"source_issue": 19, "pull": 27}, "signed_by": "agent:vincent", } def _committed_canon(survivor_id="wizard"): """A canon with a valid audience choice: one survivor, rest archive_ghost.""" data = _valid_canon() for f in data["forms"]: f["state"] = "survivor" if f["id"] == survivor_id else "archive_ghost" data["audience_choice"] = { "survivor_id": survivor_id, "stolen_trait": "Proves the execution path", "source_episode": "01-pilot", "source_issue": 19, "committed_at": "2026-08-14T18:00:00Z", } return data class SchemaVersionTests(unittest.TestCase): """Review item 3: schema_version is enforced, not just present.""" def test_current_canon_is_valid(self): result = cv.validate_canon(_valid_canon()) self.assertTrue(result["valid"], result["errors"]) def test_missing_schema_version_fails(self): data = _valid_canon() del data["schema_version"] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("schema_version" in e for e in result["errors"])) def test_wrong_schema_version_fails(self): data = _valid_canon() data["schema_version"] = 1 result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("schema_version" in e and "unsupported" in e for e in result["errors"])) class ReceiptStructureTests(unittest.TestCase): """Review items 5 & 7: exact deltas, structured provenance, fail-closed.""" def test_valid_receipt_passes(self): data = _valid_canon() data["receipts"] = [_valid_receipt()] result = cv.validate_canon(data) self.assertTrue(result["valid"], result["errors"]) def test_missing_action_fails(self): data = _valid_canon() r = _valid_receipt() del r["action"] data["receipts"] = [r] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("action" in e for e in result["errors"])) def test_missing_power_delta_fails(self): data = _valid_canon() r = _valid_receipt() del r["power_delta"] data["receipts"] = [r] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("power_delta" in e for e in result["errors"])) def test_missing_scar_delta_fails(self): data = _valid_canon() r = _valid_receipt() del r["scar_delta"] data["receipts"] = [r] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("scar_delta" in e for e in result["errors"])) def test_missing_cost_delta_fails(self): data = _valid_canon() r = _valid_receipt() del r["cost_delta"] data["receipts"] = [r] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("cost_delta" in e for e in result["errors"])) def test_empty_delta_string_fails(self): data = _valid_canon() r = _valid_receipt() r["power_delta"] = " " data["receipts"] = [r] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("power_delta" in e for e in result["errors"])) def test_free_text_provenance_fails(self): data = _valid_canon() r = _valid_receipt() r["provenance"] = "pull/27@deadbeef" # string, not structured data["receipts"] = [r] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("provenance must be an object" in e for e in result["errors"])) def test_provenance_missing_binding_fails(self): data = _valid_canon() r = _valid_receipt() r["provenance"] = {"source_issue": 19} # no pull or commit data["receipts"] = [r] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("must bind a pull" in e for e in result["errors"])) def test_provenance_bad_commit_hash_fails(self): data = _valid_canon() r = _valid_receipt() r["provenance"] = {"source_issue": 19, "commit": "xyz"} # not hex data["receipts"] = [r] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("must bind a pull" in e for e in result["errors"])) def test_provenance_commit_only_passes(self): data = _valid_canon() r = _valid_receipt() r["provenance"] = {"source_issue": 19, "commit": "b8d5e686c2"} data["receipts"] = [r] result = cv.validate_canon(data) self.assertTrue(result["valid"], result["errors"]) def test_provenance_bad_source_issue_fails(self): data = _valid_canon() r = _valid_receipt() r["provenance"] = {"source_issue": "19", "pull": 27} # string, not int data["receipts"] = [r] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("source_issue" in e for e in result["errors"])) def test_none_receipt_fails_closed(self): data = _valid_canon() data["receipts"] = [None] result = cv.validate_canon(data) # must not raise self.assertFalse(result["valid"]) self.assertTrue(any("receipt must be an object" in e for e in result["errors"])) def test_non_dict_receipt_fails_closed(self): data = _valid_canon() data["receipts"] = ["just a string"] result = cv.validate_canon(data) # must not raise self.assertFalse(result["valid"]) self.assertTrue(any("receipt must be an object" in e for e in result["errors"])) class SignerAllowlistTests(unittest.TestCase): """Review item 6: true configured allowlist, not a namespace pattern.""" def test_known_agent_passes(self): data = _valid_canon() data["receipts"] = [_valid_receipt()] self.assertTrue(cv.validate_canon(data)["valid"]) def test_human_anyone_rejected(self): data = _valid_canon() r = _valid_receipt() r["signed_by"] = "human:anyone" data["receipts"] = [r] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("not in the configured allowlist" in e for e in result["errors"])) def test_unknown_agent_rejected(self): data = _valid_canon() r = _valid_receipt() r["signed_by"] = "agent:unknown" data["receipts"] = [r] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("not in the configured allowlist" in e for e in result["errors"])) def test_configured_human_passes(self): data = _valid_canon() r = _valid_receipt() r["signed_by"] = "human:grepples" data["receipts"] = [r] result = cv.validate_canon(data) self.assertTrue(result["valid"], result["errors"]) def test_custom_signer_set_respected(self): data = _valid_canon() r = _valid_receipt() r["signed_by"] = "agent:custom" data["receipts"] = [r] # default allowlist rejects it self.assertFalse(cv.validate_canon(data)["valid"]) # but an explicit allowlist including it accepts it self.assertTrue(cv.validate_canon(data, signers={"agent:custom"})["valid"]) class AudienceChoiceTests(unittest.TestCase): """Review items 2 & 7: sole survivor, rejected forms archived, non-empty sources.""" def test_valid_committed_canon_passes(self): result = cv.validate_canon(_committed_canon()) self.assertTrue(result["valid"], result["errors"]) def test_choice_without_survivor_fails(self): data = _valid_canon() # all candidates data["audience_choice"] = { "survivor_id": "wizard", "stolen_trait": "Proves the execution path", "source_episode": "01-pilot", "source_issue": 19, "committed_at": "2026-08-14T18:00:00Z", } result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("survivor" in e for e in result["errors"])) def test_rejected_forms_must_be_archive_ghost(self): data = _committed_canon() # One rejected form is still a candidate -> must fail for f in data["forms"]: if f["id"] == "machine": f["state"] = "candidate" result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("archive_ghost" in e for e in result["errors"])) def test_two_survivors_fails(self): data = _committed_canon() for f in data["forms"]: if f["id"] in ("wizard", "machine"): f["state"] = "survivor" data["audience_choice"]["survivor_id"] = "wizard" result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("survivor" in e for e in result["errors"])) def test_survivor_id_mismatch_fails(self): data = _committed_canon("machine") # machine is survivor data["audience_choice"]["survivor_id"] = "wizard" # points elsewhere result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("does not match the sole survivor" in e for e in result["errors"])) def test_empty_stolen_trait_fails(self): data = _committed_canon() data["audience_choice"]["stolen_trait"] = "" result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("stolen_trait" in e for e in result["errors"])) def test_empty_source_episode_fails(self): data = _committed_canon() data["audience_choice"]["source_episode"] = " " result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("source_episode" in e for e in result["errors"])) def test_bad_committed_at_fails(self): data = _committed_canon() data["audience_choice"]["committed_at"] = "not-a-date" result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("ISO timestamp" in e for e in result["errors"])) def test_choice_non_dict_fails_closed(self): data = _valid_canon() data["audience_choice"] = "wizard" result = cv.validate_canon(data) # must not raise self.assertFalse(result["valid"]) self.assertTrue(any("audience_choice must be an object" in e for e in result["errors"])) class StructuralFailClosedTests(unittest.TestCase): """Review item 7: malformed containers never raise.""" def test_non_dict_canon_fails_closed(self): result = cv.validate_canon([1, 2, 3]) # must not raise self.assertFalse(result["valid"]) self.assertTrue(any("canon must be an object" in e for e in result["errors"])) def test_forms_non_list_fails_closed(self): data = _valid_canon() data["forms"] = "not a list" result = cv.validate_canon(data) # must not raise self.assertFalse(result["valid"]) self.assertTrue(any("forms must be a non-empty array" in e for e in result["errors"])) def test_non_dict_form_fails_closed(self): data = _valid_canon() data["forms"].append(42) result = cv.validate_canon(data) # must not raise self.assertFalse(result["valid"]) self.assertTrue(any("form must be an object" in e for e in result["errors"])) def test_duplicate_form_id_fails(self): data = _valid_canon() data["forms"].append(copy.deepcopy(data["forms"][0])) result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("duplicate form id" in e for e in result["errors"])) def test_short_palette_fails(self): data = _valid_canon() data["forms"][0]["palette"] = ["red"] result = cv.validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("palette" in e for e in result["errors"])) class TransitionTests(unittest.TestCase): """State machine: candidate -> survivor | archive_ghost; terminal after.""" def test_candidate_to_survivor_valid(self): result = cv.validate_transition({"id": "wizard", "state": "candidate"}, "survivor") self.assertTrue(result["valid"]) def test_candidate_to_ghost_valid(self): result = cv.validate_transition({"id": "wizard", "state": "candidate"}, "archive_ghost") self.assertTrue(result["valid"]) def test_survivor_is_terminal(self): result = cv.validate_transition({"id": "wizard", "state": "survivor"}, "archive_ghost") self.assertFalse(result["valid"]) self.assertTrue(any("illegal transition" in e for e in result["errors"])) def test_ghost_is_terminal(self): result = cv.validate_transition({"id": "wizard", "state": "archive_ghost"}, "candidate") self.assertFalse(result["valid"]) self.assertTrue(any("illegal transition" in e for e in result["errors"])) def test_non_dict_form_fails_closed(self): result = cv.validate_transition("wizard", "survivor") # must not raise self.assertFalse(result["valid"]) class DailyReceiptFixtureTests(unittest.TestCase): """Daily Lab receipt fixtures with structured provenance binding.""" def test_vincent_receipt_fixture(self): data = _valid_canon() data["receipts"] = [_valid_receipt()] result = cv.validate_canon(data) self.assertTrue(result["valid"], result["errors"]) def test_mixed_agent_receipts(self): data = _valid_canon() vincent = _valid_receipt() timmy = _valid_receipt() timmy["form_id"] = "machine" timmy["signed_by"] = "agent:timmy" timmy["provenance"] = {"source_issue": 8, "pull": 14} data["receipts"] = [vincent, timmy] result = cv.validate_canon(data) self.assertTrue(result["valid"], result["errors"]) def test_human_receipt_fixture(self): data = _valid_canon() r = _valid_receipt() r["signed_by"] = "human:grepples" data["receipts"] = [r] result = cv.validate_canon(data) self.assertTrue(result["valid"], result["errors"]) if __name__ == "__main__": unittest.main()