symfonic.services.shadow.digest¶
digest ¶
Canonical digests — the identity function for requests and payloads.
Two places need "is this the same request?" to mean the same thing: the deterministic stub (so one request always gets one answer) and the replay index (so a recorded answer is found again). Sharing one canonicaliser is what keeps a replay from silently missing a hit that the recorder produced.
The digest is also the comparator's only notion of identity, which makes one failure mode worse than being wrong: being coarse. A canonicaliser that reduced every non-JSON value to its type name would give two materially different provider requests the same digest, and the comparator would report parity for a replacement that did not behave like the legacy path. So values that carry state are canonicalised structurally — dataclasses, pydantic models, enums, temporal values, and any object exposing Python-level state — and a value whose state this module cannot see at all is refused rather than flattened. Fail closed: no digest is better than a digest that collides.
The honest limit: an object is digested by the state Python exposes. A type that keeps part of its state at C level (a socket keeps its peer address there) is digested by the visible part, which is coarser than the value but still not a bare type name. Only a value that exposes nothing is refused outright.
Nothing here executes user code. Reduction reads declared fields and instance
state; it never calls __repr__, __str__, or a model's serialiser,
because a repr can carry a memory address, a bearer token, or a customer name
straight into an evidence file.