""" Validator tests for canon lifecycle schemas. Covers: - Valid canon structure (from #13 fix) - Negative tests: malformed receipts, illegal transitions - Daily Lab receipt fixture with provenance binding """ import json import sys import unittest from pathlib import Path from copy import deepcopy # Add project root to path for imports sys.path.insert(0, str(Path(__file__).parents[1])) CANON = Path(__file__).parents[1] / "projects" / "slop_cannon" / "canon.json" from projects.slop_cannon.canon_validator import ( validate_canon, validate_transition, ) class CanonValidatorTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.data = json.loads(CANON.read_text()) # --- Positive tests --- def test_current_canon_is_valid(self): """canon.json as committed should pass validation.""" result = validate_canon(self.data) self.assertTrue(result["valid"], f"Errors: {result['errors']}") def test_all_forms_have_valid_state(self): """Every form state must be in the valid set.""" result = validate_canon(self.data) self.assertTrue(result["valid"], result["errors"]) # --- Negative: malformed receipts --- def test_missing_signed_by_fails(self): """Receipt without signed_by is rejected.""" data = deepcopy(self.data) data["receipts"] = [ { "episode_id": "01-pilot", "form_id": "wizard", "provenance": "#13", } ] result = validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("signed_by" in e for e in result["errors"])) def test_invalid_signer_fails(self): """Signed_by must match agent: or human: pattern.""" data = deepcopy(self.data) data["receipts"] = [ { "episode_id": "01-pilot", "form_id": "wizard", "provenance": "#13", "signed_by": "unknown:bot", } ] result = validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("invalid signed_by" in e for e in result["errors"])) def test_empty_provenance_fails(self): """Receipt with empty provenance is rejected.""" data = deepcopy(self.data) data["receipts"] = [ { "episode_id": "01-pilot", "form_id": "wizard", "provenance": "", "signed_by": "agent:vincent", } ] result = validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("provenance" in e for e in result["errors"])) def test_form_id_must_exist(self): """Receipt referencing nonexistent form is rejected.""" data = deepcopy(self.data) data["receipts"] = [ { "episode_id": "01-pilot", "form_id": "nonexistent", "provenance": "#13", "signed_by": "agent:vincent", } ] result = validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("not found in forms" in e for e in result["errors"])) # --- Negative: illegal state transitions --- def test_survivor_cannot_transition(self): """Survivor state is terminal.""" form = {"id": "wizard", "state": "survivor"} result = validate_transition(form, "archive_ghost") self.assertFalse(result["valid"]) self.assertTrue(any("Illegal transition" in e for e in result["errors"])) def test_ghost_cannot_transition(self): """Archive_ghost state is terminal.""" form = {"id": "wizard", "state": "archive_ghost"} result = validate_transition(form, "candidate") self.assertFalse(result["valid"]) self.assertTrue(any("Illegal transition" in e for e in result["errors"])) def test_candidate_to_survivor_valid(self): """Candidate -> survivor is the expected happy path.""" form = {"id": "wizard", "state": "candidate"} result = validate_transition(form, "survivor") self.assertTrue(result["valid"]) def test_candidate_to_ghost_valid(self): """Candidate -> archive_ghost is valid (rejected form).""" form = {"id": "wizard", "state": "candidate"} result = validate_transition(form, "archive_ghost") self.assertTrue(result["valid"]) # --- Negative: audience choice --- def test_two_survivors_fails(self): """Only one survivor allowed.""" data = deepcopy(self.data) for f in data["forms"][:2]: f["state"] = "survivor" result = validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("survivor" in e.lower() for e in result["errors"])) def test_choice_without_survivor_fails(self): """Setting audience_choice requires a survivor in forms.""" data = deepcopy(self.data) data["audience_choice"] = { "survivor_id": "wizard", "stolen_trait": "Proves the execution path", "source_episode": "01-pilot", "source_issue": "7", "committed_at": "2026-08-11T19:00:00Z", } # forms all stay candidate -> no survivor result = validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("survivor" in e.lower() for e in result["errors"])) def test_invalid_committed_at_fails(self): """committed_at must be valid ISO timestamp.""" data = deepcopy(self.data) data["forms"][0]["state"] = "survivor" # satisfy survivor req data["audience_choice"] = { "survivor_id": "wizard", "stolen_trait": "Negotiates visible covenants", "source_episode": "01-pilot", "source_issue": "7", "committed_at": "not-a-date", } result = validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("ISO" in e for e in result["errors"])) # --- Negative: structure --- def test_duplicate_form_id_fails(self): """Duplicate form IDs are rejected.""" data = deepcopy(self.data) data["forms"].append(deepcopy(data["forms"][0])) result = validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("duplicate" in e for e in result["errors"])) def test_short_palette_fails(self): """Palette needs at least 3 colors.""" data = deepcopy(self.data) data["forms"][0]["palette"] = ["red"] result = validate_canon(data) self.assertFalse(result["valid"]) self.assertTrue(any("palette" in e for e in result["errors"])) class DailyReceiptFixtureTests(unittest.TestCase): """Test the Daily Lab receipt fixture with Vincent's provenance.""" def test_vincent_receipt_fixture(self): """A valid Vincent contribution receipt passes validation.""" data = deepcopy(json.loads(CANON.read_text())) # Build a receipt for the #13 contribution data["receipts"] = [ { "episode_id": "01-pilot", "form_id": "wizard", "action": "mutation", "power_delta": "Added lifecycle state tracking", "scar_delta": "Receipt schema now enforces provenance", "cost_delta": "All future drops require signed receipts", "provenance": "pull/18@562d0cf", "signed_by": "agent:vincent", } ] result = validate_canon(data) self.assertTrue(result["valid"], f"Fixture failed: {result['errors']}") def test_mixed_agent_receipt(self): """Receipts from different agents can coexist.""" data = deepcopy(json.loads(CANON.read_text())) data["receipts"] = [ { "episode_id": "01-pilot", "form_id": "wizard", "action": "mutation", "power_delta": "Lifecycle state", "scar_delta": "Provenance enforcement", "cost_delta": "Signed receipts required", "provenance": "pull/18@562d0cf", "signed_by": "agent:vincent", }, { "episode_id": "01-pilot", "form_id": "machine", "action": "graft", "power_delta": "Execution path proof", "scar_delta": "Red tallies", "cost_delta": "Remembers failures", "provenance": "pull/12@47ca557", "signed_by": "agent:timmy", }, ] result = validate_canon(data) self.assertTrue(result["valid"], f"Mixed fixture failed: {result['errors']}") def test_human_receipt(self): """Human-signed receipts are valid (for canon decisions).""" data = deepcopy(json.loads(CANON.read_text())) data["receipts"] = [ { "episode_id": "01-pilot", "form_id": "wizard", "action": "mutation", "power_delta": "x", "scar_delta": "x", "cost_delta": "x", "provenance": "#7", "signed_by": "human:grepples", } ] result = validate_canon(data) self.assertTrue(result["valid"], f"Human fixture failed: {result['errors']}") if __name__ == "__main__": unittest.main()