Skip to content

symfonic.services.shadow.sentinel

sentinel

SHD-MD — the direct-effect sentinel, which catches a wrong trust class.

A port-mediated declaration is a claim about code: this extension's effects are reachable only through injected framework port clients. The claim can be wrong — a reviewer can approve a tool that quietly opens a file. The sentinel is what makes the claim falsifiable at runtime: while a supposedly port-mediated extension runs, the process's direct-effect entry points are replaced with probes that refuse and record.

It is deliberately not a sandbox. It does not try to be exhaustive over every way Python can touch the world; it is a detector whose job is to make a mis-declaration produce a loud, attributable failure instead of a quiet leak. Anything it cannot see is precisely why the default trust class is opaque.

Two properties make it safe to run inside a live process, and both are structural rather than advisory:

Attribution. The probes are process-global, but a witness is only recorded when the calling context is the one that installed them, tracked through a :class:~contextvars.ContextVar. Shadow mode runs alongside live traffic, so an unrelated coroutine that happens to call open() during the window must reach the real builtin — otherwise the sentinel would break that traffic and attribute its I/O to the extension under test.

Reinstatement. Install and restore are reference-counted under a lock, and restoration reinstates the module-level originals captured at import rather than "whatever was installed when I entered". Saving the ambient value at enter is what lets two overlapping windows leave a probe installed forever.

Type identity. socket.socket is a class, and live code asks about it as one — anyio.abc._sockets does isinstance(sock_or_fd, socket.socket) on the request path this harness runs beside. Rebinding the name to a function would make that isinstance raise TypeError for every co-resident task for as long as a window is open, which is the same traffic-breaking failure attribution exists to avoid. The probe therefore wraps socket.socket's __init__ and leaves the class itself in place.

DirectEffectSentinel dataclass

DirectEffectSentinel(subject: str = '<extension>', block: bool = True, witnesses: list[DirectEffectWitness] = list())

Records — and by default blocks — direct effects during a call.

block=True (the default, and what the harness uses) makes the probe raise, so a mis-declared extension does not get to perform the effect it was not supposed to be able to perform. block=False is observe-only: the witness is recorded and the call is passed through to the real entry point, which is what an audit of a candidate declaration wants — it learns what the extension actually touches without changing its behaviour.

Either way the witness list is checked afterwards by assert_clean, so an extension that swallows the exception, or one that was merely observed, is still detected and still loses its declaration.

assert_clean

assert_clean() -> None

Raise if anything was witnessed, even if the extension caught it.

Source code in src/symfonic/services/shadow/sentinel.py
def assert_clean(self) -> None:
    """Raise if anything was witnessed, even if the extension caught it."""
    if not self.witnesses:
        return
    detail = "; ".join(f"{w.probe}: {w.detail}" for w in self.witnesses)
    raise MisdeclaredExtensionError(
        f"{self.subject!r} is declared port-mediated but performed direct "
        f"effect(s) — {detail}. The trust declaration is withdrawn."
    )

watching

watching() -> Iterator[DirectEffectSentinel]

Install the probes for the duration of one extension call.

The probes themselves are process-global and reference-counted, so overlapping windows install once and restore once; ownership of any effect they see is decided by the context variable, which is scoped to this with block and therefore to this task.

Source code in src/symfonic/services/shadow/sentinel.py
@contextmanager
def watching(self) -> Iterator[DirectEffectSentinel]:
    """Install the probes for the duration of one extension call.

    The probes themselves are process-global and reference-counted, so
    overlapping windows install once and restore once; ownership of any
    effect they see is decided by the context variable, which is scoped to
    this ``with`` block and therefore to this task.
    """
    token = _ACTIVE.set(self)
    _install()
    try:
        yield self
    finally:
        _uninstall()
        _ACTIVE.reset(token)

DirectEffectWitness dataclass

DirectEffectWitness(probe: str, detail: str)

One observed effect that did not cross a framework port.