Skip to content

symfonic.agent.cutover.settings_contract

settings_contract

What "a retired configuration field" means, and why each group is one (TA8.26).

The shape and the reasons. The twenty-five rows themselves live in :mod:~symfonic.agent.cutover.retired_settings and the rule that reads them in :mod:~symfonic.agent.cutover.config_retirement -- the same three-way split :mod:~symfonic.agent.cutover.policy and :mod:~symfonic.agent.cutover.envelope already make, and made for the same reason: the trusted surface should be readable end to end without the reflection that enforces it in the way, and each piece stays inside the repository's module budget.

Why twenty-five fields at all. TA8.24's disposition plan grouped the 149 MISSING CONTRACT rows of the configuration admission inventory into clusters and gave cluster C2 -- these twenty-five -- the recommendation RETIRE/REJECT, on the finding that each names an implementation mechanic of the legacy body (a phase-12 extractor, a nap loop, a promotion pass, a metacognition dial, a graph state class, a dev checkpointer, a hydration route, a gate that no longer exists) rather than a stable kernel capability. Today each merely falls to legacy, because :func:~symfonic.agent.cutover.envelope.admit_invocation is default-deny and no allowlist entry names it. Falling to legacy is the absence of a decision: it says nothing to the adopter who set the field.

One rule, three declared inactivity classes. A field is used when its value differs from the stock default and is not an inactive value. That is one predicate, not twenty-five: :data:INACTIVE_CLASSES names the three ways a value can ask for nothing and every row picks one. Emptiness is not use -- metacognition_sensitive_tags=[], nightly_nap_cron="" and quick_nap_interval=0 are what a caller who wants nothing passes, and refusing them would retire the parameter rather than the behaviour.

Groups, not a list. :data:RETIREMENT_GROUPS states the reason each group refuses, once per group rather than once per field. The reason is the contract; the twenty-five names are its coverage, and tests/agent/cutover/test_retired_configuration.py asserts both halves -- because a parametrised sweep over the table proves the mechanism fires and nothing at all about whether the right fields were chosen.

The stock defaults are mirrored, not imported. cutover deliberately never imports :class:~symfonic.agent.config.FrameworkConfig; the envelope reflects over whatever config object it is handed. So each row states the default it was written against, and the test file fails if any of the twenty-five drifts from the value the model actually declares -- the same discipline LEVER_RETIREMENT_LINE keeps with the normative release constant.

RetiredSetting dataclass

RetiredSetting(group: str, stock: Any, inactive: str, instead: str)

One retired configuration field: its group, its default, its next step.

instead lives here rather than being supplied at the raise site for the reason :class:~symfonic.agent.cutover.retirement.RetiredArgument gives: a refusal that names the line and stops is a dead end, and a table that carries the next step makes that obligation structural instead of conventional. Where nothing replaced the field, instead says so in those words rather than trailing off.

is_use

is_use(value: Any) -> bool

Did the caller actually ask for this field's behaviour?

Fails closed. A value whose comparison to the stock default cannot be answered -- an __eq__ that raises, a lazily imported stub, an adopter object with an opinionated __len__ -- counts as use, for the reason :func:~symfonic.agent.cutover.baseline.equivalent gives for answering False: on an admission decision "I do not know" is "no", and on a retirement it is "refuse".

Source code in src/symfonic/agent/cutover/settings_contract.py
def is_use(self, value: Any) -> bool:
    """Did the caller actually ask for this field's behaviour?

    Fails closed. A value whose comparison to the stock default cannot be
    answered -- an ``__eq__`` that raises, a lazily imported stub, an
    adopter object with an opinionated ``__len__`` -- counts as *use*, for
    the reason :func:`~symfonic.agent.cutover.baseline.equivalent` gives
    for answering ``False``: on an admission decision "I do not know" is
    "no", and on a retirement it is "refuse".
    """
    if equivalent(value, self.stock):
        return False
    try:
        return not INACTIVE_CLASSES[self.inactive](value)
    except Exception:  # noqa: BLE001 - an unanswerable value is a refusal
        return True