symfonic.agent.cutover.baseline¶
baseline ¶
Comparing a live config against a freshly built stock one.
Default-deny needs exactly one primitive: given an object and a stock instance
of the same class, name the first declared field whose value differs. This
module knows how to look; it holds no opinion about which differences matter.
That opinion — the allowlist, the baseline overrides, the wording of a refusal
— lives in :mod:~symfonic.agent.cutover.envelope, so the policy can be read
in one place without the reflection underneath it in the way.
Every function here fails closed. field_names returns None rather
than an empty tuple when an object cannot be enumerated, and equivalent
answers False when an __eq__ raises or returns something that is not
True. An enumeration that silently yielded nothing, or an equality that
silently swallowed an exception, would turn the envelope back into the
default-admit denylist this module exists to replace — with no clause left to
point at.
brief ¶
A short, always-safe repr for a refusal message.
The refusal is read by an operator deciding whether a fallback is expected,
so it names the value as well as the field. It is truncated because a
config value can be a whole tool catalogue, and it is guarded because a
half-configured object's __repr__ can raise — a diagnostic that crashes
the turn it was describing would be worse than the drop it reports.
Source code in src/symfonic/agent/cutover/baseline.py
equivalent ¶
left == right, where anything but a clear True means "differs".
Config values are arbitrary adopter objects: a provider handle, a numpy
array, a lazily-imported stub whose __eq__ raises on a partially
initialised module. Every one of those answers "I do not know", and for an
admission decision "I do not know" is "no".
Source code in src/symfonic/agent/cutover/baseline.py
field_names ¶
The declared field names of a pydantic model or a dataclass instance.
None means "this object cannot be enumerated", which the caller must
treat as a refusal. It is deliberately not an empty tuple: a caller that
iterated zero fields would find zero differences and admit everything,
which is the precise failure this module is built to prevent.
Source code in src/symfonic/agent/cutover/baseline.py
stock_instance ¶
A default-constructed instance of type(obj), or None on failure.
Built per call rather than cached. AgentConfig and the pydantic models
around it are mutable enough in practice that a cached baseline would be
one process-wide object defining what counts as default: any caller that
reached it and mutated an attribute would silently redefine admission for
every later turn. Construction costs nothing next to the model call it
guards.