Skip to content

symfonic.services.switching.constraints

constraints

CUT-RB-4 — the machine-readable cross-capability compatibility constraint set.

Constraints are data, not code, because the same set has to run in two places: at mutation time inside the control plane, and again at admission time as defense in depth. Two hand-written implementations of "is this combination legal?" would eventually disagree, and the disagreement would only surface during a cutover.

CompatibilityConstraint dataclass

CompatibilityConstraint(subject: str, subject_generation: str, requires: str, minimum: str)

subject_generation requires requires at minimum or newer.

ConstraintSet

ConstraintSet(constraints: Sequence[CompatibilityConstraint] = ())

The declared constraints plus the well-formedness rules every vector obeys.

Source code in src/symfonic/services/switching/constraints.py
def __init__(self, constraints: Sequence[CompatibilityConstraint] = ()) -> None:
    self._constraints = tuple(constraints)

validate

validate(vector: GenerationVector, *, context: str = 'vector') -> None

Raise on the first illegal combination (fail-closed, SEC-FCP-1).

Source code in src/symfonic/services/switching/constraints.py
def validate(self, vector: GenerationVector, *, context: str = "vector") -> None:
    """Raise on the first illegal combination (fail-closed, SEC-FCP-1)."""
    reasons = self.violations(vector)
    if reasons:
        joined = "; ".join(reasons)
        raise ConstraintViolationError(
            f"{context} {vector.describe()!r} violates the compatibility "
            f"constraint set: {joined}."
        )

violations

violations(vector: GenerationVector) -> tuple[str, ...]

Every reason this vector is illegal, in declaration order.

Source code in src/symfonic/services/switching/constraints.py
def violations(self, vector: GenerationVector) -> tuple[str, ...]:
    """Every reason this vector is illegal, in declaration order."""
    found: list[str] = []
    for name, generation in vector.entries:
        try:
            subject, _ = parse_generation(generation)
        except ConstraintViolationError as exc:
            found.append(str(exc))
            continue
        if subject != name:
            found.append(
                f"vector entry {name!r} binds generation {generation!r}, whose "
                f"subject is {subject!r}; an entry may not rename its own generation"
            )
    for constraint in self._constraints:
        reason = constraint.violated_by(vector)
        if reason is not None:
            found.append(reason)
    return tuple(found)

parse_generation

parse_generation(generation_id: str) -> tuple[str, int]

Split name@N (CUT-RB-3 vocabulary). Unparsable ids never guess.

Source code in src/symfonic/services/switching/constraints.py
def parse_generation(generation_id: str) -> tuple[str, int]:
    """Split ``name@N`` (CUT-RB-3 vocabulary). Unparsable ids never guess."""
    match = _GENERATION.match(generation_id)
    if match is None:
        raise ConstraintViolationError(
            f"generation id {generation_id!r} is not of the form 'name@N'; "
            "a generation identifies a schema+behavior generation, not a package version."
        )
    return match["name"], int(match["version"])