Skip to content

symfonic.agent.cutover.tools_surfaces

tools_surfaces

C1-H: tool routing and execution policy, six rows (TA8.39).

TA8.25 recommended MIGRATE for all six, on the argument that "the kernel tools route must explicitly honor them before admission rather than treating similarly named symbols as proof". This lane took that instruction literally and it is the reason four of the six do not move: there is no kernel tools route to honour them with, and the similarly named symbols are exactly what a reader would otherwise mistake for one.

What the triage's current_consumers list gets wrong, and it matters for both C1-H and C1-I. It names configuration/compat/fragments.py::capability_fragments for four of these six rows. That function is reached only from FrameworkConfigAdapter.from_mapping, whose only production caller is symfonic.diagnostics.inspection.probe -- the migration inspector. It is a translator for a diagnostic report, not a runtime consumer, and no invocation of run, stream or stream_typed reaches it on either route. A row scored as consumed because capability_fragments mentions it would be scored on the report about the migration rather than on the migration.

Two rows move, and both move as inert admissions. read_only_tools and skill_auto_approve are each read by exactly one live consumer, and each of those consumers sits behind a field the envelope refuses. That is the C1-N licence TA8.44 established, applied to two more guards -- so they are admitted, their disposition is PARTIALLY MIGRATED rather than MIGRATED, and the coupling is asserted in :data:~.admission_surfaces.INERT_COUPLING_GUARDS rather than trusted.

One row is a REPLACE. procedural_force_first_action_tool is the deprecated bool half of a lever that already has its replacement shipped in this same model. Its contract is published in :mod:~symfonic.agent.cutover.force_lever, which also carries the row RETIRED_SETTINGS folds in; the adopter-visible break is one line of configuration.

Three rows are overridden to MISSING CONTRACT, each with its own argument attached to its own row, because they fail on three different mechanisms rather than one shared one.

tools_surfaces_for_subgroup

tools_surfaces_for_subgroup(subgroup: str) -> tuple[AdmissionSurface, ...]

Every row of one TA8.25 subgroup, in declaration order.

Parity is asserted per field and never aggregated across the lane: these rows share a branch because they share files, not because they share evidence, and a helper that returned one verdict for six rows would be the aggregation this lane's acceptance forbids.

Source code in src/symfonic/agent/cutover/tools_surfaces.py
def tools_surfaces_for_subgroup(subgroup: str) -> tuple[AdmissionSurface, ...]:
    """Every row of one TA8.25 subgroup, in declaration order.

    Parity is asserted per field and never aggregated across the lane: these
    rows share a branch because they share files, not because they share
    evidence, and a helper that returned one verdict for six rows would be the
    aggregation this lane's acceptance forbids.
    """
    return tuple(
        surface for surface in TOOLS_SURFACES.values() if surface.subgroup == subgroup
    )