cellos-projector 0.5.0

Projection layer for CellOS — consumes JetStream CloudEvents into in-memory cell/formation state. Used by cellos-server.
Documentation

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 in src/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 single cell.evidence_bundle.v1.emitted CloudEvent. 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 and cellos-state-server. Accepts raw CloudEventV1 lines and SignedEventEnvelopeV1 wrappers 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 / -V contract 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 ID selects a single snapshot; --pretty enables 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. Replays cell.compliance.v1.summary events from JetStream and reports monoculture / template patterns in dnsEgressJustification strings. 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:

cargo run -p cellos-projector --bin cellos-projector -- \
    /tmp/events.jsonl --pretty

Render an audit document from a single evidence-bundle event:

use cellos_projector::audit_doc::{render_audit_doc, EXPECTED_TYPE};
use serde_json::json;

let event = json!({
    "specversion": "1.0",
    "type":        EXPECTED_TYPE,
    "id":          "uuid-1",
    "source":      "//cellos/supervisor",
    "data": {
        "cellId":            "cell-abc",
        "runId":             "run-2026-05-16-001",
        "specId":            "spec-789",
        "specSignatureHash": "sha256:...",
        "lifecycle":         [],
        "hostSeries":        {},
        "guestEvents":       [],
        "residueClass":      "none",
        "attestations":      {},
    }
});

let markdown = render_audit_doc(&event);
println!("{markdown}");

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 \
cargo run -p cellos-projector --bin cellos-state-server
# in another shell
curl http://127.0.0.1:9090/cells

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 \
cargo run -p cellos-projector --bin cellos-audit-justification \
    > /tmp/justification-report.json

Testing

cargo test -p cellos-projector

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's SigningEventSink emits a wrapped envelope, the projector decodes and verifies it.
  • signed_envelope_round_trip.rsdecode_event against 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 — owns CellStateProjection, CellStateSnapshot, CloudEventV1, SignedEventEnvelopeV1, the event-decoding primitives.
  • cellos-supervisor — producer of the wrapped dev.cellos.events.signed_envelope.v1 transport this crate unwraps.
  • cellos-server — the online HTTP API that mirrors what cellos-state-server does over NATS directly.

ADRs

  • ADR-0011 — HTTP control plane; cellos-server delegates the replay/projection contract this crate implements offline.
  • ADR-0014 — formation event model; the CellStateProjection reducer honours it.