Expand description
plecto-host — embeds wasmtime to run plecto:filter components (ADR 000001 / 000002).
ADR 000004 slice: the filter runtime model. The host branches on trust at load (ADR 000011’s knot, made concrete):
- trusted filters get a fixed-capacity pool of reusable instances on a
pooling-allocator engine, checked out per request (ADR 000012).
initruns once per instance — Tenet 4 pays off (init-derived state stays resident). The pool is lazily filled: a single thread only ever needs one instance, so init stays once; under concurrency the pool builds more (up to its cap), which is where the pooling allocator finally earns its keep. Saturation (every instance checked out) waits a bounded time then fails closed (Unavailable), and an instance is recycled after serving a configured number of requests to bound linear-memory state accumulation (§6.6). Binding the pool to the tokio/quinn fast path (blocking pool vs fiber) is M2’s job. - untrusted filters get a fresh instance per request on an on-demand engine,
so linear memory is zeroized by construction (no slot reuse → CVE-2022-39393
surface absent, ADR 000006). The cost is
initevery request — the deliberate trade of isolation (ADR 000011).
State lives behind a KvBackend (in-memory or redb) — filters are stateless (Fork 4),
keys are host-namespaced per filter identity + primitive (ADR 000011). The Linker
stays deny-by-default: it lends ONLY the plecto host-API (log / clock / kv /
counter / ratelimit). No WASI, network, filesystem, or sockets (ADR 000006).
Modules§
- otlp
- ADR 000040: the OTLP trace-export half of host observability. Two pieces, both dependency-less (the same reasoning as the hand-written Prometheus exposition — the trace protos are Stable/frozen in opentelemetry-proto, so field numbers never change):
Structs§
- Acquire
- The outcome of a token-bucket acquire (mirrors the WIT
host-ratelimit.acquire). - Bucket
- A token-bucket specification (mirrors the WIT
host-ratelimit.bucket). - Conformance
Check - One named property, pass/fail, with a human-readable detail (the failure reason, or a short confirmation on success).
- Conformance
Report - The full battery’s result.
plecto conformanceexits non-zero unlessConformanceReport::is_conformantis true. - DevSigner
- A persistent ECDSA P-256 / cosign-scheme signer. Holds one key pair; the same key signs
both a filter component and its SBOM, matching a
TrustPolicythat trusts exactly that key. - FanOut
Sink - Send every span to several sinks (e.g. export + aggregate at once). The host holds one sink; this composes many behind it.
- Filter
Span - One filter execution, as a span in the OTel data model. The host builds and emits one of
these per
on_request/on_responsecall; aTelemetrySinkreceives it. - Header
- A single HTTP header field.
- Host
- The wasmtime host: two engines (one per isolation mode) plus the shared state backend. One per process/worker.
- Http
Request - The request as seen by a filter. Header-only in v0.1.0 (no body).
- Http
Response - A response a filter synthesises when it short-circuits the chain.
- InMemory
Sink - A reference / test sink that retains up to
MAX_RETAINED_SPANSspans in memory. - Load
Options - Options for
Host::load. A struct (not a bare arg) because deny-by-default grows more load-time knobs onto it. Defaults to the safe side:Untrusted(fail-closed) with metering on (ADR 000006). A future declarative manifest (ADR 000007) injects these. - Loaded
Filter - A loaded filter, ready to run per request. Trusted filters reuse instances from a
TrustedPool(checked out per request, ADR 000012); untrusted filters instantiate fresh each request. - LogLine
- A log line captured from the host-log capability (test visibility / future tracing).
- Memory
Backend - Process-lifetime, in-memory backend. The default until a durable store is configured.
- Metrics
Sink - Host-aggregated RED-style metrics (ADR 000009 “metrics are host-aggregated”), derived from
the span stream in-process: a sink that tallies rather than retains. Errors are the
RunErroroutcomes (trap / deadline / instantiate / unavailable); short-circuits are counted separately (a filter blocking is not a fault). - Metrics
Snapshot - A point-in-time read of
MetricsSink. - Noop
Sink - The default: observability off, zero cost.
- Redb
Backend - redb-backed durable state. Atomicity comes from redb’s single-writer write
transaction: each
increment/try_acquiredoes its read-modify-write inside one transaction (ADR 000004). redb is fully synchronous; ADR 000011’s async-aware seam is thisKvBackendimpl — when host calls go async, the commits move to a blocking pool here without touching callers. - Request
Edit - A rewrite a filter asks the host to apply to the request before continuing.
- Request
Trace - The host-owned trace context for one request transaction. Created by the chain driver (the
ConfigSnapshot/ fast-path server), it parents every filter span so the whole chain — request side and response side — shares one trace. Cheap to copy. - Response
Edit - A rewrite a filter asks the host to apply to the response before continuing.
- Signed
Artifact - The material the host verifies before instantiating a filter (ADR 000006). The component
bytes plus a keyed cosign signature over them, and a mandatory SBOM with its own
signature. Signatures are RAW DER ECDSA bytes: decoding cosign’s base64
.sigand fetching the artifact from an OCI registry is the ADR 000007 /wkgboundary, kept out of the host so ADR 000006 (verify) and ADR 000007 (distribute) stay decoupled. - Trust
Policy - The set of public keys the operator trusts to sign filters (ADR 000006 provenance). A
filter loads only if a trusted key verifies BOTH its component signature and its SBOM
signature (keyed cosign, offline — no Fulcio / Rekor / network). An empty policy
trusts no one, so nothing loads: deny-by-default / fail-closed, with no “allow unsigned”
escape hatch in the production API. The keys live on the
Host, not on eachloadcall, so the operator manages one trust root.
Enums§
- Contract
Version - Which
plecto:filterpackage version a loaded component targets. - DevKey
Error - Typed dev-key errors (bp-rust: a library’s public surface stays
thiserror;anyhowis for binary entry points). The CLI callers absorb this intoanyhowat their edge. - Hook
- Which hook produced a span (an attribute + a help for sinks/tests).
- Isolation
- How a filter is instantiated and isolated (ADR 000004 / 000011). Not a “trust score”: it selects the instance lifecycle, mirroring how Fastly/Spin model per-request vs reusable sandboxes. Who is trusted is decided elsewhere (OCI signing, ADR 000006); this only says which lifecycle a loaded filter gets.
- Load
Error - Why [
Host::load] rejected a filter (bp-rust: typed library errors, not ad hocanyhow::ensure!). Every variant is a fail-closed rejection at the provenance/id gate, before wasmtime ever touches the component bytes — exceptLoadError::Instantiate/LoadError::Wasmtime, which surface a failure from wasmtime itself (linking / type-checking / the eager trusted-instance build).Host::load’s public signature staysanyhow::Result(unchanged, soplecto-control::ControlError::Load’s existinganyhow::Errorpassthrough keeps working); callers that want the concrete variant candowncast_ref::<LoadError>(). - LogLevel
- Request
Body Decision - The typed outcome of a request-side BODY filter (buffer-then-decide, ADR 000025). v1 carries
the buffered body as
list<u8>; the deferred true-streaming increment swaps it tostream<u8>(the contract keeps room for abodyresource then). Never a bare flag (Tenet 3). - Request
Decision - The typed outcome of a request-side filter (Tenet 3). Never a bare flag.
- Response
Decision - The typed outcome of a response-side filter (ADR 000073).
- RunError
- Why a per-request filter call did not produce a
decision. Kept deliberately distinct fromRequestDecision/ResponseDecision— those are the filter’s intentional typed output; aRunErroris the filter failing. The fast path MUST fail-closed on it: synthesise an error response and never forward to upstream (CLAUDE.md — no fail-open). Keeping the two apart also makes “deadline” vs “trap” an observable health signal. - Span
Outcome - What a filter execution resulted in — the union of its intentional
decisionand theRunErrorfailure modes, so a span records traps and deadlines as faithfully as acontinue. Maps to an OTelStatus(the decisions areOk; the faults areError).
Constants§
- DEV_
KEY_ MARKER - Prefixed onto a persisted dev public-key file, before the PEM block. Plain text ahead of
-----BEGIN...is not part of the PEM grammar (parsers skip straight to the marker), so this survives being read back as a normal SPKI PEM while staying grep-able — the hookplecto validateuses to warn when a dev key ends up in a production manifest’s[trust](ADR 000065 decision 5). - FILTER_
WIT - The canonical
plecto:filter@0.3.0contract text, byte-identical to the vendoredwit/world.witthis module’scrate::bindingsresolves — so a consumer that needs the raw WIT source (e.g.plecto new-filter’s scaffold, ADR 000072) can never drift from what this binary’s own host actually runs. Re-exported viaplecto-controlforplecto-server, which takes no directplecto-hostproduction dependency.
Traits§
- KvBackend
- The place a stateless filter’s mutable state lives. Object-safe so the host can hold
Arc<dyn KvBackend>and pick the backend at construction. Every method is internally synchronized and infallible from the filter’s view — a backend error is logged and resolved fail-closed (reads vanish, rate limits deny), never a panic on the data plane (bp-rust). - Telemetry
Sink - Where the host sends each
FilterSpan. Deliberately sync (the OTel SDK’sSpanExporteris async and would pull tokio — ADR 000009 keeps that named-deferred): a sink must not block the data plane.NoopSinkis the default; an OTLP-mapping sink is added when network export lands.
Functions§
- apply_
bucket - Refill then consume. State is
(tokens, last_refill_ms); the host advanceslastby whole intervals only, so no fractional tokens are lost between calls. Returns the new state to persist and the acquire outcome. - bound_
sbom - A minimal in-toto-style SBOM statement that binds
component: itssubjectdigest issha256(component), satisfying the load gate’s SBOM↔component binding (review f000003 #1). The predicate is empty (content policy is deferred). Production helper — this crate’s ownHost::loadverifies the binding it produces; a real supply chain gets its attestations fromcosign attest. Also re-exported (unchanged) fromtest_supportfor the existing test suites. - header
- Build a canonical header (byte-valued) for callers and tests.
- public_
key_ path_ for - The public-key sibling path
load_or_createwrites next to a private-key path (<path>.pub). Exposed so a caller (e.g.plecto new-filterwriting a dev manifest’s[trust]) can name it without re-deriving the convention. - run_
conformance - Run the generic conformance battery against raw component bytes.