cellos-projector
Project CellOS CloudEvents into current state. Reads the event log, runs
CellStateProjection, emits snapshots, renders audit docs.
What it is
cellos-projector ships three binaries, one library, and zero ambition to
talk to a host. It is a pure consumer of CloudEvents — given a JSONL file
or a live JetStream stream, it folds events into CellStateProjections
(defined in cellos-core) and answers two questions: "what is the current
state of this cell?" and "what does the audit document for this evidence
bundle look like?"
The crate sits at L8 of the layer model — strictly read-side, downstream
of everything else. It consumes the CloudEvent vocabulary owned by
cellos-core, the per-event signing wrapper introduced by
cellos-supervisor (I5), and the signed-trust-keyset envelopes verified
inside cellos-core::verify_signed_trust_keyset_envelope. ADR-0011
(HTTP control plane) explicitly delegates the replay-and-project work to
this crate, and ADR-0014 (formation CloudEvent state model) is the
contract for the formation aggregations.
What cellos-projector deliberately does NOT do:
- It does not write to the event log. Every binary is read-only.
- It does not run cells, admit specs, or mutate authority. The state model it produces is derived — purely a function of the event log.
- It does not depend on
cellos-supervisor. The supervisor's per-event signing wrapper is decoded by a transport-type string constant insrc/main.rs:16, not by linking the producer crate. - It does not bundle
cellos-host-telemetry. The audit-doc renderer reads only the on-wire JSON shape, not upstream typed schemas (src/lib.rs:20).
Public API surface
The library is intentionally small.
audit_doc::render_audit_doc(&serde_json::Value) -> String— pure, deterministic Markdown projection of a singlecell.evidence_bundle.v1.emittedCloudEvent. Returns a single-line Markdown error sentinel rather than panicking on bad input (D11).src/audit_doc.rs:50.audit_doc::EXPECTED_TYPE— the canonical CloudEvent type string the renderer accepts.src/audit_doc.rs:39.audit_doc::ERROR_SENTINEL_PREFIX— stable single-line error marker tests rely on.src/audit_doc.rs:43.event_decode::decode_event(&[u8], &EventVerifierConfig) -> Result<CloudEventV1>— shared decode path used by both the JSONL projector andcellos-state-server. Accepts rawCloudEventV1lines andSignedEventEnvelopeV1wrappers transparently; verifies envelopes when a keyring is configured.src/event_decode.rs:100.event_decode::EventVerifierConfig— verification config sourced from process env.src/event_decode.rs:31.build_info::BUILD_SHA,short_sha,version_line,print_version_if_requested— the--version/-Vcontract every binary in this crate honours.src/build_info.rs:20.
Binaries
cellos-projector— read a JSONL stream, project it, print snapshots. Default: print all snapshots as JSON;--cell-id ID/--spec-id IDselects a single snapshot;--prettyenables pretty-printing.src/main.rs.cellos-state-server— live fleet state projector over NATS JetStream. Replays all events, tails new arrivals, serves current snapshots via HTTP (GET /healthz,GET /cells,GET /cells/<id>,GET /compliance/export).src/bin/cellos-state-server.rs.cellos-audit-justification— SEC-19 fleet audit. Replayscell.compliance.v1.summaryevents from JetStream and reports monoculture / template patterns indnsEgressJustificationstrings. Read-only — does not change admission gates.src/bin/cellos-audit-justification.rs.
Architecture / how it works
┌────────────────────────────────────────┐
│ JSONL file or NATS JetStream stream │
│ (cellos.events.>) │
└────────────────────┬───────────────────┘
▼
┌───────────────────────────────────────┐
│ event_decode::decode_event │
│ - parse CloudEventV1 │
│ - unwrap SignedEventEnvelopeV1 │
│ - verify if keyring configured │
└────────────────────┬──────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ cellos_core::CellStateProjection (one per cell or spec) │
│ apply(event) → ProjectionLifecycleStage, │
│ ProjectionIdentityStage, │
│ ProjectionExportStage, ... │
│ snapshot() → CellStateSnapshot │
└─────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────┐ ┌──────────────────┐
│ cellos-projector (binary) │ │ cellos-state- │
│ JSON / pretty stdout │ │ server (HTTP) │
└─────────────────────────────────┘ └──────────────────┘
┌─────────────────────────────────┐
│ audit_doc::render_audit_doc │
│ Markdown audit page │
└─────────────────────────────────┘
The on-disk projection is keyed by cell:<cellId> or spec:<specId>
(src/main.rs:233), preserving correlation when events carry only one
of the two. Lines that fail to parse abort the run with a line-numbered
error; signed envelopes that fail verification are fatal when any
verifier keyring is configured (the producer-opt-in / consumer-opt-in
pair from src/main.rs:152).
The audit-doc renderer (src/audit_doc.rs) writes six sections — header,
lifecycle table, host-probe series summary, guest-event sample, residue
class footer, integrity attestations — in a deterministic order. The
output is byte-identical for byte-identical input, so it round-trips
cleanly through git.
Configuration
cellos-projector (JSONL binary)
| Env var | Effect |
|---|---|
CELLOS_EVENT_VERIFY_KEYS_PATH |
JSON keyring (kid → base64url Ed25519 pubkey). Empty/unset → unwrap signed envelopes without verifying. src/main.rs:182. |
CELLOS_EVENT_VERIFY_HMAC_KEYS_PATH |
HMAC-SHA256 keyring. Same shape; value is base64url raw bytes. src/main.rs:198. |
Argv: cellos-projector <events.jsonl> [--cell-id ID | --spec-id ID] [--pretty]. Also: --version / -V, --help / -h.
cellos-state-server
| Env var | Default | Effect |
|---|---|---|
CELLOS_NATS_URL |
nats://localhost:4222 |
NATS server URL. |
CELLOS_NATS_STREAM |
CELLOS |
JetStream stream name. |
CELLOS_NATS_CONSUMER |
cellos-state |
Durable consumer name. |
CELLOS_EVENT_SUBJECT |
cellos.events.> |
Subject filter. |
CELLOS_STATE_ADDR |
0.0.0.0:8080 |
HTTP listen address. |
CELLOS_EVENT_VERIFY_KEYS_PATH |
unset | Same as the JSONL binary; envelope verification keys. |
CELLOS_EVENT_REQUIRE_SIGNED |
unset | When 1/true/yes/on, reject raw CloudEventV1 lines. src/event_decode.rs:1. |
cellos-audit-justification
| Env var | Default | Effect |
|---|---|---|
CELLOS_NATS_URL |
nats://localhost:4222 |
NATS server URL. |
CELLOS_NATS_STREAM |
CELLOS_EVENTS |
JetStream stream name. |
CELLOS_EVENT_SUBJECT |
cellos.events.> |
Subject filter. |
CELLOS_AUDIT_MONOCULTURE_THRESHOLD |
5 |
Min distinct cells per justification to flag. |
CELLOS_AUDIT_DRAIN_TIMEOUT_MS |
2000 |
Per-message timeout to detect stream drain. |
Stdout: JSON report. Stderr: human summary plus structured logs.
Examples
Project a JSONL stream and print every snapshot pretty:
Render an audit document from a single evidence-bundle event:
use ;
use json;
let event = json!;
let markdown = render_audit_doc;
println!;
Replay JetStream into live cell snapshots:
CELLOS_NATS_URL=nats://127.0.0.1:4222 \
CELLOS_NATS_STREAM=CELLOS \
CELLOS_STATE_ADDR=127.0.0.1:9090 \
# in another shell
Audit DNS-egress justification entropy across a fleet:
CELLOS_NATS_URL=nats://127.0.0.1:4222 \
CELLOS_NATS_STREAM=CELLOS_EVENTS \
CELLOS_AUDIT_MONOCULTURE_THRESHOLD=10 \
Testing
Tests under crates/cellos-projector/tests/ exercise the pure paths:
audit_doc_round_trip.rs— pin the byte-deterministic Markdown output against a golden file.i5_sign_emit_to_projector_e2e.rs— end-to-end signing path: the supervisor'sSigningEventSinkemits a wrapped envelope, the projector decodes and verifies it.signed_envelope_round_trip.rs—decode_eventagainst both raw and wrapped lines.smoke.rs— projection key resolution and snapshot shape.
The two NATS-backed binaries (cellos-state-server,
cellos-audit-justification) are smoke-checked in workspace integration
tests; running them locally needs nats-server -js on the configured
URL.
Related crates
cellos-core— ownsCellStateProjection,CellStateSnapshot,CloudEventV1,SignedEventEnvelopeV1, the event-decoding primitives.cellos-supervisor— producer of the wrappeddev.cellos.events.signed_envelope.v1transport this crate unwraps.cellos-server— the online HTTP API that mirrors whatcellos-state-serverdoes over NATS directly.