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 ¶
Raise if anything was witnessed, even if the extension caught it.
Source code in src/symfonic/services/shadow/sentinel.py
watching ¶
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
DirectEffectWitness
dataclass
¶
One observed effect that did not cross a framework port.