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')

EBranch module-attribute

EBranch = TypeVar('EBranch')

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
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
@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
46
47
48
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: 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
 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
120
121
122
@dataclass(frozen=True)
class BinaryNode(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: BinaryNode[Any, Any],
        *,
        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
96
97
98
99
@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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def needs_parentheses(
    self,
    child: BinaryNode[Any, Any],
    *,
    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
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
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 __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: Generic[L]

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

Source code in src/guardrail_kernel/nodes.py
159
160
161
162
163
164
165
166
167
168
@dataclass(frozen=True)
class UnaryNode(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.

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
171
172
173
174
175
@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
177
178
179
180
181
@dataclass(frozen=True)
class BoolBinaryNode(Generic[L, R], BinaryNode[L, R]):
    """A binary node whose operands combine into a z3 boolean term."""

    _operator: typing.ClassVar[Callable]

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
183
184
185
186
187
188
189
190
191
192
193
194
@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 exposes one (the DSL values do), otherwise falls back to repr.

Source code in src/guardrail_kernel/nodes.py
333
334
335
336
337
338
339
340
341
342
343
def display_value(value: object) -> str:
    """Best-effort human label for any operand.

    Uses the value's ``.label`` when it exposes one (the DSL values do),
    otherwise falls back to ``repr``.
    """
    label = getattr(value, "label", None)
    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
346
347
348
349
350
351
def label_of(value: object) -> str:
    """Like :func:`display_value`; the canonical operand-labelling entry point."""
    label = getattr(value, "label", None)
    if isinstance(label, str):
        return label
    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
374
375
376
377
378
def binary_label(node: BinaryNode[Any, Any]) -> 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 descriptor reference. The descriptor's literal name parameter preserves the static TimeLike / DurationLike distinction without a parallel runtime tag. Boolean predicates are not carrier dimensions and continue to use _z3_dim.

TimeLike module-attribute

TimeLike = DimensionLike[Literal['Instant']]

DurationLike module-attribute

DurationLike = DimensionLike[Literal['Duration']]

DimensionIdentity

Typed identity facade derived from the canonical dimension descriptor.

Source code in src/guardrail_kernel/dimensions.py
57
58
59
60
61
62
class DimensionIdentity[NameT: str]:
    """Typed identity facade derived from the canonical dimension descriptor."""

    @property
    def dimension_name(self) -> NameT:
        return cast(NameT, dimension_name_of(self))

dimension_name property

dimension_name

DimensionLike

Bases: Protocol

A dimensioned arithmetic value with statically visible identity.

Source code in src/guardrail_kernel/dimensions.py
65
66
67
68
69
70
71
72
73
@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
80
81
82
83
84
85
86
87
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.

Source code in src/guardrail_kernel/dimensions.py
27
28
29
30
31
32
33
34
35
36
37
38
def dimension_of(value: object) -> Dimension[Any] | 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.
    """
    dimension = getattr(value, "_dimension", None)
    if dimension is None or isinstance(dimension, property):
        dimension = getattr(type(value), "_dimension", None)
    return cast("Dimension[Any] | None", dimension)

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
41
42
43
44
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

unit_of

unit_of(value)

The unit tag a value carries, or None.

Carriers keep the tag under different names by convention — a tagged-vector literal as currency (e.g. Money), an expression wrapper as unit — so this is the single reader that reconciles them, alongside :func:dimension_of.

Source code in src/guardrail_kernel/dimensions.py
47
48
49
50
51
52
53
54
def unit_of(value: object) -> str | None:
    """The unit tag a value carries, or ``None``.

    Carriers keep the tag under different names by convention — a tagged-vector literal as
    ``currency`` (e.g. ``Money``), an expression wrapper as ``unit`` — so this is the single
    reader that reconciles them, alongside :func:`dimension_of`.
    """
    return getattr(value, "currency", None) or getattr(value, "unit", None)

is_bool_like

is_bool_like(value)

True if value carries the bool dimension tag.

Source code in src/guardrail_kernel/dimensions.py
90
91
92
def is_bool_like(value: object) -> bool:
    """True if ``value`` carries the bool dimension tag."""
    return getattr(value, "_z3_dim", None) == "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]

Predicate dataclass

Bases: 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
 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
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
@typing.final
@dataclass(frozen=True, eq=False)
class Predicate(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
    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})",
        )

node instance-attribute

node

label instance-attribute

label

annotate

annotate(text)

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

Source code in src/guardrail_kernel/expressions.py
78
79
80
81
82
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)

TimeComparableMixin

Bases: _ComparableRuntime

Source code in src/guardrail_kernel/expressions.py
292
class TimeComparableMixin(_ComparableRuntime): ...

DurationComparableMixin

Bases: _ComparableRuntime

Source code in src/guardrail_kernel/expressions.py
294
class DurationComparableMixin(_ComparableRuntime): ...

SlotComparableMixin

Bases: _ComparableRuntime

Source code in src/guardrail_kernel/expressions.py
296
class SlotComparableMixin(_ComparableRuntime): ...

SlotRef dataclass

Bases: SlotComparableMixin

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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
@typing.final
@dataclass(frozen=True)
class SlotRef[ValueT = Any](SlotComparableMixin):
    """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: object) -> 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: object) -> Predicate[_Eq[SlotRef[ValueT], object]]:
        """``slot @ value`` — an assignment predicate over this output slot."""
        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
326
327
328
329
330
331
def includes(self, target: object) -> 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
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
@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
112
113
114
115
116
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
119
120
121
122
123
124
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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

The local-subject proxy _.

CurrentSubjectRef is the primitive authoring proxy. Attribute access yields slot references; the well-known departure observable is time-typed, which is why the only coupling here is a deferred import of the dimensions layer (safe at call time, never at import time).

CurrentSubjectRef dataclass

The local proxy _ standing for "the current agent/subject".

Inside a rule or subject block, _.confidence / _.routes name this vertex's own slots, and _.departure names its time observable. The proxy is rebound to the concrete subject during ingestion.

Source code in src/guardrail_kernel/dsl.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@dataclass(frozen=True)
class CurrentSubjectRef:
    """The local proxy ``_`` standing for "the current agent/subject".

    Inside a rule or subject block, ``_.confidence`` / ``_.routes`` name this
    vertex's own slots, and ``_.departure`` names its time observable. The
    proxy is rebound to the concrete subject during ingestion.
    """

    @property
    def departure(self) -> BoundTimePoint:
        """The current subject's time observable (e.g. ``_.departure``)."""
        from guardrail_calculus.dimensions import BoundTimePoint

        return BoundTimePoint("departure")

    def __getattr__(self, name: str) -> SlotRef:
        """Any other attribute is an output-slot reference on this subject."""
        return SlotRef(Path((name,)))

departure property

departure

The current subject's time observable (e.g. _.departure).