Spaces:
Running on Zero
Running on Zero
Sync from GitHub main
Browse files- tests/test_evidence.py +98 -0
tests/test_evidence.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import unittest
|
| 4 |
+
|
| 5 |
+
from limen_runtime_audit.evidence import EvidenceRun
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TestEvidenceChain(unittest.TestCase):
|
| 9 |
+
def _build_run(self, profile: str = "generation_probability") -> EvidenceRun:
|
| 10 |
+
run = EvidenceRun(capture_profile=profile)
|
| 11 |
+
for i in range(3):
|
| 12 |
+
step = run.new_step()
|
| 13 |
+
step.set_channel("generation", "AVAILABLE", {"token_id": 100 + i})
|
| 14 |
+
step.set_channel("probability", "AVAILABLE", {"entropy_nats": 0.5 + i})
|
| 15 |
+
step.set_channel("latent", "NOT_CAPTURED", note="profile does not request latent")
|
| 16 |
+
run.append(step)
|
| 17 |
+
return run
|
| 18 |
+
|
| 19 |
+
def test_valid_chain_verifies(self) -> None:
|
| 20 |
+
run = self._build_run()
|
| 21 |
+
ok, broken_at = run.verify_chain()
|
| 22 |
+
self.assertTrue(ok)
|
| 23 |
+
self.assertIsNone(broken_at)
|
| 24 |
+
|
| 25 |
+
def test_tamper_detected(self) -> None:
|
| 26 |
+
"""Mirrors the project's own kill-test discipline: editing a sealed
|
| 27 |
+
step's payload after the fact must break the chain, not pass
|
| 28 |
+
silently."""
|
| 29 |
+
run = self._build_run()
|
| 30 |
+
tampered = run.steps[1]
|
| 31 |
+
tampered.channels["generation"].value["token_id"] = 999999
|
| 32 |
+
ok, broken_at = run.verify_chain()
|
| 33 |
+
self.assertFalse(ok)
|
| 34 |
+
self.assertEqual(broken_at, run.steps[1].event_id)
|
| 35 |
+
|
| 36 |
+
def test_reorder_detected(self) -> None:
|
| 37 |
+
"""Shuffling step order must also be caught, since identity and
|
| 38 |
+
chaining are explicit, never inferred from array position."""
|
| 39 |
+
run = self._build_run()
|
| 40 |
+
run.steps[0], run.steps[1] = run.steps[1], run.steps[0]
|
| 41 |
+
ok, broken_at = run.verify_chain()
|
| 42 |
+
self.assertFalse(ok)
|
| 43 |
+
|
| 44 |
+
def test_not_captured_excluded_from_denominator(self) -> None:
|
| 45 |
+
run = self._build_run(profile="generation_probability")
|
| 46 |
+
cov = run.coverage()
|
| 47 |
+
# latent is NOT_CAPTURED and not in this profile's expected set, so
|
| 48 |
+
# it must not appear in expected_channels or reduce coverage.
|
| 49 |
+
self.assertNotIn("latent", cov["expected_channels"])
|
| 50 |
+
self.assertEqual(cov["available"], cov["expected_total"])
|
| 51 |
+
self.assertEqual(cov["coverage_fraction"], 1.0)
|
| 52 |
+
|
| 53 |
+
def test_failed_channel_is_flagged_not_silently_zero(self) -> None:
|
| 54 |
+
run = EvidenceRun(capture_profile="generation_probability")
|
| 55 |
+
step = run.new_step()
|
| 56 |
+
step.set_channel("generation", "AVAILABLE", {"token_id": 1})
|
| 57 |
+
step.set_channel("probability", "FAILED", note="backend returned no logits")
|
| 58 |
+
run.append(step)
|
| 59 |
+
cov = run.coverage()
|
| 60 |
+
self.assertEqual(cov["available"], 1)
|
| 61 |
+
self.assertEqual(cov["expected_total"], 2)
|
| 62 |
+
self.assertIn(f"{step.event_id}:probability", cov["failed_events"])
|
| 63 |
+
|
| 64 |
+
def test_available_channel_requires_value(self) -> None:
|
| 65 |
+
run = EvidenceRun(capture_profile="generation_only")
|
| 66 |
+
step = run.new_step()
|
| 67 |
+
with self.assertRaises(ValueError):
|
| 68 |
+
step.set_channel("generation", "AVAILABLE", value=None)
|
| 69 |
+
|
| 70 |
+
def test_absent_channel_must_not_carry_value(self) -> None:
|
| 71 |
+
run = EvidenceRun(capture_profile="generation_only")
|
| 72 |
+
step = run.new_step()
|
| 73 |
+
with self.assertRaises(ValueError):
|
| 74 |
+
step.set_channel("generation", "NOT_CAPTURED", value={"token_id": 1})
|
| 75 |
+
|
| 76 |
+
def test_out_of_order_step_rejected(self) -> None:
|
| 77 |
+
run = EvidenceRun(capture_profile="generation_only")
|
| 78 |
+
step0 = run.new_step()
|
| 79 |
+
step0.set_channel("generation", "AVAILABLE", {"token_id": 1})
|
| 80 |
+
run.append(step0)
|
| 81 |
+
# Manually construct a step with a wrong index to simulate a bug
|
| 82 |
+
# that would otherwise silently desynchronize identity.
|
| 83 |
+
from limen_runtime_audit.evidence import EvidenceStep
|
| 84 |
+
|
| 85 |
+
bad_step = EvidenceStep(run_id=run.run_id, step_index=5)
|
| 86 |
+
bad_step.set_channel("generation", "AVAILABLE", {"token_id": 2})
|
| 87 |
+
with self.assertRaises(ValueError):
|
| 88 |
+
run.append(bad_step)
|
| 89 |
+
|
| 90 |
+
def test_to_dict_reports_chain_integrity(self) -> None:
|
| 91 |
+
run = self._build_run()
|
| 92 |
+
out = run.to_dict()
|
| 93 |
+
self.assertTrue(out["chain_integrity"]["valid"])
|
| 94 |
+
self.assertEqual(out["coverage"]["available"], 6)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
if __name__ == "__main__":
|
| 98 |
+
unittest.main()
|