Skip to content

Grammar

A policy is a few lines of text such as test(suspicious) ; act(block): "when suspicious happens, propose block". This package turns that text (or the same thing built with Python helpers) into an automaton the runtime can step through. Open this page when you write policies in code, need to catch a ParseError, StarHeightError or CompileError (raised for oversized policies and for ambiguous ones, where one event could lead to two actions), or are changing the policy language itself.

Public here: parse, evt, act, seq, star, union, compile_expr, star_height, StarHeightError (top level), plus ParseError, CompileError and complement from grounded_decision.grammar. How to write a policy: Write a policy. How compilation works: Grammar compile.

ast

Typed policy expressions.

The control fragment is a generalized regular expression over event names, with actions as marked terminals. Complement is a Boolean operation and does not increase star-height (family 134).

Expr dataclass

Expr()

Base class for policy expressions.

Epsilon dataclass

Epsilon()

Bases: Expr

Empty sequence.

Test dataclass

Test(name: str)

Bases: Expr

Consume one named event.

Act dataclass

Act(name: str)

Bases: Expr

Terminal marking: propose this action.

Complement dataclass

Complement(inner: Expr)

Bases: Expr

Boolean complement over the same event alphabet.

evt

evt(name: str) -> Test

Event/test atom. Named evt so pytest does not collect it.

Source code in src/grounded_decision/grammar/ast.py
def evt(name: str) -> Test:
    """Event/test atom. Named ``evt`` so pytest does not collect it."""

    return Test(name)

height

Star-height of a generalized regular expression.

Family 134 (Lean): every regular language has generalized star-height ≤ 3. This module enforces the bound on the expression the user wrote. It does not rewrite an arbitrary language down to height 3.

StarHeightError

Bases: ValueError

Expression exceeds the allowed generalized star-height.

parse

Policy DSL: text → Expr via Lark LALR.

The kernel after this module is still the catalog-134 algebra (star-height, Thompson NFA, subset DFA). Lark replaces the old regex tokenizer, which silently skipped illegal characters and had no source positions.

Grammar source: policy.lark next to this file.

Examples::

test(apply) ; (test(eligible) ; act(approve) | test(ineligible) ; act(deny))
(test(ping) ; act(ack))*
test("low-risk") | test(high_risk)   # comments and quoted names
eps

ParseError

Bases: ValueError

Policy text is not in the DSL (Lark unexpected input, wrapped).

PolicyTransformer

Bases: Transformer[Token, Expr]

Lark tree → frozen Expr.

Callbacks use Lark's default calling convention: each rule callback receives the list of its (already transformed) children.

parse

parse(source: str) -> Expr

Parse a policy string into an Expr.

Raises ParseError with Lark's line/column context. Empty or whitespace-only input is an error. Comments start with #.

Source code in src/grounded_decision/grammar/parse.py
def parse(source: str) -> Expr:
    """Parse a policy string into an ``Expr``.

    Raises ``ParseError`` with Lark's line/column context. Empty or
    whitespace-only input is an error. Comments start with ``#``.
    """

    if not source or not source.strip():
        raise ParseError("empty policy")
    try:
        # ``Lark.parse`` takes ``LarkInput``, an alias over the unparameterized
        # generic ``TextSlice``, so pyright sees its signature as partially
        # unknown even though ``str`` is the documented input type.
        tree = _parser().parse(source)  # pyright: ignore[reportUnknownMemberType]
    except UnexpectedInput as exc:
        raise ParseError(_describe_parse_failure(exc, source)) from exc
    if not isinstance(tree, Expr):
        raise ParseError(f"parser returned {type(tree).__name__}, expected Expr")
    return tree

dfa

Compile a policy expression to a small DFA / Mealy machine.

Actions are markings on states, not input letters. Tests are letters. Complement determinizes and flips accepting bits on the event language, then drops action markings (complement is a language operation).

CompileError

Bases: ValueError

The policy cannot be compiled: past a size limit, or ambiguous.

A policy is ambiguous when one event can lead to two different actions at once, as in test(e) ; (act(a) | act(b)).

DFA dataclass

DFA(
    states: int,
    start: int,
    alphabet: tuple[str, ...],
    trans: tuple[tuple[int, ...], ...],
    actions: tuple[str | None, ...],
    accepting: tuple[bool, ...],
    dead: int | None = None,
)

Deterministic machine with optional action labels on states.

dead class-attribute instance-attribute

dead: int | None = None

The reject sink: the state reached when the policy has no continuation for an event. None when every event always has one.

compile_expr

compile_expr(
    expr: Expr, limits: Limits = DEFAULT_LIMITS
) -> DFA

Compile a policy expression to a DFA with actions marked on states.

Raises:

Type Description
StarHeightError

Star-height exceeds limits.max_star_height.

CompileError

The NFA or DFA exceeds its size cap, or the policy is ambiguous: one event (or the start) can lead to two different actions, as in test(e) ; (act(a) | act(b)). Give each action its own event, for example test(e) ; (test(ok) ; act(a) | test(bad) ; act(b)).

Source code in src/grounded_decision/grammar/dfa.py
def compile_expr(expr: Expr, limits: Limits = DEFAULT_LIMITS) -> DFA:
    """Compile a policy expression to a DFA with actions marked on states.

    Raises:
        StarHeightError: Star-height exceeds ``limits.max_star_height``.
        CompileError: The NFA or DFA exceeds its size cap, or the policy is
            ambiguous: one event (or the start) can lead to two different
            actions, as in ``test(e) ; (act(a) | act(b))``. Give each
            action its own event, for example
            ``test(e) ; (test(ok) ; act(a) | test(bad) ; act(b))``.
    """

    return _compile(expr, limits, check_actions=True)