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"])
|