Direct Adapter¶
termverify.direct.DirectAdapter is the deterministic in-process execution path.
It implements the synchronous Adapter protocol by composing two application-owned
ports:
ConstraintPortsapplies the requested seed, manual clock, locale, timezone, terminal, filesystem, and network constraints.DirectApplicationinitializes the subject, handles input and clock epochs, reports quiescent observations, and drains shutdown to a terminal result.
Wiring¶
Construct the adapter with one application object that implements both constraint enforcement and execution. This identity requirement binds receipts to the same subject session that is initialized:
from termverify.direct import DirectAdapter
adapter = DirectAdapter(application)
result = adapter.start(run_id, configuration)
The adapter negotiates constraints in canonical order before calling
application.initialize(). A successful application operation returns
EpochCompleted; natural subject exit returns TerminalResult with a
RunFinished outcome; deterministic application failure returns AdapterFailure
with the operation-appropriate code. Applications must not prewrap failures in a
TerminalResult; the adapter owns failure cleanup and RunFailed construction.
Execution rules¶
- The adapter is single-use and single-flight. It rejects operations before readiness, overlapping/reentrant operations, and operations after termination.
KeyInput,TextInput,Resize, andStopmust use the current manual time.ClockAdvance.at_msmust equal the current time plusdelta_ms.KeyInput.keysis an immutable tuple containing one already-validatedtermverify.key/v1semantic chord. The adapter forwards the object unchanged; it does not translate it to text, toolkit names, or terminal bytes.- Application-reported observations and diagnostics must use the active epoch's manual time. Invalid or unexpected port responses fail closed.
- Port exceptions are contained and converted to stable structured failures; exception text is not exposed as replay evidence.
- After initialization begins, every adapter-detected failure invokes the
synchronous
application.abort(Stop(...))fallback before the failure becomes terminal. An abort exception is recorded as a stable cleanup-failure marker onStartFailedbefore readiness orRunFailedafter readiness. - Quiescence is reported by the application port. The adapter never infers it from wall-clock silence or a polling timeout.
An application that cannot execute a valid KeyInput returns
AdapterFailure("adapter-runtime-failed", ...) with deterministic details such
as {"input_kind": "key", "reason": "unsupported"}. The direct adapter aborts
and constructs RunFailed; it does not emit post-readiness run.unsupported,
which remains configuration-negotiation-only. Applications must never fall back
to TextInput or an escape sequence for an unsupported semantic chord.
Constraint receipts must exactly match the requested run and effective value,
and every receipt must state the mandatory constructive enforcement tier
(termverify.enforcement-tier/v1) — the application's by-construction
assertion that the subject reaches the constrained resource only through the
injected port. Stating it truthfully is part of the DirectApplication port
contract; any other tier, or a delivery record (which belongs exclusively
to the delivered tier), is a contract breach the adapter rejects as a
structured StartFailed. An unsupported constraint or enforcement failure
stops negotiation immediately; the subject is not initialized.
Zero-reach subjects¶
constructive also covers a subject that does not reach a constrained
resource at all: "only through the injected port" is vacuously true when
there is no reach. Stating constructive for a zero-reach constraint is
truthful and never over-claims — the run is more constrained than the
receipt asserts, never less — so the closed termverify.enforcement-tier/v1
vocabulary has no separate no-reach tier. An application that can prove
zero reach (for example by a guarded import or a static capability audit)
may record the stronger claim in a diagnostic record; it is non-oracle
evidence and changes nothing about negotiation. (The wire protocol also
tolerates an x- member on a capability.result payload, but the Python
receipt types carry no extension member, so no adapter API produces one.)