Skip to content

Kernel (guardrail_kernel)

The import-safe substrate: provenance nodes, dimensional protocols, the Predicate value, slot/vertex references, and the local proxy _. Nothing here imports the public package.

Nodes

nodes

Import-safe provenance nodes and path/label utilities.

This is the foundational substrate of the calculus. It must not import guardrail_calculus — generators, tests, and later compiler phases import this module without executing the full public DSL.

L module-attribute

L = TypeVar('L')

R module-attribute

R = TypeVar('R')

C module-attribute

C = TypeVar('C')

TBranch module-attribute

TBranch = TypeVar('TBranch')

ProvenanceT module-attribute

ProvenanceT = TypeVar('ProvenanceT')

EBranch module-attribute

EBranch = TypeVar('EBranch')

KernelNode module-attribute

KernelNode = (
    _Add
    | _Sub
    | _Mul
    | _Div
    | _FloorDiv
    | _Mod
    | _Eq
    | _Lt
    | _Le
    | _Gt
    | _Ge
    | _And
    | _Or
    | _Not
    | _If
    | _Includes
)

BinaryKind module-attribute

BinaryKind = (
    _Add
    | _Sub
    | _Mul
    | _Div
    | _FloorDiv
    | _Mod
    | _Eq
    | _Lt
    | _Le
    | _Gt
    | _Ge
    | _And
    | _Or
)

FoldExpr module-attribute

FoldExpr = _Add | _Sub | _Mul | _FloorDiv | _Mod

SolverExpr module-attribute

SolverExpr = (
    _Add
    | _Sub
    | _And
    | _Or
    | _Not
    | _Eq
    | _Le
    | _Lt
    | _Ge
    | _Gt
    | _If
)

Literalish

Literalish = bool | int | float | str

ProvenanceCarrier

Bases: ABC, Generic[ProvenanceT]

A value whose node is the provenance tree it was built from.

Deliberately a nominal base and not a Protocol: SlotRef answers any absent attribute by extending its path, so hasattr(value, "node") — and therefore any runtime_checkable structural check — holds for every slot reference. Inheritance cannot be satisfied by accident, so a match here means the value really carries provenance. Probing for .node() instead is what turned a lowering fall-through into unbounded recursion (G9), and the same probe here fell back to None only because an isinstance guard happened to follow it.

Declared in this module rather than beside :class:Predicate because expressions imports nodes; a base defined the other way round would be a cycle, which is why the probe existed at all.

Source code in src/guardrail_kernel/nodes.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class ProvenanceCarrier(ABC, Generic[ProvenanceT]):
    """A value whose ``node`` is the provenance tree it was built from.

    Deliberately a *nominal* base and not a ``Protocol``: ``SlotRef`` answers
    any absent attribute by extending its path, so ``hasattr(value, "node")``
    — and therefore any ``runtime_checkable`` structural check — holds for
    every slot reference. Inheritance cannot be satisfied by accident, so a
    match here means the value really carries provenance. Probing for
    ``.node()`` instead is what turned a lowering fall-through into unbounded
    recursion (G9), and the same probe here fell back to ``None`` only
    because an ``isinstance`` guard happened to follow it.

    Declared in this module rather than beside :class:`Predicate` because
    ``expressions`` imports ``nodes``; a base defined the other way round
    would be a cycle, which is why the probe existed at all.
    """

    _node: ProvenanceT

    @abstractmethod
    def node(self) -> ProvenanceT:
        """The provenance tree this value was built from.

        A method rather than a field so the declared node type can be *derived*
        from a value's own type argument by a self-specialised overload -- the
        mapping that lets the declaration-only `Val`/`PointVal` facades become
        real classes. mypy refuses `@property` stacked on `@overload`, so a call
        is the only form that can carry it.

        Abstract, so the contract is stated and nothing is left unreachable:
        every carrier implements it, and an unimplemented one cannot be
        constructed rather than silently answering a base field.
        """
        ...

node abstractmethod

node()

The provenance tree this value was built from.

A method rather than a field so the declared node type can be derived from a value's own type argument by a self-specialised overload -- the mapping that lets the declaration-only Val/PointVal facades become real classes. mypy refuses @property stacked on @overload, so a call is the only form that can carry it.

Abstract, so the contract is stated and nothing is left unreachable: every carrier implements it, and an unimplemented one cannot be constructed rather than silently answering a base field.

Source code in src/guardrail_kernel/nodes.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@abstractmethod
def node(self) -> ProvenanceT:
    """The provenance tree this value was built from.

    A method rather than a field so the declared node type can be *derived*
    from a value's own type argument by a self-specialised overload -- the
    mapping that lets the declaration-only `Val`/`PointVal` facades become
    real classes. mypy refuses `@property` stacked on `@overload`, so a call
    is the only form that can carry it.

    Abstract, so the contract is stated and nothing is left unreachable:
    every carrier implements it, and an unimplemented one cannot be
    constructed rather than silently answering a base field.
    """
    ...

Labelled

Bases: Protocol

A value that can render itself for an explanation.

Structural where :class:ProvenanceCarrier is nominal, and correctly so: rendering is open to any operand that can name itself, and every such value declares a real labelSlotRef returns its dotted path — so there is nothing for a path-extending proxy to answer by accident.

Note the limit, which cost a round of renderer snapshots: a runtime_checkable protocol verifies that a member exists, never its type or kind. BinaryNode.label is a method and satisfies this, so callers still test that what they got is a str.

Source code in src/guardrail_kernel/nodes.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@runtime_checkable
class Labelled(Protocol):
    """A value that can render itself for an explanation.

    Structural where :class:`ProvenanceCarrier` is nominal, and correctly so:
    rendering is open to any operand that can name itself, and every such
    value declares a real ``label`` — ``SlotRef`` returns its dotted path —
    so there is nothing for a path-extending proxy to answer by accident.

    Note the limit, which cost a round of renderer snapshots: a
    ``runtime_checkable`` protocol verifies that a member *exists*, never its
    type or kind. ``BinaryNode.label`` is a method and satisfies this, so
    callers still test that what they got is a ``str``.
    """

    @property
    def label(self) -> str: ...

label property

label

OperandNode

A provenance node exposing its children through operands.

Nominal for the same reason as :class:ProvenanceCarrier: operands is an ordinary name, so a path-extending proxy answers a probe for it — with a SlotRef, not a tuple, which a structural walk then tries to iterate. Inheriting says a type really is a node; nothing else can claim it.

Source code in src/guardrail_kernel/nodes.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
class OperandNode:
    """A provenance node exposing its children through ``operands``.

    Nominal for the same reason as :class:`ProvenanceCarrier`: ``operands`` is
    an ordinary name, so a path-extending proxy answers a probe for it — with
    a ``SlotRef``, not a tuple, which a structural walk then tries to iterate.
    Inheriting says a type really is a node; nothing else can claim it.
    """

    @property
    def operands(self) -> tuple[object, ...]:
        raise NotImplementedError

operands property

operands

Path dataclass

A dotted reference to a slot, vertex, or subject observable.

Paths are the spine of provenance: every reference (triage.outputs.routes, drew.departure) is a Path, and the parts are kept structured rather than flattened to a string so they can be re-rooted, displayed, or lowered.

Source code in src/guardrail_kernel/nodes.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
@typing.final
@dataclass(frozen=True)
class Path:
    """A dotted reference to a slot, vertex, or subject observable.

    Paths are the spine of provenance: every reference (``triage.outputs.routes``,
    ``drew.departure``) is a ``Path``, and the parts are kept structured rather
    than flattened to a string so they can be re-rooted, displayed, or lowered.
    """

    parts: tuple[str, ...]

    def child(self, name: str) -> Path:
        """Return a new path with ``name`` appended as a further segment."""
        return Path((*self.parts, name))

    @property
    def dotted(self) -> str:
        """The path as a ``a.b.c`` string (used as a z3 variable name)."""
        return ".".join(self.parts)

    @property
    def display(self) -> str:
        """A human-facing rendering with the leading segment capitalised."""
        if not self.parts:
            return "<empty-path>"

        first, *rest = self.parts
        return ".".join((first.capitalize(), *rest))

parts instance-attribute

parts

dotted property

dotted

The path as a a.b.c string (used as a z3 variable name).

display property

display

A human-facing rendering with the leading segment capitalised.

child

child(name)

Return a new path with name appended as a further segment.

Source code in src/guardrail_kernel/nodes.py
126
127
128
def child(self, name: str) -> Path:
    """Return a new path with ``name`` appended as a further segment."""
    return Path((*self.parts, name))

BinaryNode dataclass

Bases: OperandNode, Generic[L, R]

Base of the two-operand provenance nodes (_Add, _Eq, ...).

The left/right operands preserve exactly what the author wrote, so the expression tree doubles as its own provenance. label() returns the operator symbol shared by all instances of a concrete subclass.

Source code in src/guardrail_kernel/nodes.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
@dataclass(frozen=True)
class BinaryNode(OperandNode, Generic[L, R]):
    """Base of the two-operand provenance nodes (``_Add``, ``_Eq``, ...).

    The ``left``/``right`` operands preserve exactly what the author wrote, so
    the expression tree doubles as its own provenance. ``label()`` returns the
    operator symbol shared by all instances of a concrete subclass.
    """

    left: L
    right: R

    _symbol: typing.ClassVar[str]
    _precedence: typing.ClassVar[int] = 0
    _right_equal_precedence: typing.ClassVar[_RightEqualPrecedence] = (
        _RightEqualPrecedence.NEVER
    )

    @classmethod
    def label(cls) -> str:
        """The operator symbol for this node kind (``+``, ``=``, ``<``, ...)."""
        return cls._symbol

    @property
    def operands(self) -> tuple[L, R]:
        """The child operands, for structural traversal of the expression tree."""
        return (self.left, self.right)

    def needs_parentheses(
        self,
        child: BinaryKind,
        *,
        right: bool,
    ) -> bool:
        """Return whether ``child`` needs parentheses when printed inside this node."""
        if child._precedence < self._precedence:
            return True
        if not right or child._precedence != self._precedence:
            return False

        policy = self._right_equal_precedence
        return policy is _RightEqualPrecedence.ALWAYS or (
            policy is _RightEqualPrecedence.DIFFERENT_OPERATOR
            and not isinstance(child, type(self))
        )

left instance-attribute

left

right instance-attribute

right

operands property

operands

The child operands, for structural traversal of the expression tree.

label classmethod

label()

The operator symbol for this node kind (+, =, <, ...).

Source code in src/guardrail_kernel/nodes.py
176
177
178
179
@classmethod
def label(cls) -> str:
    """The operator symbol for this node kind (``+``, ``=``, ``<``, ...)."""
    return cls._symbol

needs_parentheses

needs_parentheses(child, *, right)

Return whether child needs parentheses when printed inside this node.

Source code in src/guardrail_kernel/nodes.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
def needs_parentheses(
    self,
    child: BinaryKind,
    *,
    right: bool,
) -> bool:
    """Return whether ``child`` needs parentheses when printed inside this node."""
    if child._precedence < self._precedence:
        return True
    if not right or child._precedence != self._precedence:
        return False

    policy = self._right_equal_precedence
    return policy is _RightEqualPrecedence.ALWAYS or (
        policy is _RightEqualPrecedence.DIFFERENT_OPERATOR
        and not isinstance(child, type(self))
    )

ArithMixin

A mixin that enables building node trees via standard arithmetic operators.

Source code in src/guardrail_kernel/nodes.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
class ArithMixin:
    """A mixin that enables building node trees via standard arithmetic operators."""

    def __add__[O](
        self: typing_extensions.Self, other: O
    ) -> _Add[typing_extensions.Self, O]:
        return _Add(self, other)

    def __radd__[O](
        self: typing_extensions.Self, other: O
    ) -> _Add[O, typing_extensions.Self]:
        return _Add(other, self)

    def __sub__[O](
        self: typing_extensions.Self, other: O
    ) -> _Sub[typing_extensions.Self, O]:
        return _Sub(self, other)

    def __rsub__[O](
        self: typing_extensions.Self, other: O
    ) -> _Sub[O, typing_extensions.Self]:
        return _Sub(other, self)

    def __mul__[O](
        self: typing_extensions.Self, other: O
    ) -> _Mul[typing_extensions.Self, O]:
        return _Mul(self, other)

    def __rmul__[O](
        self: typing_extensions.Self, other: O
    ) -> _Mul[O, typing_extensions.Self]:
        return _Mul(other, self)

    def __truediv__[O](
        self: typing_extensions.Self, other: O
    ) -> _Div[typing_extensions.Self, O]:
        return _Div(self, other)

    def __rtruediv__[O](
        self: typing_extensions.Self, other: O
    ) -> _Div[O, typing_extensions.Self]:
        return _Div(other, self)

    def __floordiv__[O](
        self: typing_extensions.Self, other: O
    ) -> _FloorDiv[typing_extensions.Self, O]:
        return _FloorDiv(self, other)

    def __rfloordiv__[O](
        self: typing_extensions.Self, other: O
    ) -> _FloorDiv[O, typing_extensions.Self]:
        return _FloorDiv(other, self)

    def __mod__[O](
        self: typing_extensions.Self, other: O
    ) -> _Mod[typing_extensions.Self, O]:
        return _Mod(self, other)

    def __rmod__[O](
        self: typing_extensions.Self, other: O
    ) -> _Mod[O, typing_extensions.Self]:
        return _Mod(other, self)

UnaryNode dataclass

Bases: OperandNode, Generic[L]

Base of the single-operand provenance nodes (_Not).

Source code in src/guardrail_kernel/nodes.py
269
270
271
272
273
274
275
276
277
278
@dataclass(frozen=True)
class UnaryNode(OperandNode, Generic[L]):
    """Base of the single-operand provenance nodes (``_Not``)."""

    value: L

    @property
    def operands(self) -> tuple[L]:
        """The single child operand."""
        return (self.value,)

value instance-attribute

value

operands property

operands

The single child operand.

BinaryFold

Bases: Protocol

A node's folding operator, typed by the operands it is handed.

Two overloads because there are two callers. Constant folding applies these to literals and gets a bool; the solver lowering applies the very same attribute to solver terms and gets a solver term back. The second overload says only "the operand's own business" -- it does not need to name what the solver's boolean is, which is what keeps this module free of z3.

Source code in src/guardrail_kernel/nodes.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
class BinaryFold(Protocol):
    """A node's folding operator, typed by the operands it is handed.

    Two overloads because there are two callers. Constant folding applies these
    to literals and gets a `bool`; the solver lowering applies the very same
    attribute to solver terms and gets a solver term back. The second overload
    says only "the operand's own business" -- it does not need to name what the
    solver's boolean is, which is what keeps this module free of z3.
    """

    @overload
    def __call__(self, left: Literalish, right: Literalish, /) -> bool: ...
    @overload
    def __call__(self, left: typing.Any, right: typing.Any, /) -> typing.Any: ...

ArithBinaryNode dataclass

Bases: Generic[L, R], BinaryNode[L, R], ArithMixin

A binary node whose operands combine into a z3 arithmetic term.

Source code in src/guardrail_kernel/nodes.py
302
303
304
305
306
@dataclass(frozen=True)
class ArithBinaryNode(Generic[L, R], BinaryNode[L, R], ArithMixin):
    """A binary node whose operands combine into a z3 arithmetic term."""

    _operator: typing.ClassVar[typing.Callable]

BoolBinaryNode dataclass

Bases: Generic[L, R], BinaryNode[L, R]

A binary node whose operands combine into a z3 boolean term.

Source code in src/guardrail_kernel/nodes.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
@dataclass(frozen=True)
class BoolBinaryNode(Generic[L, R], BinaryNode[L, R]):
    """A binary node whose operands combine into a z3 boolean term."""

    #: Typed by what it *does*, which is two different things depending on what
    #: it is given: `operator.eq(3, 4)` is a `bool`, while `operator.eq(a, b)`
    #: over two solver terms builds a solver term. The same attribute, applied by
    #: constant folding in one place and by the solver lowering in another.
    #:
    #: An earlier pass declared plain `-> bool`, which is false for the second
    #: caller. The correction to that assumed the honest type needed to *name*
    #: the solver's boolean, which kernel purity forbids here. It does not:
    #: `BinaryFold` says "literals in, `bool` out; anything else is the operand's
    #: own business", and z3 is never mentioned.
    _operator: typing.ClassVar[BinaryFold]

FoldBinaryNode dataclass

Bases: Generic[L, R], BinaryNode[L, R], ArithMixin

A binary node admitted only at construction time (concrete operands).

It carries a Python _operator for constant folding, but has no solver lowering: it is never solver-facing, so the solver's lowering deliberately rejects it. This is how the construction-only operators (//, %) stay out of the linear, solver-facing fragment while remaining expressible in constructor formulas.

Source code in src/guardrail_kernel/nodes.py
326
327
328
329
330
331
332
333
334
335
336
337
@dataclass(frozen=True)
class FoldBinaryNode(Generic[L, R], BinaryNode[L, R], ArithMixin):
    """A binary node admitted only at construction time (concrete operands).

    It carries a Python ``_operator`` for constant folding, but has no solver
    lowering: it is never solver-facing, so the solver's lowering deliberately
    rejects it. This is how
    the construction-only operators (``//``, ``%``) stay out of the linear,
    solver-facing fragment while remaining expressible in constructor formulas.
    """

    _operator: typing.ClassVar[Callable]

display_value

display_value(value)

Best-effort human label for any operand.

Uses the value's .label when it declares one (the DSL values do), otherwise falls back to repr. Structural (:class:Labelled) rather than nominal on purpose: rendering is open to any operand that can name itself, and every such value — SlotRef included — declares a real label, so there is nothing here for a path-extending proxy to answer by accident.

Source code in src/guardrail_kernel/nodes.py
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
def display_value(value: object) -> str:
    """Best-effort human label for any operand.

    Uses the value's ``.label`` when it declares one (the DSL values do),
    otherwise falls back to ``repr``. Structural (:class:`Labelled`) rather
    than nominal on purpose: rendering is open to any operand that can name
    itself, and every such value — ``SlotRef`` included — declares a real
    ``label``, so there is nothing here for a path-extending proxy to answer
    by accident.
    """
    if isinstance(value, Labelled):
        label = value.label
        # `runtime_checkable` verifies that a member *exists*, never its type
        # or kind: `BinaryNode.label` is a method, so it satisfies `Labelled`
        # and yields a bound method here. Structural checking buys the intent
        # and the typed read; it does not buy the shape, so the str test stays.
        if isinstance(label, str):
            return label
    return repr(value)

label_of

label_of(value)

Like :func:display_value; the canonical operand-labelling entry point.

Source code in src/guardrail_kernel/nodes.py
518
519
520
def label_of(value: object) -> str:
    """Like :func:`display_value`; the canonical operand-labelling entry point."""
    return display_value(value)

binary_label

binary_label(node)

Render a binary node with parentheses where precedence requires them.

Source code in src/guardrail_kernel/nodes.py
545
546
547
548
549
def binary_label(node: BinaryKind) -> str:
    """Render a binary node with parentheses where precedence requires them."""
    left = _operand_label(node.left, node, right=False)
    right = _operand_label(node.right, node, right=True)
    return f"{left} {node.label()} {right}"

Dimensional protocols

dimensions

Dimensional protocols and coercers.

The dimension layer exposes stable predicates and coercers used both by the runtime operator implementation and by compiler validation. It is import-safe: it depends only on typing, never on guardrail_calculus or the solver/z3.

Registry-backed values carry a typed _dimension record reference. Its literal name parameter preserves static per-dimension identity (:class:DimensionLike's NameT) without a parallel runtime tag; the concrete per-dimension aliases over it live with the built-in dimension consumers in guardrail_calculus.dimensions, keeping this module free of dimension names. Boolean predicates are not carrier dimensions and continue to use _z3_dim.

DimensionTagged

Bases: ABC

A value (or carrier class) declaring the canonical _dimension tag.

The carrier set is extensible — the registry is how a new dimension joins — but it is not open: joining means declaring this tag, so the shape is closed even as the set grows.

Nominal, and not a Protocol, for the reason :class:~guardrail_kernel.nodes.ProvenanceCarrier is: inheritance cannot be satisfied by accident. Two things went wrong while this was structural, and neither could be seen from the outside.

A Protocol whose member is a plain _dimension: Dimension[Any] declares a settable variable, and mypy refuses a frozen dataclass for one — "expected settable variable, got read-only attribute". Every carrier here is a frozen dataclass, so the protocol was unsatisfiable by precisely the values it described. Nothing failed, because its only two uses could not notice: isinstance is a runtime check and passes regardless, and :func:dimension_of took value: object, which accepts anything.

And the tag could not be read — while Dimension was also the schema-field descriptor, a typed attribute read was projected through its __get__ to the raw carrier type, and the two checkers projected in exact opposition, with no declaration form right in both. Every carrier routed through dimension_of and cast the result, five times over. A5.1 moved that __get__ onto :class:~guardrail_kernel.dimension_registry.CarrierField, which is used only where a schema field wants the projection, so the read below is plain and no capability was given up for it.

:meth:dimension remains a method rather than a bare attribute, because the shape of the tag still varies: a value declares it as a dataclass field and a class-based carrier as a ClassVar, and one method reads both. Same shape as ProvenanceCarrier.node(), one module away.

Source code in src/guardrail_kernel/dimensions.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
class DimensionTagged(ABC):
    """A value (or carrier class) declaring the canonical ``_dimension`` tag.

    The carrier set is *extensible* — the registry is how a new dimension
    joins — but it is not *open*: joining means declaring this tag, so the
    shape is closed even as the set grows.

    Nominal, and not a ``Protocol``, for the reason
    :class:`~guardrail_kernel.nodes.ProvenanceCarrier` is: inheritance cannot
    be satisfied by accident. Two things went wrong while this was structural,
    and neither could be seen from the outside.

    A ``Protocol`` whose member is a plain ``_dimension: Dimension[Any]``
    declares a *settable* variable, and mypy refuses a frozen dataclass for
    one — "expected settable variable, got read-only attribute". Every carrier
    here is a frozen dataclass, so the protocol was unsatisfiable by precisely
    the values it described. Nothing failed, because its only two uses could
    not notice: ``isinstance`` is a runtime check and passes regardless, and
    :func:`dimension_of` took ``value: object``, which accepts anything.

    And the tag could not be *read* — while ``Dimension`` was also the
    schema-field descriptor, a typed attribute read was projected through its
    ``__get__`` to the raw carrier type, and the two checkers projected in
    exact opposition, with no declaration form right in both. Every carrier
    routed through ``dimension_of`` and cast the result, five times over.
    A5.1 moved that ``__get__`` onto
    :class:`~guardrail_kernel.dimension_registry.CarrierField`, which is used
    only where a schema field wants the projection, so the read below is plain
    and no capability was given up for it.

    :meth:`dimension` remains a method rather than a bare attribute, because
    the *shape* of the tag still varies: a value declares it as a dataclass
    field and a class-based carrier as a ``ClassVar``, and one method reads
    both. Same shape as ``ProvenanceCarrier.node()``, one module away.
    """

    if typing.TYPE_CHECKING:
        # Declaration-only, twice over. A *read-only property* is the only
        # base form that admits both carrier shapes — an instance-variable
        # annotation refuses the ``ClassVar`` carriers ("cannot override
        # instance variable with class variable") and a ``ClassVar`` one
        # refuses the field carriers, in mirror image. And it must not exist
        # at runtime, because a real property is a *data* descriptor: it would
        # win over the instance dict and break every carrier that assigns the
        # tag in ``__init__``. At runtime each carrier's own field or
        # ``ClassVar`` answers, which is what :meth:`dimension` reads.
        @property
        def _dimension(self) -> Dimension[Any]: ...

    def dimension(self) -> Dimension[Carrier]:
        """The registry record this value is tagged with.

        A plain read, with no cast, since A5.1 removed ``Dimension.__get__``:
        a ``Dimension``-typed attribute now reads back as the record in both
        checkers. It stays a method rather than becoming a bare attribute
        because the width above is still real — a class-based carrier declares
        the tag as a ``ClassVar`` and a value declares it as a field, and only
        a method reads the same for both.
        """
        return self._dimension

dimension

dimension()

The registry record this value is tagged with.

A plain read, with no cast, since A5.1 removed Dimension.__get__: a Dimension-typed attribute now reads back as the record in both checkers. It stays a method rather than becoming a bare attribute because the width above is still real — a class-based carrier declares the tag as a ClassVar and a value declares it as a field, and only a method reads the same for both.

Source code in src/guardrail_kernel/dimensions.py
81
82
83
84
85
86
87
88
89
90
91
def dimension(self) -> Dimension[Carrier]:
    """The registry record this value is tagged with.

    A plain read, with no cast, since A5.1 removed ``Dimension.__get__``:
    a ``Dimension``-typed attribute now reads back as the record in both
    checkers. It stays a method rather than becoming a bare attribute
    because the width above is still real — a class-based carrier declares
    the tag as a ``ClassVar`` and a value declares it as a field, and only
    a method reads the same for both.
    """
    return self._dimension

UnitTagged

Bases: Protocol

A carrier exposing its unit tag under the neutral unit name.

Source code in src/guardrail_kernel/dimensions.py
94
95
96
97
98
99
@runtime_checkable
class UnitTagged(Protocol):
    """A carrier exposing its unit tag under the neutral ``unit`` name."""

    @property
    def unit(self) -> str | None: ...

unit property

unit

DimensionIdentity

Bases: DimensionTagged

Typed identity facade derived from the canonical dimension descriptor.

Declares :class:DimensionTagged as a base rather than assuming it. Every user in the tree already paired the two -- four classes in guardrail_calculus.dimensions and the extensibility fixture all list both -- and until this said so, dimension_name was asserting two things at once: that the value carries a tag at all, and that the tag's name is the NameT this class was parameterised with. The first is a fact about a base class and is now declared; only the second is left, and it is the one nothing can derive.

Source code in src/guardrail_kernel/dimensions.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
class DimensionIdentity[NameT: str](DimensionTagged):
    """Typed identity facade derived from the canonical dimension descriptor.

    Declares :class:`DimensionTagged` as a base rather than assuming it. Every
    user in the tree already paired the two -- four classes in
    ``guardrail_calculus.dimensions`` and the extensibility fixture all list
    both -- and until this said so, ``dimension_name`` was asserting *two*
    things at once: that the value carries a tag at all, and that the tag's name
    is the ``NameT`` this class was parameterised with. The first is a fact
    about a base class and is now declared; only the second is left, and it is
    the one nothing can derive.
    """

    @property
    def dimension_name(self) -> NameT:
        # `Dimension.name` is a `str` read off registry data, and no arrangement
        # of the registry can make it the declared literal: `DimensionIdentity[
        # Literal["Money"]]` is a phantom identity the type system carries and
        # the runtime never sees. Same shape as the `phantom-structure` casts in
        # `_realise_rule` -- a declared surface meeting one shared registry-driven
        # engine -- which is why the baseline entry justifies it rather than
        # listing it as debt.
        return cast(NameT, self.dimension().name)

dimension_name property

dimension_name

DimensionLike

Bases: Protocol

A dimensioned arithmetic value with statically visible identity.

Source code in src/guardrail_kernel/dimensions.py
207
208
209
210
211
212
213
214
215
@runtime_checkable
class DimensionLike[NameT: str](Protocol):
    """A dimensioned arithmetic value with statically visible identity."""

    @property
    def label(self) -> str: ...

    @property
    def dimension_name(self) -> NameT: ...

label property

label

dimension_name property

dimension_name

BoolLike

Bases: Protocol

A value living in the bool dimension (a predicate / proposition).

Source code in src/guardrail_kernel/dimensions.py
218
219
220
221
222
223
224
225
226
@runtime_checkable
class BoolLike(Protocol):
    """A value living in the *bool* dimension (a predicate / proposition)."""

    @property
    def _z3_dim(self) -> Literal["bool"]: ...

    @property
    def label(self) -> str: ...

label property

label

dimension_of

dimension_of(value)

The carrier dimension descriptor a value carries, or None.

The single reader of the _dimension tag: an instance attribute, or the class ClassVar when the instance exposes it only as an unbound property. The dimension facade (:meth:DimensionIdentity.dimension_name) and the calculus dispatcher (_resolve_dim, z3_if) read identity through here.

The wide probe, for a value that may not be tagged at all. A caller that already holds a carrier calls :meth:DimensionTagged.dimension instead and gets no None to discard.

Source code in src/guardrail_kernel/dimensions.py
102
103
104
105
106
107
108
109
110
111
112
113
114
def dimension_of(value: object) -> Dimension[Carrier] | None:
    """The carrier dimension descriptor a value carries, or ``None``.

    The single reader of the ``_dimension`` tag: an instance attribute, or the class
    ``ClassVar`` when the instance exposes it only as an unbound ``property``. The dimension
    facade (:meth:`DimensionIdentity.dimension_name`) and the calculus dispatcher
    (``_resolve_dim``, ``z3_if``) read identity through here.

    The wide probe, for a value that may not be tagged at all. A caller that
    already holds a carrier calls :meth:`DimensionTagged.dimension` instead and
    gets no ``None`` to discard.
    """
    return value.dimension() if isinstance(value, DimensionTagged) else None

dimension_name_of

dimension_name_of(value)

The name of the carrier dimension value carries, or None.

Source code in src/guardrail_kernel/dimensions.py
117
118
119
120
def dimension_name_of(value: object) -> str | None:
    """The name of the carrier dimension ``value`` carries, or ``None``."""
    dimension = dimension_of(value)
    return None if dimension is None else dimension.name

resolve_field_dimension

resolve_field_dimension(schema_type, name)

The carrier dimension of schema_type's name field, or None.

A value-authored carrier has no owner class, so no _dimension ClassVar to read — it is instead annotated with its Dimension record directly (Annotated[Carrier, SOME_DIMENSION], e.g. guardrail_calculus.dimension_values's ClockRef); that metadata is checked first. Otherwise, unwraps the field's type hint down to its _dimension ClassVar — a generic origin (e.g. ClockTime[int] -> ClockTime), or an Annotated wrapper with no Dimension in its metadata — the shape every still-class-based carrier uses. The single reader used by the subject-ref proxies (CurrentSubjectRef, SubjectRef) to route a schema field access to its dimension-specific placeholder without hardcoding a dimension name at the call site.

Source code in src/guardrail_kernel/dimensions.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def resolve_field_dimension(
    schema_type: type[Any], name: str
) -> "Dimension[Any] | None":
    """The carrier dimension of ``schema_type``'s ``name`` field, or ``None``.

    A value-authored carrier has no owner class, so no ``_dimension``
    ClassVar to read — it is instead annotated with its ``Dimension`` record
    directly (``Annotated[Carrier, SOME_DIMENSION]``, e.g.
    ``guardrail_calculus.dimension_values``'s ``ClockRef``); that metadata is
    checked first. Otherwise, unwraps the field's type hint down to its
    ``_dimension`` ClassVar — a generic origin (e.g. ``ClockTime[int]`` ->
    ``ClockTime``), or an ``Annotated`` wrapper with no ``Dimension`` in its
    metadata — the shape every still-class-based carrier uses. The single
    reader used by the
    subject-ref proxies (``CurrentSubjectRef``, ``SubjectRef``) to route a
    schema field access to its dimension-specific placeholder without
    hardcoding a dimension name at the call site.
    """
    schema_origin = typing_extensions.get_origin(schema_type) or schema_type
    type_hints = typing.get_type_hints(schema_origin, include_extras=True)
    hint = type_hints.get(name)
    if hint is None:
        return None
    metadata = getattr(hint, "__metadata__", None)
    if metadata is not None:
        for item in metadata:
            if isinstance(item, Dimension):
                return item
        hint = typing_extensions.get_args(hint)[0]
    origin = typing_extensions.get_origin(hint) or hint
    # The *class* is tested here, not an instance: a carrier declares its tag
    # as a ``ClassVar``, so the class object itself carries it. Now that the
    # base is nominal that is `issubclass`, not `isinstance` -- the class
    # object is not an instance of its own base. A carrier that declares the
    # tag as an instance field has none to read off the type, and correctly
    # yields ``None`` rather than a descriptor.
    if isinstance(origin, type) and issubclass(origin, DimensionTagged):
        tag = origin.__dict__.get("_dimension")
        return tag if isinstance(tag, Dimension) else None
    return None

unit_of

unit_of(value)

The unit tag a value carries, or None.

Every carrier spells the tag unit, so this reads one declared name alongside :func:dimension_of. It used to reconcile a second spelling — currency, for a tagged-vector literal such as Money — and the reconciliation outlived the split: money is a DimensionValue whose unit is "GBP", and nothing in the tree declares currency at all. Replacing the probe with the declared shape is what surfaced that; a getattr for a name no type defines simply returns None forever.

Source code in src/guardrail_kernel/dimensions.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def unit_of(value: object) -> str | None:
    """The unit tag a value carries, or ``None``.

    Every carrier spells the tag ``unit``, so this reads one declared name
    alongside :func:`dimension_of`. It used to reconcile a second spelling —
    ``currency``, for a tagged-vector literal such as ``Money`` — and the
    reconciliation outlived the split: money is a ``DimensionValue`` whose
    ``unit`` is ``"GBP"``, and nothing in the tree declares ``currency`` at
    all. Replacing the probe with the declared shape is what surfaced that;
    a ``getattr`` for a name no type defines simply returns ``None`` forever.
    """
    # `unit` is an ordinary name, so a probe for it is answered by a
    # path-extending proxy — `unit_of(_.some.slot)` would hand back a slot
    # reference dressed as a unit tag. The declared shape cannot be claimed.
    return value.unit if isinstance(value, UnitTagged) else None

is_bool_like

is_bool_like(value)

True if value carries the bool dimension tag, and narrows to it.

TypeIs rather than TypeIs, which is the subtle half. The two differ on the negative branch, and the and clause below looks at first like it rules TypeIs out: surely a BoolLike whose tag is not "bool" answers False while still being a BoolLike?

No such value exists in the type system. BoolLike declares _z3_dim as Literal["bool"], so anything satisfying the protocol statically carries that tag; the comparison is here only because runtime_checkable checks attribute presence and not attribute value. The negative branch therefore holds, and TypeIs intersects with the declared type where TypeIs would replace it -- which is what lets as_z3_bool keep its lowerable union instead of collapsing to this protocol.

Answering plain bool narrowed nothing, so every caller re-established by hand what this had already determined.

Source code in src/guardrail_kernel/dimensions.py
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
def is_bool_like(value: object) -> TypeIs[BoolLike]:
    """True if ``value`` carries the bool dimension tag, and narrows to it.

    ``TypeIs`` rather than ``TypeIs``, which is the subtle half. The two
    differ on the negative branch, and the `and` clause below looks at first
    like it rules `TypeIs` out: surely a `BoolLike` whose tag is not `"bool"`
    answers `False` while still being a `BoolLike`?

    No such value exists in the type system. `BoolLike` declares `_z3_dim` as
    `Literal["bool"]`, so anything satisfying the protocol *statically* carries
    that tag; the comparison is here only because `runtime_checkable` checks
    attribute presence and not attribute value. The negative branch therefore
    holds, and `TypeIs` intersects with the declared type where `TypeIs`
    would replace it -- which is what lets `as_z3_bool` keep its lowerable
    union instead of collapsing to this protocol.

    Answering plain `bool` narrowed nothing, so every caller re-established by
    hand what this had already determined.
    """
    return isinstance(value, BoolLike) and value._z3_dim == "bool"

Expressions

expressions

Predicate, slot references, and the unified comparison surface.

This module sits on top of :mod:guardrail_kernel.nodes and :mod:guardrail_kernel.dimensions. It is import-safe — the only reference to guardrail_calculus is a deferred import inside :meth:Predicate.annotate, which never runs at import time.

NodeT module-attribute

NodeT = TypeVar('NodeT')

UBool module-attribute

UBool = TypeVar('UBool', bound=BoolLike)

BoolExpr module-attribute

BoolExpr = Predicate

Boolish_Bound module-attribute

Boolish_Bound = BoolLike | SlotRef[Any]

RouteTarget

RouteTarget = SlotRef[Any] | VertexRef[Any]

Predicate dataclass

Bases: ProvenanceCarrier, Generic[NodeT]

A boolean expression that remembers how it was built.

Predicate is the central value of the DSL: comparisons and @ assignments produce one. node is the provenance tree (e.g. _Ge[SlotRef, Probability]) threaded into NodeT so the static type records the exact shape; label is the human rendering; z3 is the solver term. & / | / ~ combine predicates into larger ones.

Source code in src/guardrail_kernel/expressions.py
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
@typing.final
@dataclass(frozen=True, eq=False)
class Predicate(ProvenanceCarrier, Generic[NodeT]):
    """A boolean expression that remembers how it was built.

    ``Predicate`` is the central value of the DSL: comparisons and ``@``
    assignments produce one. ``node`` is the provenance tree (e.g.
    ``_Ge[SlotRef, Probability]``) threaded into ``NodeT`` so the static type
    records the exact shape; ``label`` is the human rendering; ``z3`` is the
    solver term. ``&`` / ``|`` / ``~`` combine predicates into larger ones.
    """

    _node: NodeT

    def node(self) -> NodeT:
        """The provenance tree this value was built from.

        A method rather than a field so the declared node type can be *derived*
        from a value's own type argument by a self-specialised overload -- the
        mapping that lets the declaration-only `Val`/`PointVal` facades become
        real classes. mypy refuses `@property` stacked on `@overload`, so a call
        is the only form that can carry it.
        """
        return self._node

    label: str
    _z3_dim: Literal["bool"] = field(
        default="bool",
        init=False,
        repr=False,
        compare=False,
    )

    def annotate(self, text: str) -> Prop[typing_extensions.Self]:
        """Wrap this predicate as a :class:`Prop` carrying human-facing text."""
        from guardrail_calculus import Prop

        return Prop(expr=self, text=text)

    def __and__(self, other: UBool) -> Predicate[_And[Predicate[NodeT], UBool]]:
        if not is_bool_like(other):
            return NotImplemented

        return Predicate(
            _node=_And(self, other),
            label=f"({self.label}) and ({other.label})",
        )

    def __or__(self, other: UBool) -> Predicate[_Or[Predicate[NodeT], UBool]]:
        if not is_bool_like(other):
            return NotImplemented

        return Predicate(
            _node=_Or(self, other),
            label=f"({self.label}) or ({other.label})",
        )

    def __invert__(self) -> Predicate[_Not[Predicate[NodeT]]]:
        return Predicate(
            _node=_Not(self),
            label=f"not ({self.label})",
        )

label instance-attribute

label

node

node()

The provenance tree this value was built from.

A method rather than a field so the declared node type can be derived from a value's own type argument by a self-specialised overload -- the mapping that lets the declaration-only Val/PointVal facades become real classes. mypy refuses @property stacked on @overload, so a call is the only form that can carry it.

Source code in src/guardrail_kernel/expressions.py
72
73
74
75
76
77
78
79
80
81
def node(self) -> NodeT:
    """The provenance tree this value was built from.

    A method rather than a field so the declared node type can be *derived*
    from a value's own type argument by a self-specialised overload -- the
    mapping that lets the declaration-only `Val`/`PointVal` facades become
    real classes. mypy refuses `@property` stacked on `@overload`, so a call
    is the only form that can carry it.
    """
    return self._node

annotate

annotate(text)

Wrap this predicate as a :class:Prop carrying human-facing text.

Source code in src/guardrail_kernel/expressions.py
91
92
93
94
95
def annotate(self, text: str) -> Prop[typing_extensions.Self]:
    """Wrap this predicate as a :class:`Prop` carrying human-facing text."""
    from guardrail_calculus import Prop

    return Prop(expr=self, text=text)

SlotComparableMixin

Bases: _ComparableRuntime

Source code in src/guardrail_kernel/expressions.py
325
class SlotComparableMixin[ValueT = Any](_ComparableRuntime): ...

SlotRef dataclass

Bases: SlotComparableMixin[ValueT]

A reference to an output slot, e.g. _.decision or _.confidence.

Attribute access extends the path (_.coverage.limit), comparisons build constraint predicates, and _.slot @ value builds an assignment predicate. Slot comparisons currently carry a placeholder z3 term; real slot semantics arrive with the Phase 2 slot→z3 lowering.

Source code in src/guardrail_kernel/expressions.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
@typing.final
@dataclass(frozen=True)
class SlotRef[ValueT = Any](SlotComparableMixin[ValueT]):
    """A reference to an output slot, e.g. ``_.decision`` or ``_.confidence``.

    Attribute access extends the path (``_.coverage.limit``), comparisons build
    constraint predicates, and ``_.slot @ value`` builds an assignment
    predicate. Slot comparisons currently carry a placeholder z3 term; real
    slot semantics arrive with the Phase 2 slot→z3 lowering.
    """

    path: Path

    def __getattr__(self, name: str) -> SlotRef[Any]:
        if name.startswith("_"):
            raise AttributeError(name)
        return SlotRef(self.path.child(name))

    @property
    def label(self) -> str:
        return self.path.dotted

    def includes(self, target: RouteTarget) -> Predicate[_Includes]:
        """Build a route-membership predicate: ``this slot includes target``."""
        return Predicate(
            _node=_Includes(self, target),
            label=f"{self.label} includes {display_value(target)}",
        )

    def __matmul__(self, other: ValueT) -> Predicate[_Eq[SlotRef[ValueT], ValueT]]:
        """``slot @ value`` — an assignment predicate over this output slot.

        The operand is the slot's own ``ValueT``, not ``object``: a slot
        declares what it holds, so ``ref[W].triage.outputs.result @ 42`` on a
        ``Slot[str]`` is refused where it used to pass. Untyped slots default
        ``ValueT`` to ``Any`` and are no worse off than they were.
        """
        return Predicate(
            _node=_Eq(self, other),
            label=f"{self.label} = {display_value(other)}",
        )

path instance-attribute

path

label property

label

includes

includes(target)

Build a route-membership predicate: this slot includes target.

Source code in src/guardrail_kernel/expressions.py
355
356
357
358
359
360
def includes(self, target: RouteTarget) -> Predicate[_Includes]:
    """Build a route-membership predicate: ``this slot includes target``."""
    return Predicate(
        _node=_Includes(self, target),
        label=f"{self.label} includes {display_value(target)}",
    )

VertexRef dataclass

A reference to a graph vertex, e.g. r.fraud_review.

Attribute access reaches into the vertex's outputs (.outputs.result); route sets and includes checks are built from these references.

Source code in src/guardrail_kernel/expressions.py
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
@typing.final
@dataclass(frozen=True)
class VertexRef[OutputsT = Any]:
    """A reference to a graph vertex, e.g. ``r.fraud_review``.

    Attribute access reaches into the vertex's outputs (``.outputs.result``);
    route sets and ``includes`` checks are built from these references.
    """

    path: Path

    def __getattr__(self, name: str) -> SlotRef[Any]:
        if name.startswith("_"):
            raise AttributeError(name)
        return SlotRef(self.path.child(name))

    @property
    def outputs(self) -> OutputsT:
        return cast(OutputsT, SlotRef(self.path.child("outputs")))

    @property
    def label(self) -> str:
        return self.path.dotted

path instance-attribute

path

outputs property

outputs

label property

label

make_predicate

make_predicate(node, label)
Source code in src/guardrail_kernel/expressions.py
132
133
134
135
136
def make_predicate[NodeT](
    node: NodeT,
    label: str,
) -> Predicate[NodeT]:
    return Predicate(_node=node, label=label)

eq_pred

eq_pred(left, right)
Source code in src/guardrail_kernel/expressions.py
139
140
141
142
143
144
def eq_pred[L, R](left: L, right: R) -> Predicate[_Eq[L, R]]:
    node = _Eq(left, right)
    return make_predicate(
        node=node,
        label=binary_label(node),
    )

comparison_expr

comparison_expr(left, right, node)
Source code in src/guardrail_kernel/expressions.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def comparison_expr[NodeT: BoolBinaryNode[Any, Any]](
    left: object,
    right: object,
    node: NodeT,
) -> Predicate[NodeT]:
    operator = node.label()
    return Predicate(
        _node=node,
        label=(
            f"{left.label} {operator} {display_value(right)}"
            if isinstance(left, SlotRef)
            else f"{label_of(left)} {operator} {label_of(right)}"
        ),
    )

The local proxy

dsl

CurrentSubjectRef dataclass

The local proxy representing "the current agent/subject".

Any attribute access returns a SlotRef. At runtime, the proxy dynamically resolves slot dimensions (such as Instant/time fields) by inspecting the associated schema type.

Source code in src/guardrail_kernel/dsl.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@dataclass(frozen=True)
class CurrentSubjectRef[T]:
    """The local proxy representing "the current agent/subject".

    Any attribute access returns a SlotRef. At runtime, the proxy dynamically
    resolves slot dimensions (such as Instant/time fields) by inspecting the
    associated schema type.
    """

    schema_type: type[T] | None = None

    def __getattr__(self, name: str) -> BoundPointReference | SlotRef[Any]:
        """Any other attribute is an output-slot reference on this subject."""
        if self.schema_type is not None:
            dim = resolve_field_dimension(self.schema_type, name)
            if dim is not None:
                # Deferred: the kernel stays free of a guardrail_calculus
                # dependency at import time. symbolic_point_refs_for /
                # BoundPointReference are the calculus-layer, registry-data-
                # driven replacement for the old per-dimension facade lookup
                # (roadmap V12) — a point reference exists because the
                # dimension's structure is affine, not because it was
                # registered by hand.
                from guardrail_calculus.dimension_values import (
                    BoundPointReference,
                    symbolic_point_refs_for,
                )

                if symbolic_point_refs_for(dim):
                    return BoundPointReference(name, _dimension=dim)

        return SlotRef(Path((name,)))

schema_type class-attribute instance-attribute

schema_type = None

Construction arithmetic

The registry's constant-fold arithmetic and its value domain: the wire and the Rust fold carry i64, so the admitted arithmetic is i64-valued at every node — Int64 annotates the pydantic boundary fields (runtime-validated), CheckedInt64/CheckedFloat brand kernel values statically proven wire-representable, and fold is the gate that mints the int side.

dimension_registry

Compile-time dimension descriptors and the context-specific reference leaves.

This is the data substrate for the dimension registry (see docs/dev/sprints/roadmap/+1/dimension-registry.md and its companion proposal). Dimensions are declared data — a carrier sort, an invariant, units, spec-authored constructors, and a small algebra — not hand-rolled runtime classes. This module defines those descriptor records and the four context-specific reference leaves that appear inside their expression trees:

ParamRef      a constructor parameter (a concrete literal at construction time)
CarrierValue  the carrier magnitude of "this" value (invariant context)
UnitTag       the unit tag of "this" value
OperandRef    an algebra operand, projected to ``.value`` / ``.unit``

The leaves carry only identity and a label; they deliberately have no .z3, because each context substitutes them before lowering — constructor params fold to a literal at construction, while operand/carrier refs resolve during solver lowering.

This module is the additive step-1 scaffolding. The analyzability admission gate (per-context linearity over the expression trees), the fold-vs-z3 arithmetic nodes, and the rewiring of the existing carriers arrive in later migration steps.

Kernel purity: this module must not import guardrail_calculus.

Int64 module-attribute

Int64 = Annotated[int, Interval(ge=_I64_MIN, le=_I64_MAX)]

The construction arithmetic's value domain, as a boundary annotation.

The wire and the Rust fold both carry i64 (FoldInt.value: i64, WireInt.value: i64), so Python's unbounded ints past these bounds are a fiction the boundary cannot represent. annotated_types is pydantic's own constraint vocabulary, importable without pydantic: the pydantic wire models annotate their integer fields with this alias and get runtime range validation (and JSON-schema minimum/maximum) from it, while accepting untrusted plain ints — validation boundaries take unproven input. Values that must already be proven in-range carry CheckedInt64 instead.

CheckedInt64 module-attribute

CheckedInt64 = NewType('CheckedInt64', int)

An int statically branded as proven inside the wire's i64 domain.

The nominal half of the enforcement Int64 can't give (value ranges are not expressible in Python's type system): a plain int is not assignable where CheckedInt64 is required, so unvetted values cannot reach i64-typed seams without passing a gate — fold range-checks every node and returns branded values, and a decoded wire model's pydantic-validated fields may be blessed at the decode seam. A CheckedInt64 is-an int everywhere downstream.

One caveat: in a union with a plain float, mypy's numeric tower would let an unbranded int satisfy the float member by promotion — which is why the seams that carry both arms pair this brand with CheckedFloat rather than float (promotion targets the type float itself, never a NewType of it). The runtime gates stay authoritative either way.

CheckedFloat module-attribute

CheckedFloat = NewType('CheckedFloat', float)

A float statically branded as proven wire-representable (finite).

The REAL-carrier sibling of CheckedInt64, with finiteness as the proven refinement: the wire is JSON, which cannot carry NaN/Inf (the pydantic boundary models reject them via FiniteFloat fields, and the Rust decoder's decimal parse never sees them). Minted only where finiteness is already established — a bounds check such as probability's [0, 1] (any comparison with NaN is false, so bounded implies finite), or a decoded wire model's validated field. Doubling as the float union member in carrier seams, it also closes mypy's int-to-float promotion hole: a plain int promotes to float, never to a NewType of it.

FoldError

Bases: Exception

A constructor carrier_expr could not be folded to a literal.

Source code in src/guardrail_kernel/dimension_registry.py
1763
1764
class FoldError(Exception):
    """A constructor ``carrier_expr`` could not be folded to a literal."""

fold

fold(expr, params)

Evaluate a constructor carrier_expr on concrete literal params.

Walks the node tree, substituting each ParamRef with its value and applying each node's own _operator. Rejects anything outside the admitted construction arithmetic — this is the construction-context arm of the analyzability gate, and the reason // / % stay out of the solver.

The arithmetic is i64-valued at every node (Int64), because that is what the wire and the Rust fold can represent: literals, parameter values, and each binary result are range-checked, so Python's unbounded ints cannot silently author a value the boundary would reject or misread. The one Python/Rust asymmetry this leaves is within-i64 mid-expression overflow, where release-mode Rust wraps +/-/* while Python computes exactly — the documented overflow-divergence envelope (fold.rs's apply()); Python erring on the honest side here means it raises where Rust may wrap. i64::MIN // -1 raises on both sides (Rust: QuotientOverflow).

Source code in src/guardrail_kernel/dimension_registry.py
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
def fold(expr: FoldOperand, params: Mapping[str, Int64]) -> CheckedInt64:
    """Evaluate a constructor ``carrier_expr`` on concrete literal ``params``.

    Walks the node tree, substituting each `ParamRef` with its value and
    applying each node's own ``_operator``. Rejects anything outside the admitted
    construction arithmetic — this is the construction-context arm of the
    analyzability gate, and the reason ``//`` / ``%`` stay out of the solver.

    The arithmetic is i64-valued at every node ([`Int64`][guardrail_kernel.dimension_registry.Int64]), because that is
    what the wire and the Rust fold can represent: literals, parameter values,
    and each binary result are range-checked, so Python's unbounded ints cannot
    silently author a value the boundary would reject or misread. The one
    Python/Rust asymmetry this leaves is *within*-i64 mid-expression overflow,
    where release-mode Rust wraps `+`/`-`/`*` while Python computes exactly —
    the documented overflow-divergence envelope (`fold.rs`'s `apply()`);
    Python erring on the honest side here means it raises where Rust may wrap.
    ``i64::MIN // -1`` raises on both sides (Rust: `QuotientOverflow`).
    """
    if isinstance(expr, bool):  # bool is an int subclass; disallow as a magnitude
        raise FoldError(f"non-foldable literal {expr!r}")
    match expr:
        case int():
            return _require_i64(expr, "literal")
        case ParamRef():
            try:
                return _require_i64(params[expr.name], f"parameter {expr.name!r}")
            except KeyError:
                raise FoldError(f"unbound parameter {expr.name!r}") from None
        case _Add() | _Sub() | _Mul() | _FloorDiv() | _Mod():
            left = fold(expr.left, params)
            right = fold(expr.right, params)
            return _require_i64(int(expr._operator(left, right)), "result")
        case _:
            # Both halves are load-bearing, and neither substitutes for the other.
            # `assert_never` is the *static* proof: add a kind to `FoldOperand`
            # without a case above and this stops type-checking. The raise is the
            # *runtime* contract, and this arm is genuinely reachable -- phase A
            # leaves operands at `Any`, so `_Add(_Ge(...), 1)` type-checks fine and
            # nothing but this refuses it. Reaching `assert_never` at runtime would
            # turn a diagnosable FoldError into an AssertionError, which is exactly
            # what it did until this arm was written this way.
            if typing.TYPE_CHECKING:
                typing.assert_never(expr)
            raise FoldError(f"non-foldable node {expr!r}")