Skip to content

symfonic.platform.governance_rungs

governance_rungs

Which kernel rung each governance phase runs on.

The map, and only the map. What a refusal is lives in :mod:symfonic.platform.governance_refusal; what one run of one rung decided lives in :mod:symfonic.platform.governance_verdict; what a composed pipeline contributes lives in :mod:symfonic.platform.governance_declaration. Four questions that were one file, and the reasoning for each rung's placement is the part worth keeping together -- it was measured, and the first measurement was wrong.

GovernanceRefused

GovernanceRefused(*, stage: str, phase: str, disposition: Disposition, reason: str, rule_ids: tuple[str, ...] = ())

Bases: ContractViolationError

A governance stage refused, and the turn must stop.

Raised rather than returned as rejected(...), and the distinction is the kernel's rather than a style choice. REJECTED means this contribution was not applied -- a well-formed answer the dispatcher declined, after which the turn continues; tests/kernel pins that ("a rung that ended the turn on a rejection would make refusal and failure the same event"). What governance means is do not proceed, and the kernel's word for that is an exception, which every phase already turns into a stopped turn through require_no_crashed_stage.

The first version of this door used rejected for both. It read as a refusal in the trace and the tool call ran anyway -- ten rounds refused, ten calls executed -- because nothing consumes REJECTED and nothing was supposed to.

Source code in src/symfonic/platform/governance_refusal.py
def __init__(
    self,
    *,
    stage: str,
    phase: str,
    disposition: Disposition,
    reason: str,
    rule_ids: tuple[str, ...] = (),
) -> None:
    self.stage = stage
    self.phase = phase
    self.disposition = disposition
    self.reason = reason
    #: Which rules refused, individually. A stage is a place and a rule is
    #: a decision: three tenant rules run at ``policy_steering``, and a
    #: caller told only the stage knows which file to open rather than
    #: which rule to change.
    self.rule_ids = rule_ids
    super().__init__(
        f"governance {disposition.value} at {stage} during {phase}: {reason}"
    )