cellos-cortex 0.1.0

Bridge between CellOS execution cells and the Cortex doctrine layer — DoctrineAuthorityPolicy, CortexCellRunner, CellosLedgerEmitter.
docs.rs failed to build cellos-cortex-0.1.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

cellos-cortex

The CellOS ↔ Cortex bridge. The only crate that owns wire shapes spanning both systems.

What it is

Two directions of flow cross this crate, and only this crate:

  • Cortex → CellOS: Cortex hands a bounded [ContextPack] (memory digest + doctrine refs + task description + optional expiry) to [CortexCellRunner], which translates it into a [cellos_core::types::ExecutionCellDocument] and submits it through a [CellSubmitter] (typically backed by cellos-supervisor).
  • CellOS → Cortex: every CellOS lifecycle [cellos_core::types::CloudEventV1] is serialized into a Cortex-shaped ledger row by [CellosLedgerEmitter], which implements [cellos_core::ports::EventSink].

The bridge sits at L6 of the layer model, beside cellos-supervisor. The discipline (ADR-0008) is hard: neither cellos-* nor cortex-* imports the other directly. This crate owns the simplified ContextPack wire shape (intentionally flatter than Cortex's internal cortex_context::ContextPack) and the CortexLedgerRow ingest shape; either side may evolve internal types without breaking the bridge.

What cellos-cortex deliberately does NOT do:

  • It does not depend on cortex-* crates. Cortex ContextPack JSON is parsed via ContextPack::from_cortex_json rather than by linking the upstream type (src/context.rs:23).
  • It does not depend on CellOS supervisor internals beyond what lib.rs exposes — submissions go through the CellSubmitter trait, not a direct method call.
  • It does not own Cortex's signed canonical ledger format. This crate emits the ingest row (CortexLedgerRow); Cortex anchors and re-signs on receipt.
  • It does not perform agent reasoning. The pack's task is appended to the cell's argv and the agent inside the cell does the work.

Public API surface

The full re-export list lives in src/lib.rs:30. By module:

context

  • ContextPack — the bridge-shaped pack (memory digest, doctrine refs, task, optional expiry). src/context.rs:48.
  • ContextPack::new(task) — convenience constructor for tests.
  • ContextPack::from_cortex_json(&[u8]) — parse a real Cortex wire-form pack and project to the bridge shape (see source TODO for the compatibility-gap rationale).
  • ContextPack::is_expired(now_ms)expires_at comparator used by the runner to refuse stale packs.

runner

  • CortexCellRunner — the dispatcher. src/runner.rs:123.
  • CellSubmitter — the abstract submission trait. src/runner.rs:113.
  • CellSubmissionOutcome — the submitter's return value: cell id, optional exit code, captured lifecycle events. src/runner.rs:90.
  • CortexCellResult — the bridge-shaped result of a completed cell run (exit code, success, destroyed-at, export paths, doctrine refs propagated from the dispatched pack). src/runner.rs:77.
  • ContextPackTranslation — recorded pack → spec translation, useful for audit. src/runner.rs:105.
  • wait_for_result_from_jsonl(cell_id, jsonl_path, timeout) — free-function result-reception helper that tails the supervisor's JSONL stream and builds a CortexCellResult. src/runner.rs:415.
  • CELL_OS_JSONL_EVENTS_ENV — env-var name (CELL_OS_JSONL_EVENTS) the helper reads. src/runner.rs:55.

ledger

  • CellosLedgerEmitter — the EventSink adapter that turns CloudEvents into ledger rows. src/ledger.rs:171.
  • CellosLedgerEmitter::new(sink) — unsigned legacy path.
  • CellosLedgerEmitter::with_signing_key(sink, Option<SigningKey>) — explicit Ed25519 signing key.
  • CellosLedgerEmitter::with_env_signing(sink) — read the Ed25519 seed from CELLOS_CORTEX_LEDGER_SIGNING_KEY_BASE64. Pair with the free associated function CellosLedgerEmitter::from_env_signing_key() if you want the raw Option<SigningKey>.
  • LedgerSink — pluggable destination trait. src/ledger.rs:125.
  • NdjsonLedgerSink — append-NDJSON file sink. src/ledger.rs:132.
  • CortexLedgerRow — the Cortex-shaped ingest row. src/ledger.rs:70.
  • EmittedLedgerEntry — wire shape with optional detached signature. src/ledger.rs:110.
  • LEDGER_SIGNING_KEY_ENV"CELLOS_CORTEX_LEDGER_SIGNING_KEY_BASE64". src/ledger.rs:178.
  • ledger::http_sink::HttpLedgerSink — HTTP transport, gated behind the http-ledger cargo feature. src/ledger.rs:277.

policy

  • DoctrineAuthorityRule — the per-doctrine constraint (max_ttl_seconds, require_secret_delivery, forbid_egress, require_egress_justification, correlation_label). src/policy.rs:47.
  • DoctrineAuthorityPolicy — the doctrine id → rule table. src/policy.rs:88.
  • DoctrineAuthorityPolicy::empty() — no-op policy.
  • DoctrineAuthorityPolicy::built_in() — the canonical ADR-0009 defaults.
  • DoctrineAuthorityPolicy::load_from_env() / load_from_path(&Path) — merge operator overrides on top of the built-ins (per-id keys win, missing keys fall back).
  • apply_policy(policy, pack, spec) — apply every doctrine rule that matches the pack's doctrine_refs. src/policy.rs:238.

Architecture / how it works

       ┌────────────────────────────────────────────────────────────┐
       │                   Cortex (separate workspace)              │
       │                                                            │
       │   ContextPack (rich) ── reduce to wire JSON ─►             │
       └───────────────────────────────────┬────────────────────────┘
                                           ▼
            ┌────────────────────────────────────────────────────┐
            │ cellos-cortex (this crate)                         │
            │                                                    │
            │   ContextPack::from_cortex_json                    │
            │           │                                        │
            │           ▼                                        │
            │   CortexCellRunner.translate(pack)                 │
            │     1. structural: task → argv, expires_at → TTL    │
            │     2. apply_policy(DoctrineAuthorityPolicy, ...)   │
            │     3. ExecutionCellDocument                       │
            │           │                                        │
            │           ▼                                        │
            │   CellSubmitter.submit(document)  ─────────────────►── cellos-supervisor
            │           │                                        │
            │           ▼                                        │
            │   cloud_event_v1_cortex_dispatched → event_sink    │
            │                                                    │
            │                                                    │
            │   CellosLedgerEmitter (EventSink)                  │
            │     CloudEventV1 → CortexLedgerRow                 │
            │            → optional Ed25519 sign                 │
            │            → LedgerSink::append                    │
            │                                                    │
            └───────────┬────────────────────────────────────────┘
                        ▼
              NdjsonLedgerSink | HttpLedgerSink | (operator-supplied)

Doctrine policies are monotonic toward least authority (src/policy.rs:13):

  • max_ttl_seconds only clamps TTL down.
  • require_secret_delivery only strengthens secret delivery; a broker mode is never downgraded to Env.
  • forbid_egress: true empties authority.egressRules.
  • require_egress_justification: true strips rules whose dnsEgressJustification is missing.
  • correlation_label records which doctrine rule fired in spec.correlation.labels.

Rules that would raise authority are silently dropped (ADR-0009 §Consequences).

Signed ledger entries (session 14 doctrine, src/ledger.rs:17): when constructed with a signing key, each emitted EmittedLedgerEntry carries cellos_sig — a base64 (URL-safe, no-pad) detached Ed25519 signature over the canonical JSON bytes of the inner event. Cortex verifies entries against the operator-pinned verifying key; unsigned entries preserve the legacy wire shape exactly (cellos_sig omitted via #[serde(skip_serializing_if = "Option::is_none")]).

Configuration

Env var Effect
CELLOS_CORTEX_LEDGER_SIGNING_KEY_BASE64 URL-safe, no-pad base64 of the 32-byte Ed25519 seed used to sign ledger entries. Read by CellosLedgerEmitter::with_env_signing (the constructor) and CellosLedgerEmitter::from_env_signing_key (the raw-key helper). Empty/unset → unsigned. src/ledger.rs:178.
CELLOS_CORTEX_POLICY_PATH Path to a JSON file holding a DoctrineAuthorityPolicy. Merged on top of built_in(). Read by DoctrineAuthorityPolicy::load_from_env. src/policy.rs:204.
CELL_OS_JSONL_EVENTS JSONL CloudEvent stream wait_for_result_from_jsonl tails to build CortexCellResult. src/runner.rs:55.
CORTEX_LEDGER_ENDPOINT HTTP endpoint for http_sink::HttpLedgerSink::from_env (feature http-ledger). src/ledger.rs:13.

Cargo features

Feature Effect
http-ledger Pull in reqwest and build http_sink::HttpLedgerSink.

Examples

Build a runner that dispatches Cortex packs to an in-process submitter (typically a cellos-supervisor adapter), with the built-in doctrine policy:

use std::sync::Arc;
use cellos_cortex::{
    CellSubmitter, ContextPack, CortexCellRunner,
    CellosLedgerEmitter, NdjsonLedgerSink,
    DoctrineAuthorityPolicy,
};

// Sink for CellOS → Cortex ledger rows.
let sink    = Arc::new(NdjsonLedgerSink::new("/var/log/cellos-cortex.ndjson"));
let emitter = Arc::new(
    CellosLedgerEmitter::with_env_signing(sink).expect("signing key parse"),
);

// `submitter: Arc<dyn CellSubmitter>` is built at the composition root —
// in tests we use an in-process fake; in production it wraps the supervisor.
let submitter: Arc<dyn CellSubmitter> = /* ... */;

let runner = CortexCellRunner::new(
        submitter,
        vec!["/usr/local/bin/cortex-agent".into()],
    )
    .with_default_ttl_seconds(600)
    .with_policy(DoctrineAuthorityPolicy::load_from_env().expect("load policy"))
    .with_event_sink(emitter);

// Dispatch a pack:
let pack = ContextPack::new("review pull-request #42");
let outcome = runner.dispatch(&pack).await?;
println!("dispatched cell {}", outcome.cell_id);

Verify a signed ledger entry on the Cortex side (see tests::verify_signature_roundtrip for a worked example).

Testing

cargo test -p cellos-cortex

Tests under crates/cellos-cortex/tests/ exercise the bridge end-to-end: pack → spec translation, policy application, ledger signature round-trip, JSONL result reception. No live broker or network required.

The http-ledger feature is gated by default; to exercise it:

cargo test -p cellos-cortex --features http-ledger

Related crates

  • cellos-core — owns CloudEventV1, ExecutionCellDocument, EventSink, the lifecycle event builders.
  • cellos-supervisor — the typical CellSubmitter backend (linked here but not the other way).
  • cellos-sink-jsonl — pairs naturally with wait_for_result_from_jsonl on the receiver side.

ADRs

  • ADR-0008 — this crate's reason for existing; the import-direction discipline.
  • ADR-0009 — doctrine → authority mapping; the contract apply_policy enforces.