Server (sx_server.ml):
- HTTP mode: JIT hook now opt-in via SX_SERVING_JIT, matching epoch mode
(was unconditional — live serving-JIT miscompiles J1/J2/J3 de-risked)
- command channel: malformed/non-ASCII line returns an error response
instead of killing the shared process (C1/C1b)
- response cache: soft error pages no longer cached (S4);
http_render_page returns (html, is_error)
Kernel spec + regen:
- crit-2: signal-return frame stored the saved kont under :f but the reader
looked up "saved-kont" — handler value became the whole program's result
and the covering test passed vacuously. Fixed; raise-continuable now also
resumes at the raise site (rest-k, not unwound-k), mirroring signal-condition
- quasiquote: R7RS longhand unquote-splicing aliased to splice-unquote
(used to serialize literally — silent zero-splice)
- guard: re-raise sentinel gensym'd per execution (was forgeable by any
(list '__guard-reraise__ x) value)
- do: IIFE-head form no longer misparses as a Scheme do-loop
- render: area/base/embed/param/track added to HTML_TAGS (were void-only
and rendered as Undefined symbol)
- REGEN REPAIR: checked-in sx_ref.ml carried hand-written additions that
every regeneration silently lost (let-values/define-values/delay/
delay-force registrations, AdtValue define-type) plus 5 regen blockers
(arrow-name mangling, 3-arg get, &rest defines, HO-position helper refs,
transpiler prim-table gaps). Moved into bootstrap.py FIXUPS/skips and the
transpiler prim table — regen is now reproducible, compiles, and tests
at baseline (CI Dockerfile.test steps 3-4 could not previously have
produced a compiling kernel)
Primitives:
- contains?: dict key-check arm per its spec doc
- expt: promotes to float on int63 overflow ((expt 2 100) returned 0)
- mcp_tree parity with sx_primitives: get (Integer indices + 3-arg default),
split (literal substring, was char-class — the historical gotcha lived
here), empty? on ""/{}, contains?, equal?, keyword-name, char-code
(Integer), parse-number (Integer-aware)
Python/docs:
- shared/sx/boundary.py: dead validation now logs a one-time WARNING instead
of silently no-oping (full revival gated: tier-1 declarations deleted and
SX_BOUNDARY_STRICT=1 is live in production compose)
- CLAUDE.md: canonical reference now points at spec/*.sx; island authoring
rules corrected (let IS sequential, bodies ARE implicit begin)
Verification: full suite 5762 passed / 274 failed — fail set byte-identical
to the pre-change baseline (273 in-progress hs-* + pre-existing r7rs radix
shadow). All repros verified fixed on both the native binary and the rebuilt
WASM browser kernel. Review findings: /tmp/sx-review/*.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
167 lines
5.8 KiB
Python
167 lines
5.8 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
|
|
|
|
|
|
_LOAD_FAILURE_WARNED = False
|
|
|
|
|
|
def _load_declarations() -> None:
|
|
# KNOWN-BROKEN (2026-07 review, finding C24): `.ref.boundary_parser` was
|
|
# moved to hosts/python/boundary_parser.py AND its tier-1 declaration
|
|
# source (shared/sx/ref/boundary.sx) was deleted, so this ImportError
|
|
# fires on every call and boundary validation has been a silent no-op —
|
|
# including under SX_BOUNDARY_STRICT=1, which production compose sets.
|
|
# Do NOT "fix" the import in isolation: reviving validation while strict
|
|
# mode is live in production requires first recreating the core
|
|
# declarations and proving zero violations across all services
|
|
# (remediation plan, Phase 2 "Python boundary"). Until then we make the
|
|
# dead state visible instead of silent.
|
|
global _DECLARED_PURE, _DECLARED_IO, _DECLARED_HELPERS, _LOAD_FAILURE_WARNED
|
|
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:
|
|
# Don't cache failure — parser may not be ready yet (circular import
|
|
# during startup). Will retry on next call. Validation functions
|
|
# skip checks when declarations aren't loaded.
|
|
if not _LOAD_FAILURE_WARNED:
|
|
_LOAD_FAILURE_WARNED = True
|
|
logger.warning(
|
|
"SX boundary validation is INACTIVE (declarations failed to "
|
|
"load: %s). All validate_* calls are no-ops, even under "
|
|
"SX_BOUNDARY_STRICT=1.", e,
|
|
)
|
|
else:
|
|
logger.debug("Boundary declarations not ready yet: %s", e)
|
|
|
|
|
|
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()
|
|
if _DECLARED_PURE is None:
|
|
return # Not ready yet (circular import during startup), skip
|
|
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 or boundary-app.sx."""
|
|
_load_declarations()
|
|
if _DECLARED_IO is None:
|
|
return # Not ready yet, skip
|
|
if name not in _DECLARED_IO:
|
|
_report(
|
|
f"Undeclared I/O primitive: {name!r}. "
|
|
f"Add to boundary.sx (core) or boundary-app.sx (deployment)."
|
|
)
|
|
|
|
|
|
def validate_helper(service: str, name: str) -> None:
|
|
"""Validate that a page helper is declared in {service}/sx/boundary.sx."""
|
|
_load_declarations()
|
|
if _DECLARED_HELPERS is None:
|
|
return # Not ready yet, skip
|
|
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 {service}/sx/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.
|
|
NOT allowed: datetime, ORM models, Quart objects, raw callables.
|
|
"""
|
|
from .types import NIL
|
|
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, 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()
|
|
return _DECLARED_PURE or frozenset()
|
|
|
|
|
|
def declared_io() -> frozenset[str]:
|
|
_load_declarations()
|
|
return _DECLARED_IO or frozenset()
|
|
|
|
|
|
def declared_helpers() -> dict[str, frozenset[str]]:
|
|
_load_declarations()
|
|
return dict(_DECLARED_HELPERS) if _DECLARED_HELPERS else {}
|