symfonic.services.shadow.harness¶
harness ¶
The effect-suppressed shadow harness.
One object owns the whole claim "this run performed no externally visible effect": it refuses to start against an incomplete classification, aborts before executing an opaque extension, routes every port call through the gateway, keeps replacement state writes in an overlay, and watches port-mediated extensions for direct effects.
run does not raise on abort. An abort is an outcome of a shadow run โ
the caller wants the ledger and the reason, not a traceback โ so the result
carries the status and raise_for_status re-raises on demand.
ShadowContext ¶
ShadowContext(*, gateway: ShadowEffectGateway, state: SuppressedStateWriter, trust: ExtensionTrustRegistry, watch: bool)
What the body of a shadow run is given. Nothing else is reachable.
Source code in src/symfonic/services/shadow/context.py
claim_defects
property
¶
Reasons the suppression claim is void, independent of exceptions.
Detection must not depend on the exception reaching the harness: a
body with a bare except would otherwise buy back the claim the
sentinel just refused. Every detection path records here first and
raises second.
call_extension ¶
Run a synchronous extension only if its trust class permits it.
Opaque extensions raise before fn is touched. Port-mediated ones
run inside the sentinel, so a wrong declaration becomes a
MisdeclaredExtensionError instead of a silent effect. An extension
that returns an awaitable is refused: its body would run after the
probes came down. Use :meth:call_extension_async for those.
Source code in src/symfonic/services/shadow/context.py
call_extension_async
async
¶
Await an async extension inside the sentinel scope.
In an async-first framework the common tool shape is a coroutine
function; calling one only builds the coroutine, so a synchronous
call_extension would uninstall the probes before a single line of
the extension ran. This awaits under the probes, which is the whole
point of watching.
Source code in src/symfonic/services/shadow/context.py
ShadowHarness ¶
ShadowHarness(*, trust: ExtensionTrustRegistry, classification: EffectPortClassification = DEFAULT_EFFECT_CLASSIFICATION)
Builds and supervises shadow runs.
Source code in src/symfonic/services/shadow/harness.py
ShadowRunResult
dataclass
¶
ShadowRunResult(run_id: str, tenant_id: str, status: ShadowStatus, ledger: EffectLedger, state_intents: tuple[StateWriteIntent, ...], extensions_executed: tuple[str, ...], body_executed: bool, suppression_claim: bool, claim_defects: tuple[str, ...] = (), abort_reason: str | None = None, error: BaseException | None = None, value: Any = None)
The reviewable artifact of one shadow run.
ShadowRunSpec
dataclass
¶
ShadowRunSpec(run_id: str, tenant_id: str, extensions: tuple[str, ...] = (), baseline_state: Mapping[str, Any] = dict(), backing_state: Mapping[str, Any] | None = None, responder: StubResponder | None = None, watch_extensions: bool = True)
What a shadow run is allowed to be before it starts.