cellos-export-local 0.5.1

Local-filesystem ExportSink for CellOS — writes per-cell evidence bundles to an operator-configured directory.
Documentation
# cellos-export-local

`ExportSink` that copies each declared evidence artifact into a local
directory tree.

## What it is

Implements `cellos_core::ports::ExportSink`. For each pushed artifact,
the sink writes to `<root>/<cell_id>/<safe-name>`, creating directories
as needed, and returns an `ExportReceipt` with `targetKind = LocalPath`.

Selected in `cellos-supervisor::composition::build_export_sinks` as the
default sink when `CELLOS_EXPORT_DIR=<path>` is set and no HTTP /
S3 sink is configured for the default target.

What it does NOT do:

- It does not upload anywhere — purely filesystem `cp`.
- It does not retry, hash, or sign — those are concerns of the
  evidence-bundle layer upstream.
- It does not let artifact names escape the cell-export directory.
  The sink rejects empty names, absolute paths, path separators
  (`/` or `\\`), NUL bytes, any `..` traversal segment, and a leading
  `~`. The `cell_id` itself is required to be a single path segment.
  Contract validation upstream (`is_portable_identifier`) already
  rejects most of these; this sink re-checks raw names as defense-in-
  depth for direct callers of `push_with_len` (tests, adapters).
- On Linux it opens the destination with `O_NOFOLLOW` so a tampered
  cell directory cannot redirect writes via symlink.

## Public API surface

| Symbol | Purpose |
|---|---|
| `LocalExportSink` | The sink. |
| `LocalExportSink::new(root, cell_id)` | Construct; rejects unsafe `cell_id`. |
| `LocalExportSink::push_with_len(name, src)` | Lower-level helper that returns bytes written; primarily for tests / metrics. |

Source: [`src/lib.rs`](src/lib.rs).

## Configuration

| Env var | Description |
|---|---|
| `CELLOS_EXPORT_DIR` | Root directory for local exports. When set (and no HTTP base URL is set), the default sink is `LocalExportSink::new($CELLOS_EXPORT_DIR, $cell_id)`. |

The `cell_id` segment comes from `spec.id`.

## Examples

```bash
mkdir -p /var/lib/cellos/exports
export CELLOS_EXPORT_DIR=/var/lib/cellos/exports
cellos-supervisor --spec cell.yaml
# After the cell completes:
ls /var/lib/cellos/exports/<spec.id>/
```

## Testing

```bash
cargo test -p cellos-export-local
```

Tests use `tempfile` so no shared filesystem state is required.

## Related crates

- `cellos-export-s3` — S3 presigned PUT.
- `cellos-export-http` — generic HTTP PUT.
- `cellos-supervisor` — selects this sink in `build_export_sinks`
  when `CELLOS_EXPORT_DIR` is set.
- `cellos-core` — defines the `ExportSink` port and `ExportReceipt`.

## ADRs

- ADR-0006 — the evidence bundle is the 1.0 deliverable; `LocalExportSink`
  is the dev / single-host path for honouring it.