Skip to main content

Crate plecto_host

Crate plecto_host 

Source
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). init runs 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 init every 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).
ConformanceCheck
One named property, pass/fail, with a human-readable detail (the failure reason, or a short confirmation on success).
ConformanceReport
The full battery’s result. plecto conformance exits non-zero unless ConformanceReport::is_conformant is 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 TrustPolicy that trusts exactly that key.
FanOutSink
Send every span to several sinks (e.g. export + aggregate at once). The host holds one sink; this composes many behind it.
FilterSpan
One filter execution, as a span in the OTel data model. The host builds and emits one of these per on_request / on_response call; a TelemetrySink receives 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.
HttpRequest
The request as seen by a filter. Header-only in v0.1.0 (no body).
HttpResponse
A response a filter synthesises when it short-circuits the chain.
InMemorySink
A reference / test sink that retains up to MAX_RETAINED_SPANS spans in memory.
LoadOptions
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.
LoadedFilter
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).
MemoryBackend
Process-lifetime, in-memory backend. The default until a durable store is configured.
MetricsSink
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 RunError outcomes (trap / deadline / instantiate / unavailable); short-circuits are counted separately (a filter blocking is not a fault).
MetricsSnapshot
A point-in-time read of MetricsSink.
NoopSink
The default: observability off, zero cost.
RedbBackend
redb-backed durable state. Atomicity comes from redb’s single-writer write transaction: each increment / try_acquire does its read-modify-write inside one transaction (ADR 000004). redb is fully synchronous; ADR 000011’s async-aware seam is this KvBackend impl — when host calls go async, the commits move to a blocking pool here without touching callers.
RequestEdit
A rewrite a filter asks the host to apply to the request before continuing.
RequestTrace
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.
ResponseEdit
A rewrite a filter asks the host to apply to the response before continuing.
SignedArtifact
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 .sig and fetching the artifact from an OCI registry is the ADR 000007 / wkg boundary, kept out of the host so ADR 000006 (verify) and ADR 000007 (distribute) stay decoupled.
TrustPolicy
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 each load call, so the operator manages one trust root.

Enums§

ContractVersion
Which plecto:filter package version a loaded component targets.
DevKeyError
Typed dev-key errors (bp-rust: a library’s public surface stays thiserror; anyhow is for binary entry points). The CLI callers absorb this into anyhow at 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.
LoadError
Why [Host::load] rejected a filter (bp-rust: typed library errors, not ad hoc anyhow::ensure!). Every variant is a fail-closed rejection at the provenance/id gate, before wasmtime ever touches the component bytes — except LoadError::Instantiate / LoadError::Wasmtime, which surface a failure from wasmtime itself (linking / type-checking / the eager trusted-instance build). Host::load’s public signature stays anyhow::Result (unchanged, so plecto-control::ControlError::Load’s existing anyhow::Error passthrough keeps working); callers that want the concrete variant can downcast_ref::<LoadError>().
LogLevel
RequestBodyDecision
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 to stream<u8> (the contract keeps room for a body resource then). Never a bare flag (Tenet 3).
RequestDecision
The typed outcome of a request-side filter (Tenet 3). Never a bare flag.
ResponseDecision
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 from RequestDecision/ResponseDecision — those are the filter’s intentional typed output; a RunError is 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.
SpanOutcome
What a filter execution resulted in — the union of its intentional decision and the RunError failure modes, so a span records traps and deadlines as faithfully as a continue. Maps to an OTel Status (the decisions are Ok; the faults are Error).

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 hook plecto validate uses 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.0 contract text, byte-identical to the vendored wit/world.wit this module’s crate::bindings resolves — 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 via plecto-control for plecto-server, which takes no direct plecto-host production 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).
TelemetrySink
Where the host sends each FilterSpan. Deliberately sync (the OTel SDK’s SpanExporter is async and would pull tokio — ADR 000009 keeps that named-deferred): a sink must not block the data plane. NoopSink is 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 advances last by 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: its subject digest is sha256(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 own Host::load verifies the binding it produces; a real supply chain gets its attestations from cosign attest. Also re-exported (unchanged) from test_support for 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_create writes next to a private-key path (<path>.pub). Exposed so a caller (e.g. plecto new-filter writing a dev manifest’s [trust]) can name it without re-deriving the convention.
run_conformance
Run the generic conformance battery against raw component bytes.