cellos-host-cellos 0.5.0

Recursive CellOS-in-CellOS backend — runs CellOS cells as nested supervisors. Used for federated and self-hosting topologies.
Documentation

cellos-host-cellos

The default proprietary CellOS L2 host backend — a simulated host runtime today, with FD-hygiene primitives ready for the day the real kernel/userspace ABI lands.

What it is

cellos-host-cellos is the supervisor's default [CellBackend] implementation. It is selected whenever CELLOS_CELL_BACKEND is unset or matches no known enum value (crates/cellos-supervisor/src/composition.rs:1113-1138), so every out-of-the-box run of cellos-supervisor uses it. It also provides the default [MemorySecretBroker] used by the same composition root when CELLOS_BROKER is unset (crates/cellos-supervisor/src/composition.rs:1158).

The crate has two surfaces. The first is the simulated host backend: [ProprietaryCellBackend] (src/lib.rs:251) tracks live cells in an Arc<Mutex<HashMap<String, CellRecord>>> (src/lib.rs:251-253), optionally creates a cgroup-v2 leaf at create() when CELLOS_CGROUP_PARENT is set (src/lib.rs:281-301), and removes all host-side state at destroy() so residue tests can falsify leaks (src/lib.rs:344-372). When the real kernel/userspace ABI exists, the inner implementation can be replaced with FFI / IPC while the same CellBackend contract holds on the outside.

The second is the FD-hygiene primitive: [spawn_isolated_workload] (src/lib.rs:134) is the only sanctioned way for this backend to materialise a child process. It enforces three invariants on Unix (src/lib.rs:7-23):

  1. No ambient env. env_clear() plus exactly the declared injection set — nothing inherited from the supervisor.
  2. Stdio replaced with pipes. stdin / stdout / stderr are wired to fresh pipes whose parent ends drop before the caller can write or read, so a child cannot reach back into the supervisor's terminal or log streams.
  3. All FDs > 2 closed on exec. A pre_exec closure walks /proc/self/fd and sets FD_CLOEXEC on every fd > 2. The kernel atomically closes those at execve(2) so a workload never inherits NATS sockets, broker handles, or audit-log writers. There is a RLIMIT_NOFILE-bounded fallback for kernels without /proc (src/lib.rs:188-210).

What it deliberately does not do:

  • It does not spawn microVMs. The Firecracker path lives in cellos-host-firecracker.
  • It does not own nftables enforcement. CellHandle.nft_rules_applied is always None from this backend (src/lib.rs:334-336); the supervisor's host-subprocess path surfaces the egress signal.
  • It does not implement boot-artifact manifest digests (kernel_digest_sha256, rootfs_digest_sha256, firecracker_digest_sha256 are all None, src/lib.rs:338-340).

Public API surface

Item Where
pub struct ProprietaryCellBackend src/lib.rs:251
impl ProprietaryCellBackend { pub fn new() } src/lib.rs:262
async fn tracked_cell_count(&self) -> usize src/lib.rs:269
async fn has_tracked_state(&self, cell_id: &str) -> bool src/lib.rs:274
impl CellBackend for ProprietaryCellBackend src/lib.rs:304
pub struct WorkloadEnv src/lib.rs:48
WorkloadEnv::{new, push, iter, len, is_empty} src/lib.rs:52-93
pub struct SpawnedWorkload (Unix) src/lib.rs:97
SpawnedWorkload::{pid, wait, kill} (Unix) src/lib.rs:101-117
pub fn spawn_isolated_workload(argv: &[String], env: &WorkloadEnv) -> Result<SpawnedWorkload, CellosError> (Unix) src/lib.rs:134
pub fn spawn_isolated_workload(_argv, _env) -> Result<(), CellosError> (non-Unix stub) src/lib.rs:234
pub use memory_broker::MemorySecretBroker src/lib.rs:27, src/memory_broker.rs:20

WorkloadEnv::push rejects keys that are empty, contain =, or contain NUL; values are rejected when they contain NUL (src/lib.rs:58-79). spawn_isolated_workload rejects an empty argv (src/lib.rs:141-145). Non-Unix targets return CellosError::Host("spawn_isolated_workload: host subprocess spawn is Unix-only") (src/lib.rs:234-238).

ProprietaryCellBackend::create() rejects an empty spec.id (src/lib.rs:307-308) and rejects a duplicate live cell_id (src/lib.rs:312-315) — no two live cells ever share an id. On Linux, when CELLOS_CGROUP_PARENT is set and writable, a per-cell leaf is created at <parent>/cellos_<sanitized-id>_<uuid> and stored on the returned CellHandle.cgroup_path (src/lib.rs:289-300). destroy() rejects an unknown / already-torn-down id with CellosError::Host (src/lib.rs:347-351) and reports peers_tracked_after as the remaining live-cell count.

MemorySecretBroker (src/memory_broker.rs:20) implements cellos_core::ports::SecretBroker with TTL-bound resolve() storing the simulated value in zeroize::Zeroizing<String>, plus revoke_for_cell() which drops every row keyed under cell_id (src/memory_broker.rs:44-73). Tests / operators observe state via materialized_keys_for_cell() and total_materialized_rows() (src/memory_broker.rs:29-42).

Architecture / how it works

The backend is a thin in-memory map plus the FD-hygiene spawn helper. There is no thread pool, no tokio::process::Child tracking, no background reaper — every method is async fn only because the CellBackend trait requires it. The map is the source of truth for "which cells does this host think are live"; the supervisor calls destroy() at end-of-cell and the entry vanishes, including its cgroup leaf if one exists (src/lib.rs:355-364).

spawn_isolated_workload is structurally separate from CellBackend::create. The backend's create() does not spawn a child itself; the supervisor's host-subprocess path (cellos-supervisor::supervisor::run_cell_command) calls spawn_isolated_workload with the argv and the declared env injection set, and the backend only tracks the existence of the cell. This matches the L2/L3 split in the layer model: L2 owns the isolation primitive (FD hygiene + cgroup leaf), L3 owns the lifecycle (spawn + reap + emit events).

When the proprietary kernel ABI lands, replace the spawn helper's Command-based pre_exec wiring with the equivalent FFI/IPC call without changing the WorkloadEnv / SpawnedWorkload shape the supervisor depends on. The crate-level doc-comment calls this out explicitly (src/lib.rs:1-6).

Configuration

Env var Default Effect
CELLOS_CELL_BACKEND proprietary (this backend) Set to anything else (firecracker, gvisor, stub) to bypass this backend; unset or unknown values land here. Resolved at crates/cellos-supervisor/src/composition.rs:1029.
CELLOS_BROKER MemorySecretBroker (this crate's broker) Same pattern: unset selects the broker exported from this crate; set to env / file / github-oidc / vault-approle to override (crates/cellos-supervisor/src/composition.rs:1146-1158).
CELLOS_CGROUP_PARENT unset → no cgroup leaf Linux only. When set and writable, every create() makes <parent>/cellos_<sanitized-id>_<uuid> and stores it on the returned handle; destroy() best-effort remove_dir of the leaf, logging warnings on failure (src/lib.rs:282, src/lib.rs:355-364).

No from_env() / FirecrackerConfig-style struct. The backend is constructed with ProprietaryCellBackend::new() and reads env vars lazily.

Examples

use std::sync::Arc;
use cellos_core::ports::{CellBackend, SecretBroker};
use cellos_host_cellos::{MemorySecretBroker, ProprietaryCellBackend};

let backend: Arc<dyn CellBackend> = Arc::new(ProprietaryCellBackend::new());
let broker:  Arc<dyn SecretBroker> = Arc::new(MemorySecretBroker::new());
// The supervisor composition root wires both into the cell lifecycle.

FD-hygiene-bounded spawn (Unix only):

use cellos_host_cellos::{spawn_isolated_workload, WorkloadEnv};

let mut env = WorkloadEnv::new();
env.push("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin")?;
env.push("CELL_ID", "cell-42")?;

let argv = vec!["/usr/bin/env".to_string()];
let mut child = spawn_isolated_workload(&argv, &env)?;
let status = child.wait()?;
assert!(status.success());
# Ok::<(), cellos_core::CellosError>(())

Testing

cargo test -p cellos-host-cellos

Integration tests under tests/:

File Scope
smoke.rs Construction + happy-path create/destroy through the trait.
cellos_host_cellos_happy_path.rs End-to-end happy path.
contract.rs HCELLOS-COV — pins the observable contract against the CellBackend trait so the proprietary backend does not drift from cellos-host-stub / cellos-host-firecracker.
fd_isolation.rs Reads /proc/<child>/environ of a real child and asserts byte-for-byte equality with the declared injection set, validating invariants 1–3 of spawn_isolated_workload (Linux-only).

In-source unit tests under src/lib.rs:375-433 cover destroy-removes-state, double-destroy-errors, and peers_tracked_after accounting. The memory broker's residue contract is in src/memory_broker.rs:80-90.

Related crates

  • cellos-core — defines CellBackend, SecretBroker, CellHandle, TeardownReport, SecretView, and CellosError.
  • cellos-host-stub — the no-op alternative used in unit tests where this backend's tracking-map behaviour matters but the spawn helper is overkill.
  • cellos-host-firecracker — the microVM backend selected with CELLOS_CELL_BACKEND=firecracker.
  • cellos-host-gvisor — the runsc backend selected with CELLOS_CELL_BACKEND=gvisor.
  • cellos-supervisor — calls create() / destroy() on this backend by default and invokes spawn_isolated_workload from run_cell_command.

ADRs