Skip to content

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

brief(value: Any, *, limit: int = 60) -> str

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
def brief(value: Any, *, limit: int = 60) -> str:
    """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.
    """
    try:
        text = repr(value)
    except Exception:  # noqa: BLE001 - a broken repr must not break admission
        return f"<unrepresentable {type(value).__name__}>"
    return text if len(text) <= limit else f"{text[:limit]}..."

equivalent

equivalent(left: Any, right: Any) -> bool

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
def equivalent(left: Any, right: Any) -> bool:
    """``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".
    """
    if left is right:
        return True
    try:
        return (left == right) is True
    except Exception:  # noqa: BLE001 - an unanswerable comparison is a refusal
        return False

field_names

field_names(obj: Any) -> tuple[str, ...] | None

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
def field_names(obj: Any) -> tuple[str, ...] | None:
    """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.
    """
    fields = getattr(type(obj), "model_fields", None)
    if isinstance(fields, Mapping):
        return tuple(fields)
    if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
        return tuple(field.name for field in dataclasses.fields(obj))
    return None

stock_instance

stock_instance(obj: Any) -> Any | None

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.

Source code in src/symfonic/agent/cutover/baseline.py
def stock_instance(obj: Any) -> Any | None:
    """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.
    """
    try:
        return type(obj)()
    except Exception:  # noqa: BLE001 - any construction failure is a refusal
        return None