Files
rose-ash/shared/sx/boundary.py
giles 04366990ec
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m33s
Enforce SX boundary contract via boundary.sx spec + runtime validation
Add boundary.sx declaring all 34 I/O primitives, 32 page helpers, and 9
allowed boundary types. Runtime validation in boundary.py checks every
registration against the spec — undeclared primitives/helpers crash at
startup with SX_BOUNDARY_STRICT=1 (now set in both dev and prod).

Key changes:
- Move 5 I/O-in-disguise primitives (app-url, asset-url, config,
  jinja-global, relations-from) from primitives.py to primitives_io.py
- Remove duplicate url-for/route-prefix from primitives.py (already in IO)
- Fix parse-datetime to return ISO string instead of raw datetime
- Add datetime→isoformat conversion in _convert_result at the edge
- Wrap page helper return values with boundary type validation
- Replace all SxExpr(f"...") patterns with sx_call() or _sx_fragment()
- Add assert declaration to primitives.sx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:50:02 +00:00

145 lines
4.6 KiB
Python

"""
SX Boundary Enforcement — runtime validation.
Reads declarations from boundary.sx + primitives.sx and validates
that all registered primitives, I/O handlers, and page helpers
are declared in the spec.
Controlled by SX_BOUNDARY_STRICT env var:
- "1": validation raises errors (fail fast)
- anything else: validation logs warnings
"""
from __future__ import annotations
import logging
import os
from typing import Any
logger = logging.getLogger("sx.boundary")
# ---------------------------------------------------------------------------
# Lazy-loaded declaration sets (populated on first use)
# ---------------------------------------------------------------------------
_DECLARED_PURE: frozenset[str] | None = None
_DECLARED_IO: frozenset[str] | None = None
_DECLARED_HELPERS: dict[str, frozenset[str]] | None = None
def _load_declarations() -> None:
global _DECLARED_PURE, _DECLARED_IO, _DECLARED_HELPERS
if _DECLARED_PURE is not None:
return
try:
from .ref.boundary_parser import parse_primitives_sx, parse_boundary_sx
_DECLARED_PURE = parse_primitives_sx()
_DECLARED_IO, _DECLARED_HELPERS = parse_boundary_sx()
logger.debug(
"Boundary loaded: %d pure, %d io, %d services",
len(_DECLARED_PURE), len(_DECLARED_IO), len(_DECLARED_HELPERS),
)
except Exception as e:
logger.warning("Failed to load boundary declarations: %s", e)
_DECLARED_PURE = frozenset()
_DECLARED_IO = frozenset()
_DECLARED_HELPERS = {}
def _is_strict() -> bool:
return os.environ.get("SX_BOUNDARY_STRICT") == "1"
def _report(message: str) -> None:
if _is_strict():
raise RuntimeError(f"SX boundary violation: {message}")
else:
logger.warning("SX boundary: %s", message)
# ---------------------------------------------------------------------------
# Validation functions
# ---------------------------------------------------------------------------
def validate_primitive(name: str) -> None:
"""Validate that a pure primitive is declared in primitives.sx."""
_load_declarations()
assert _DECLARED_PURE is not None
if name not in _DECLARED_PURE:
_report(f"Undeclared pure primitive: {name!r}. Add to primitives.sx.")
def validate_io(name: str) -> None:
"""Validate that an I/O primitive is declared in boundary.sx."""
_load_declarations()
assert _DECLARED_IO is not None
if name not in _DECLARED_IO:
_report(f"Undeclared I/O primitive: {name!r}. Add to boundary.sx.")
def validate_helper(service: str, name: str) -> None:
"""Validate that a page helper is declared in boundary.sx."""
_load_declarations()
assert _DECLARED_HELPERS is not None
svc_helpers = _DECLARED_HELPERS.get(service, frozenset())
if name not in svc_helpers:
_report(
f"Undeclared page helper: {name!r} for service {service!r}. "
f"Add to boundary.sx."
)
def validate_boundary_value(value: Any, context: str = "") -> None:
"""Validate that a value is an allowed SX boundary type.
Allowed: int, float, str, bool, None/NIL, list, dict, SxExpr, StyleValue.
NOT allowed: datetime, ORM models, Quart objects, raw callables.
"""
from .types import NIL, StyleValue
from .parser import SxExpr
if value is None or value is NIL:
return
if isinstance(value, (int, float, str, bool)):
return
if isinstance(value, SxExpr):
return
if isinstance(value, StyleValue):
return
if isinstance(value, list):
for item in value:
validate_boundary_value(item, context)
return
if isinstance(value, dict):
for k, v in value.items():
validate_boundary_value(v, context)
return
type_name = type(value).__name__
ctx_msg = f" (in {context})" if context else ""
_report(
f"Non-SX type crossing boundary{ctx_msg}: {type_name}. "
f"Convert to dict/string at the edge."
)
# ---------------------------------------------------------------------------
# Declaration accessors (for introspection / bootstrapper use)
# ---------------------------------------------------------------------------
def declared_pure() -> frozenset[str]:
_load_declarations()
assert _DECLARED_PURE is not None
return _DECLARED_PURE
def declared_io() -> frozenset[str]:
_load_declarations()
assert _DECLARED_IO is not None
return _DECLARED_IO
def declared_helpers() -> dict[str, frozenset[str]]:
_load_declarations()
assert _DECLARED_HELPERS is not None
return dict(_DECLARED_HELPERS)