"""Canonical semantic state; hashes are identifiers, never embeddings.""" from dataclasses import dataclass, asdict import hashlib import json def canonical(value): return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) def digest(value): return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest() def normalize(text): return " ".join(text.split()) @dataclass(frozen=True) class Element: ref: str role: str name: str path: str visible: bool = True enabled: bool = True sensitive: bool = False def semantic(self): return dict(role=self.role, name="[REDACTED]" if self.sensitive else normalize(self.name), path=self.path, visible=self.visible, enabled=self.enabled, sensitive=self.sensitive) @property def signature(self): return digest(self.semantic()) @dataclass(frozen=True) class State: document_id: str revision: int origin: str elements: tuple[Element, ...] @property def state_hash(self): # Order is meaningful for ordinal tasks. Ephemeral refs are excluded. return digest(dict(origin=self.origin, elements=[e.semantic() for e in self.elements])) def compact(self): return dict(document=self.document_id, revision=self.revision, hash=self.state_hash, elements=[dict(ref=e.ref, **e.semantic()) for e in self.elements]) def delta(self, previous): if self.document_id != previous.document_id: return dict(reset=self.compact()) before = {e.ref: e for e in previous.elements} after = {e.ref: e for e in self.elements} return dict(base=previous.state_hash, hash=self.state_hash, revision=self.revision, removed=sorted(before.keys() - after.keys()), changed=[dict(ref=r, **e.semantic()) for r, e in after.items() if r not in before or e != before[r]], order=list(after))