2026-10-03 18:58:57 +02:00
|
|
|
import copy
|
|
|
|
|
import importlib.util
|
|
|
|
|
import json
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
import unittest
|
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
|
|
|
|
|
|
from user_engine.custody import CustodyBaseline, CustodyObservationAdapter, CustodyRead
|
|
|
|
|
|
|
|
|
|
CANON = Path(__file__).resolve().parents[2] / "net-kingdom/tools/custody-record"
|
2026-10-03 19:50:15 +02:00
|
|
|
from user_engine.custody_canon import policy
|
2026-10-03 18:58:57 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class Baselines:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.value = None
|
|
|
|
|
self.conflict = False
|
|
|
|
|
|
|
|
|
|
def load(self):
|
|
|
|
|
return copy.deepcopy(self.value)
|
|
|
|
|
|
|
|
|
|
def compare_and_store(self, previous, current):
|
|
|
|
|
if self.conflict or previous != self.value:
|
|
|
|
|
return False
|
|
|
|
|
self.value = copy.deepcopy(current)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
2026-10-03 19:50:15 +02:00
|
|
|
@unittest.skipUnless(CANON.exists(), "optional sibling NetKingdom fixtures absent")
|
2026-10-03 18:58:57 +02:00
|
|
|
class CustodyAdapterTests(unittest.TestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
self.document = json.loads((CANON / "fixtures/valid/grant.json").read_text())
|
|
|
|
|
self.now = datetime(2026, 10, 1, 10, tzinfo=timezone.utc)
|
|
|
|
|
self.read_at = self.now
|
|
|
|
|
self.source = "fixture-authenticated-owner"
|
|
|
|
|
self.baselines = Baselines()
|
|
|
|
|
self.reads = 0
|
|
|
|
|
self.failure = False
|
|
|
|
|
self.adapter = CustodyObservationAdapter(
|
|
|
|
|
reader=self, baselines=self.baselines, policy=policy,
|
|
|
|
|
source_id=self.source, record_id=self.document["record_id"],
|
|
|
|
|
allow_initial_baseline=True,
|
|
|
|
|
)
|
|
|
|
|
self.subject = dict(self.document["entries"][0]["subject"])
|
|
|
|
|
|
|
|
|
|
def read(self):
|
|
|
|
|
self.reads += 1
|
|
|
|
|
if self.failure:
|
|
|
|
|
raise RuntimeError("sensitive error must never escape")
|
|
|
|
|
return CustodyRead(self.source, self.document, self.read_at)
|
|
|
|
|
|
|
|
|
|
def observe(self, **overrides):
|
|
|
|
|
return self.adapter.observe(**{**self.subject, "tenant": "platform", "now": self.now, **overrides})
|
|
|
|
|
|
|
|
|
|
def test_exact_mapping_and_fresh_read_each_time(self):
|
|
|
|
|
result = self.observe()
|
|
|
|
|
self.assertEqual(result["subject"], self.subject)
|
|
|
|
|
self.assertEqual(result["source"], {"record_id": self.document["record_id"], "revision": 1})
|
|
|
|
|
result["subject"]["subject"] = "changed"
|
|
|
|
|
self.assertEqual(self.observe()["subject"], self.subject)
|
|
|
|
|
self.assertEqual(self.reads, 2)
|
|
|
|
|
self.assertIsNone(self.observe(issuer=self.subject["issuer"] + "/"))
|
|
|
|
|
self.assertIsNone(self.observe(subject="unknown"))
|
|
|
|
|
self.assertIsNone(self.observe(tenant="other"))
|
|
|
|
|
|
|
|
|
|
def test_failure_and_stale_read_never_refresh_old_grant(self):
|
|
|
|
|
self.assertIsNotNone(self.observe())
|
|
|
|
|
baseline = self.baselines.load()
|
|
|
|
|
self.failure = True
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
self.failure = False
|
|
|
|
|
self.now += timedelta(seconds=901)
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
self.assertEqual(self.baselines.load(), baseline)
|
|
|
|
|
|
|
|
|
|
def test_revocation_retained_and_rollback_denied_across_restart(self):
|
|
|
|
|
grant = copy.deepcopy(self.document)
|
|
|
|
|
self.assertIsNotNone(self.observe())
|
|
|
|
|
self.document = json.loads((CANON / "fixtures/valid/revocation.json").read_text())
|
|
|
|
|
self.now = self.read_at = datetime(2026, 10, 3, 12, tzinfo=timezone.utc)
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
self.assertEqual(self.baselines.value.document["revision"], 2)
|
|
|
|
|
self.adapter = CustodyObservationAdapter(reader=self, baselines=self.baselines, policy=policy,
|
|
|
|
|
source_id=self.source, record_id=grant["record_id"])
|
|
|
|
|
self.document = grant
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
self.assertEqual(self.baselines.value.document["revision"], 2)
|
|
|
|
|
|
|
|
|
|
def test_same_revision_mutation_and_source_change_denied(self):
|
|
|
|
|
self.assertIsNotNone(self.observe())
|
|
|
|
|
self.document["entries"][0]["granted"]["reason"] = "rewritten"
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
self.source = "other-owner"
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
|
|
|
|
|
def test_invalid_projection_and_future_clock_denied(self):
|
|
|
|
|
self.now -= timedelta(seconds=1)
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
self.now = self.read_at
|
|
|
|
|
self.document = json.loads((CANON / "fixtures/invalid/projection-only-grant.json").read_text())
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
self.assertIsNone(self.baselines.value)
|
|
|
|
|
|
2026-10-03 19:50:15 +02:00
|
|
|
def test_check_clock_sampled_after_owner_read(self):
|
|
|
|
|
self.adapter.clock = lambda: self.now + timedelta(seconds=2)
|
|
|
|
|
self.read_at = self.now + timedelta(seconds=1)
|
|
|
|
|
result = self.adapter.observe(**self.subject, tenant="platform")
|
|
|
|
|
self.assertIsNotNone(result)
|
|
|
|
|
self.assertEqual(result["observed_at"], self.read_at.isoformat().replace("+00:00", "Z"))
|
|
|
|
|
|
|
|
|
|
|
2026-10-03 18:58:57 +02:00
|
|
|
def test_bootstrap_and_persistence_conflict_deny(self):
|
|
|
|
|
self.adapter.allow_initial_baseline = False
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
self.assertEqual(self.reads, 0)
|
|
|
|
|
self.adapter.allow_initial_baseline = True
|
|
|
|
|
self.baselines.conflict = True
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
|
|
|
|
|
def test_empty_and_supersession_revisions_are_retained(self):
|
|
|
|
|
self.document["entries"] = []
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
self.assertEqual(self.baselines.value.document["revision"], 1)
|
|
|
|
|
# Separate approved bootstrap to the grant fixture, then handover.
|
|
|
|
|
self.baselines.value = None
|
|
|
|
|
self.document = json.loads((CANON / "fixtures/valid/grant.json").read_text())
|
|
|
|
|
self.assertIsNotNone(self.observe())
|
|
|
|
|
self.document = json.loads((CANON / "fixtures/valid/supersession.json").read_text())
|
|
|
|
|
self.now = self.read_at = datetime(2026, 10, 3, 12, tzinfo=timezone.utc)
|
|
|
|
|
self.assertIsNone(self.observe())
|
|
|
|
|
successor = self.document["entries"][1]["subject"]
|
|
|
|
|
self.assertIsNotNone(self.observe(**successor))
|
|
|
|
|
self.assertEqual(self.baselines.value.document["revision"], 2)
|