Skip to main content

Crate plecto_control

Crate plecto_control 

Source
Expand description

plecto-control — the control plane (ADR 000007 / 000008).

A single declarative TOML manifest pins filters by OCI digest and declares one chain. An ArtifactStore resolves each filter from a local, offline OCI image-layout (remote registry fetch via wkg is an out-of-band operator step, kept out of the runtime), the Host loads it through the ADR 000006 provenance gate, and a chain dispatcher drives a request through the loaded filters. reload rebuilds the set and swaps it atomically (ArcSwap): new requests see the new set, in-flight holders keep the old one until it drops. Single-node-first (ADR 000008).

The trust policy lives on the Host and is fixed at construction; changing trust roots requires a new Control. reload swaps only the filter set + chain (same Host, same epoch ticker), so a runaway filter stays bounded across reloads.

Modules§

oci
Offline OCI image-layout ArtifactStore (ADR 000007). Reads a local image-layout directory — no registry, no network — per the CNCF Wasm OCI Artifact layout, verifies the image-manifest digest against the manifest pin, and extracts the component plus its bundled signature / SBOM layers (custom mediaTypes). Remote fetch (wkg) is an out-of-band operator step that produces such a layout.
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§

Chain
The single ordered chain for v0.1 (named chains / route matching are deferred to the fast-path server).
CircuitBreaker
Per-upstream circuit-breaker thresholds (ADR 000028). Overload protection that is deliberately SEPARATE from outlier detection / health (ADR 000017): health answers “is this instance up?”, the breaker answers “is this upstream saturated?”. A request rejected by the breaker is the upstream shedding load, not an instance failing — so it never demotes an instance.
CompressionConfig
A route’s compiled [route.compression] (ADR 000074): the offered codings in server-preference order, the min-length floor, and the content-type allowlist (lower-cased at build so the per-response check is an allocation-free case-insensitive scan over a short list).
ConfigSnapshot
A configuration pinned for one request transaction. Obtain via crate::Control::snapshot; run on_request then (later) on_response against the same snapshot so a reload cannot desync the two halves.
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.
Control
The control plane: owns the Host (and thus the trust policy + epoch ticker) and the artifact store, and holds the active filter set behind an ArcSwap for lock-free reads and atomic reload. manifest_path is Some only when the plane was built from an on-disk manifest — that is what reload_from_disk (and the SIGHUP loop) re-reads.
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.
Diagnostic
A stable four-part diagnostic. docs is a repo-relative path (ADR 000065: “docs 参照は当面 リポジトリ内相対” — no dedicated docs domain yet), printed as a hint, not resolved from disk.
Endpoints
The swappable endpoint set of a group: the instances plus the LB state compiled from them (a Maglev table indexes into instances, so the two must swap together). Behind an ArcSwap on the group so periodic DNS re-resolution (the standard periodic-DNS endpoint-discovery technique — the shape of nginx resolve / Envoy STRICT_DNS) can replace the set in place while routes keep holding their Arc<UpstreamGroup>.
FanOutSink
Send every span to several sinks (e.g. export + aggregate at once). The host holds one sink; this composes many behind it.
FilterEntry
One filter to load, pinned by OCI digest.
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.
HealthConfig
Active-health-check policy (ADR 000017). A background prober issues GET {path} to each instance every interval_ms — on the instance’s own traffic port, unless port names a dedicated health-check port — and a 2xx within timeout_ms is a success, anything else (non-2xx, timeout, connect error) a failure. unhealthy_threshold consecutive failures eject a healthy instance from the rotation; healthy_threshold consecutive successes restore an ejected one. Instances start pessimistic (unhealthy); the FIRST successful probe alone promotes a never-yet-healthy instance (cold-start fast path), after which the full healthy_threshold governs re-entry. Only path is required; the rest default. PartialEq lets a reload detect a changed policy and re-probe the upstream’s instances from scratch (ADR 000017 reconcile).
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.
Manifest
A parsed manifest. Deserialised from TOML; no I/O happens here (key files and artifacts are resolved by Control). Serialize exists only to derive the semantic content hash (content_hash) — the canonical, representation-independent identity of the config.
MemoryStore
An in-memory ArtifactStore keyed by source name — the reference / test store. Its content digest is sha256 over the component bytes, so the pin mechanism is exercised without an on-disk OCI layout.
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.
Observability
Operational observability config ([observability], ADR 000009 Stage A): a separate admin listener exposing Prometheus metrics + liveness/readiness, and an opt-in structured access log. Off by default — Plecto stays quiet and exposes nothing extra unless asked (operational simplicity). Captured at construction; a reload does not re-bind the admin listener.
OutlierDetection
Per-upstream outlier-detection policy (ADR 000032). A THIRD resilience axis: active health (ADR 000017) asks “is the instance reachable?”, the circuit breaker (ADR 000028) “is the upstream saturated?”, and this “is the instance misbehaving on live traffic?”. An ejected instance leaves the rotation for a (backing-off) ejection window, independent of the health bit; a shed request (circuit breaker) never feeds it, only real upstream 5xx responses do.
Pick
A chosen instance plus a guard tracking its load (ADR 000035). For least_request the guard holds the selected instance’s incremented active-request count and decrements it on drop — on EVERY forward return path (success, retry, transport error) and on each retry hand-off, because replacing the Pick drops the previous guard. For round-robin / maglev the guard is a no-op. Derefs to the instance so callers read address() / is_healthy() directly.
ProxyProtocolTrust
The parsed, runtime form of [listen.proxy_protocol]: the trusted networks behind a containment check. The fast path asks contains(peer.ip()) and never re-parses CIDRs; keeping the match here keeps the canonicalisation rule in one place.
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.
ResolvedArtifact
The verified bytes a filter loads from: the component plus its cosign signature, and the (mandatory) SBOM plus its signature (ADR 000006). Produced after the digest-pin check.
Route
One routing rule (ADR 000013 / 000034): match a request by the [route.match] dimensions, run an inline chain of filters, and forward to a single upstream (shorthand) or a weighted set of backends (traffic split / canary). strip_prefix is a host-native path rewrite applied to the forwarded request only (the chain still sees the original path) — the common reverse-proxy prefix-strip, without a plecto:filter contract change. filters / strip_prefix / rate_limit are per-route: they apply identically across every backend (ADR 000034 keeps a backend a pure {upstream, weight} pair; a route needing different policy per target uses a separate route).
RouteCompression
A route’s compression declaration ([route.compression], ADR 000074). Every field has a safe default, so the bare block header is the whole opt-in; the fields exist to narrow it.
RouteInfo
What crate::ConfigSnapshot::find_route hands the fast-path server: which route matched (index, used to dispatch its chain) plus the data needed to forward — the weighted backends (the server picks a group, then a healthy instance from it) and the optional prefix strip. Owned / Arc-shared so it survives a move into spawn_blocking.
RouteRateLimit
Native per-route rate-limit spec (ADR 000033), declared as [route.rate_limit]. A coarse token bucket the fast path consults before forwarding — the Tier-0 baseline a filterless route otherwise lacks. rate/burst map onto the same token-bucket math as host-ratelimit (capacity = burst, refill rate tokens every second), but the surface is deliberately the friendlier two-knob (rate + burst) form, since this is a blunt floor, not a policy limiter.
SignalReloadSource
The real trigger: SIGHUP, the operator’s “I edited the manifest, pick it up” signal. Unix only — signals are a unix concept. Construct once, then hand to serve_reloads.
State
The host state backend ([state], ADR 000041): the single knob choosing where the three state capabilities (host-kv / host-counter / host-ratelimit) keep their bytes. memory (the default) keeps zero-config startup — state dies with the process; redb makes it durable at path, so counters and rate-limit windows survive a restart (fail-closed direction, ADR 000004). One backend serves all three capabilities; fixed at construction like [trust] (PartialEq backs the reload rejection — restart to apply).
TlsCert
One TLS server certificate (ADR 000014). The fast path terminates TLS with rustls and selects a cert by SNI: host names the SNI this cert serves (case-insensitive); None is the default cert presented when no SNI matches. cert_path / key_path are manifest-relative PEM files (a cert chain and its private key). Only the paths ride the manifest content hash, so a path change reloads but an in-place file edit does not (ADR 000014).
TlsClientConfig
The rustls TLS client config the fast path re-encrypts upstream forward legs with (ADR 000042), re-exported for the same reason as TlsServerConfig. Common configuration for (typically) all connections made by a program.
TlsServerConfig
The rustls TLS server config the fast path terminates with (ADR 000014), re-exported so plecto-server names the same rustls type the control plane built. Common configuration for a set of server sessions.
Trust
Trust roots: paths (manifest-relative) to trusted signer public keys, PEM (ADR 000006). PartialEq lets reload detect a trust-section change (which it rejects — trust is fixed at construction, f000004 #1).
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.
UpgradeConfig
A route’s compiled [route.upgrade] (ADR 000048): the lower-cased token allowlist and the tunnel idle timeout. Pre-normalised at build so the per-request check is a scan over a (typically one-element) list.
Upstream
A named upstream the fast-path server forwards a matched request to (ADR 000013 / 000017). One or more addresses (host:port, no scheme) are the upstream’s instances; the fast path balances across the healthy ones per lb_algorithm. The forward leg is plain HTTP/1.1 unless [upstream.tls] is declared, which re-encrypts with rustls and lets ALPN negotiate h2 / http/1.1 (ADR 000042). Every upstream carries an active-health-check policy (health) — required, because instances start pessimistic (unhealthy) and only a passing probe puts one into rotation (ADR 000017).
UpstreamGroup
A named upstream: its endpoint set, the round-robin cursor, and the health policy (ADR 000017).
UpstreamInstance
One backend instance (host:port) of an upstream, with its health state (ADR 000017).
UpstreamRegistry
The live set of upstreams, keyed by name. Owned by Control, OUTSIDE the swapped ActiveConfig, so health state survives a reload (ADR 000017). The Mutex is contended only by reconcile (on reload) and the prober supervisor (groups) / a config build (group) — never the per-request hot path, which holds an Arc<UpstreamGroup> resolved at build time.
ValidateOutcome
validate_manifest / validate_manifest_path’s success value: the manifest’s config version plus any non-fatal Diagnostic warnings (ADR 000065 decision 5) — currently just DEV_KEY_IN_TRUST, raised when a [trust] key file carries plecto_host::DEV_KEY_MARKER. A warning never fails validation: a plecto dev-generated manifest is SUPPOSED to trip it.

Enums§

ChainOutcome
The result of driving a request through the chain.
CompressionAlgorithm
A content coding Plecto can produce (ADR 000074: gzip baseline + zstd / brotli). The serde spelling is the wire token (Accept-Encoding / Content-Encoding), so the manifest reads exactly like the negotiation it configures.
ControlError
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.
HashInput
A request attribute to hash for Maglev affinity (ADR 000035), borrowed so the hot path allocates nothing: a header value’s bytes (borrowed from the request) or the peer IP (hashed as its canonical octets, not a string). The fast path builds this from a group’s HashKeySource. Copy so it can be passed to the initial pick and each retry’s pick_excluding unchanged.
HashKeySource
The request attribute a Maglev upstream hashes for affinity (ADR 000035), resolved from the manifest [upstream.hash]. The fast path turns this into a HashInput per request.
IsolationKind
Manifest spelling of the host’s Isolation. Defaults to untrusted (fail-closed).
RateLimitDecision
The fast path’s view of a rate-limit consult (ADR 000033).
RateLimitKeyKind
The dimension a native route rate limit counts against (ADR 000033).
ReloadOutcome
Outcome of a single disk reload. A failed reload is not represented here — it is the Err arm of reload_from_disk, and leaves the running set untouched (all-or-nothing, fail-closed). The success arm distinguishes “nothing to do” from “swapped”.
RequestBodyOutcome
The result of driving a buffered request body through the chain’s on-request-body hooks (ADR 000025).
ResponseOutcome
The result of driving a response back through the chain (ADR 000073): the typed successor of the old in-band “non-empty body means synthetic” 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).
StateBackendKind
Manifest spelling of the state backend (ADR 000041). Defaults to memory.

Constants§

DEV_KEY_IN_TRUST
A manifest [trust] key is marked as a dev key (ADR 000065 decision 2) — warned, not rejected, because a plecto dev-generated manifest is SUPPOSED to reference one; this only flags the case for a human to double check when it shows up somewhere they did not expect.
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.
PATH_NORMALIZATION_REJECTED
Request path failed normalization — the third wall.
QUOTA_EXCEEDED
Host-native rate-limit bucket exhausted — the second wall.
SIGNATURE_VERIFICATION_FAILED
Signature verification failure — the first wall (ADR 000006 provenance gate).

Traits§

ArtifactStore
Resolves a manifest source into a ResolvedArtifact, verifying that the artifact’s content digest equals pinned_digest (sha256:...). Implementations are fail-closed on any mismatch or malformed artifact.
ReloadSource
A source of reload triggers. recv blocks until the next trigger fires, or returns None when the source is exhausted / shut down (the loop then ends). Implementors carry no config: a trigger only ever means “re-read the manifest now”.
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§

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.
diagnose
Map a ControlError to its Diagnostic, if the plain error message needs one. None means the thiserror message already stands on its own — most variants do.
diagnosed_message
Render err for a human-facing surface (startup, plecto dev), appending the four-part diagnostic when one is registered — the piece that actually puts PLECTO-E0001 in front of a newcomer whose filter failed the signature gate (ADR 000065 decision 5).
manifest_json_schema
The manifest’s JSON Schema (ADR 000049), derived from the very serde model from_toml parses with — the schema cannot drift from the structs, and deny_unknown_fields surfaces as additionalProperties: false, so editor validation rejects exactly what validate rejects. draft-07 output: the level taplo / Even Better TOML consume (schemars’ 2020-12 default is outside taplo’s documented support). Serialising a just-generated schema cannot fail, but if it ever did, the error surfaces to the CLI caller — a silent "{}" fallback would be a schema that validates EVERYTHING, fail-open for the editor validation this exists to provide.
normalize_path
Normalize a request target’s PATH for routing and forwarding so the proxy and the origin agree on it (CWE-22 Path Traversal / CWE-436 Interpretation Conflict). Per-route filter chains are an access-control boundary, so route selection IS access control: a .. segment that selects a laxer route here but resolves to a stricter path at the upstream would bypass that route’s filters. The fast path normalizes once at ingress and then routes, runs the chain, and forwards on the SAME normalized path, so the origin cannot re-derive a different path.
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.
serve_reloads
Drive control from a ReloadSource: on every trigger, re-read the on-disk manifest and reload. Returns when the source is exhausted. A reload failure (parse / resolve / verify / load) is logged and the current set stays live (fail-closed) — a bad edit never takes the proxy down. An unchanged manifest is a cheap no-op.
validate_manifest
Statically validate manifest — the plecto validate core (the nginx -t shape): every check the server would fail closed on at startup that needs no artifact and mutates nothing. Covers the strict parse (the caller already ran it), [trust] key files, [state] coherence, per-filter metering/rate-limit ranges, duplicate ids, chain and route references, the weighted split, [[tls]] cert/key loads, and [[upstream]] (LB config + [upstream.tls] CA loads). Returns the manifest’s config version (semantic content hash, ADR 000008) on success.
validate_manifest_path
validate_manifest for an on-disk manifest: reads + strictly parses path, resolving relative paths against the manifest’s own directory (the same rule the server applies).