# 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
| `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
| `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.