# 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`](../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](../../LAYERS.md): 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

```rust
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):

```rust
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`](../cellos-core) — defines `CellBackend`, `SecretBroker`,
  `CellHandle`, `TeardownReport`, `SecretView`, and `CellosError`.
- [`cellos-host-stub`](../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`](../cellos-host-firecracker) — the microVM
  backend selected with `CELLOS_CELL_BACKEND=firecracker`.
- [`cellos-host-gvisor`](../cellos-host-gvisor) — the `runsc` backend
  selected with `CELLOS_CELL_BACKEND=gvisor`.
- [`cellos-supervisor`](../cellos-supervisor) — calls `create()` /
  `destroy()` on this backend by default and invokes
  `spawn_isolated_workload` from `run_cell_command`.

## ADRs

- [ADR-0001 — Rust + NATS/JetStream + proprietary host](../../docs/adr/0001-rust-nats-jetstream-proprietary-host.md)
  — names "the proprietary CellOS host" as the long-horizon path this
  crate is the simulated stand-in for.
- [ADR-0005 — TLS termination design](../../docs/adr/0005-tls-termination-design.md)
  — TLS termination is an egress concern, not a backend concern; this
  crate does not interpose on the cell's TCP stack.
- [ADR-0007 — RBAC + secret-ref admission](../../docs/adr/0007-rbac-secret-ref-admission.md)
  — the broker contract this crate's `MemorySecretBroker` implements
  (TTL-bounded `resolve`, full `revoke_for_cell` on teardown).
