Skip to navigation

Architecture

CrateResponsibility
rivet-catalogTyped catalog/bundle models, definition registry, attributes, assets, ownership and receipt validation.
rivet-compilerHCL loading/parsing, isolated module scopes, typed inputs/outputs, YAML/hierarchy/SOPS resolution, expansion, built-in functions and templates, remote package integrity, provenance, relationships, and confined assets.
rivet-engineResource lifecycle, deterministic convergence, reports, intent and refresh journal.
rivet-resourcesExplicitly registered portable definitions, one concrete Linux runtime per resource, and the shared service and package lifecycles over pluggable providers.
rivet-pluginExecutable installation locks and the PluginPolicy that selects them, persistent local RPC, portable plugin contracts, SDK servers, and resource/function/service/action adapters.
rivet-linuxFacts, protected filesystem/process primitives, account databases, host locks, systemd inspection/commands, and state safety.
rivet-processBounded concurrent child-process I/O, deadlines, process-group cancellation, and safe capture policies shared by controller and runner.
rivet-distributionImmutable source publication/indexes, conditional environments, native S3/GCS/local stores, private artifact caching, catalog delivery/preparation, offline metadata, the compile-service protocol and client, and operational configuration loading.
rivet-cliThe rivet binary: commands and composition of definitions, runtimes, facts, cloud acquisition, variable inputs, pinned remote module preparation, and one-shot agents. It does not link the compile server.
rivet-serverThe rivet-server binary: authenticated HTTPS compile service, GCP/mTLS/OIDC verification, allow rules and signed-asset refresh.
rivet-testingManifest and module unit tests run by rivet test.
rivet-transportShared private runner protocol, exact-version compatibility, local/SSH sessions, trusted discovery, transfer, and identity-aware verified bootstrap. No resource policy.

Definitions and runtimes

A definition owns pure portable compilation and validation. A runtime owns host checks, application, verification, and recovery. Generic catalog/compiler/engine code does not enumerate built-in resource types. Each resource directory holds definition.rs and an optional runtime.rs, listed explicitly in resources/mod.rs; package providers use the same split beneath package. A test asserts every directory is registered.

Built-in definitions-only builds support controllers without linking runner contracts. The runtime feature enables resource runtime interfaces; native registration and helpers remain Linux-gated. rivet-linux supplies infrastructure and does not depend on resource registration.

Executable extension boundary

The CLI combines built-ins with locally approved subprocess plugins. Definitions/functions use a portable role; target runtimes use a runtime role. A process is reused for its composition lifetime, with serialized bounded calls and deadlines. Discovery and separate phases can create additional short-lived sessions.

The host retains expression evaluation and annotations, asset acquisition, receipt validation, graph/ownership checks, host locking, durable intent, verification, and refresh delivery. Plugins return portable results and disposable work/verification handles; handles never enter catalogs or journals. Service and package backends supply native primitives to the existing service and package lifecycles.

Catalogs record pinned plugin compilation identities. External resources and service selections bind desired/recovery digests to the implementation. RPC remains local to the controller or runner; SSH still uses the separate runner transport. Installation approval is local policy and cannot come from a bundle.

Input and evaluation boundaries

VariableInputs stores values and sensitivity together, exposes read-only inspection, and has no value-printing Debug. Rust callers use CompilationRequest and compile_configuration_request to combine explicit inputs, inventory facts, and independently authenticated trusted facts with one asset loader. Hierarchy callers first use resolve_configuration_inputs, then pass its opaque inputs() into the request, preserving source-derived sensitivity. Resolution and compilation validate independently; CatalogCompilation::into_bundle requires all bytes before producing an offline bundle.

Function dispatch declares arity, implementation family, and annotation policy in builtin_functions.rs. EvaluationScope distinguishes configuration scope from isolated explicit template bindings; lexical bindings restore after errors. Source roots remain attached to declaring expressions. Functions/templates do not add runner-time evaluation.

Runtime composition_conflicts receives all typed declarations of one runtime explicitly. Static semantics and runtime pairings are validated at run entry. Preflight inspects every resource once; each mutation gate freshly re-inspects only the mutating resource (and its type’s composition policy) against the latest claims of the others, without rerunning planning. Composition safety uses the longest declaration timeout in its group; individual safety inspection uses its declaration timeout. Ordinary planning checks have no implicit catalog/peer access. Shared-file writers retain a verified snapshot; file work keeps resolved intent separate from host observations.

Distribution boundary

Release publication writes indexed plain files and changes no environment. Promotion verifies completeness before a conditional pointer update. Acquisition pins one release per run, and delivery separates desired catalog semantics from transport locations. Preparation verifies all asset bytes before local convergence; the engine does not fetch distribution objects.

rivet-server owns HTTPS, GCP/mTLS/OIDC verification, principal/environment allow rules, and catalog-bound signed-asset refresh. The service passes an independently authenticated TrustedFacts context into compilation; submitted inventory cannot populate it. Library callers supplying CompilationRequest::trusted or ReleaseCompileRequest::trusted must perform authentication themselves. Unused claims and credential lifetime fields do not affect desired-state digests.

The distribution pipeline currently stages immediate root HCL, without module/auxiliary expression-file preparation or YAML/hierarchy inputs. Direct compilation retains those features. Explicit offline runs restore a previously prepared catalog; they do not authenticate a new identity or observe current environment pointers. Cache metadata excludes signed URLs, tokens, and refresh tickets.

Fact collectors

Each built-in fact group is collected by one file in crates/rivet-linux/src/facts/. To add one, add a lowercase .rs file to crates/rivet-linux/src/facts/ and list it in FACT_COLLECTORS in facts/mod.rs (one mod line and one table entry). The filename is the fact key; a unit test fails if a collector file is not listed.

For example, boot_id.rs:

use super::{FactContext, ResourceError};
use serde_json::{Value, json};
pub(super) fn collect_fact(context: &FactContext) -> Result<Option<Value>, ResourceError> {
Ok(context.optional("/proc/sys/kernel/random/boot_id")
.map(|id| json!({"id": id})))
}

Return Ok(None) when the fact is inapplicable, Ok(Some(value)) for any JSON shape, and Err when collection must fail. Use the shared context for file reads so synthetic-host tests can exercise the collector. Keep collector tests inline under #[cfg(test)].

Users can add facts without rebuilding Rivet through custom fact files.

Domain terms

  • Catalog: fully evaluated desired configuration, including relationships and outputs.
  • Bundle: catalog plus verified configuration assets.
  • Source release: immutable index and plain source files identified by the index digest.
  • Environment: named mutable pointer to one complete source release, with a promotion generation.
  • Catalog delivery: manifest, provenance, and inline/external assets to be verified into a bundle before execution.
  • Trusted facts: credential-derived principal, node, and claims supplied independently of node inventory.
  • Fact: captured read-only target/platform/inventory value used at compilation.
  • Observation: current host object state read during planning or application.
  • Resource handle: declared work carrying dependency provenance, not a runtime result.
  • Logical identity: declaration address; physical ownership claim: the host object or entry it manages.
  • Expected change: prediction from current observation; verified change: postcondition-confirmed managed change.
  • Refresh: action requested by a verified change, separate from ordinary convergence.
  • Refresh obligation: durable outstanding causal work that may repeat after interrupted acknowledgement.
  • Shared prerequisite action: coalesced readiness work, such as the systemd definition-reload barrier.
  • Journal: records needed to recover interrupted work, rather than inventory or rollback data.

The repository’s CONTEXT.md is the full domain glossary, and design decisions are recorded in docs/adr/. docs/specification.md is the original design and describes features that don’t exist yet; this site documents what is implemented.

Module entry/completion barriers are catalog graph nodes with no host operation or mutation intent. Module notifications attach durable obligations to descendant resource changes and gate delivery on successful completion. The journal still records resource mutations rather than treating a module as one atomic transaction.