# Export AirGap Prep — PREP-EXPORT-AIRGAP-57-001 Status: **Ready for implementation** (2025-11-20) Owners: Exporter Service Guild · Evidence Locker Guild Scope: Portable evidence export mode (air-gap) that reuses EvidenceLocker sealed/portable bundles and packages them for ExportCenter delivery. ## Dependencies (must remain green before coding) - EvidenceLocker packaging contract (sealed + portable): `docs/modules/evidence-locker/bundle-packaging.md`, `docs/airgap/portable-evidence.md`. - Upstream sealed bundle export readiness (56-001) and bootstrap pack alignment (56-002) — inputs are reused verbatim, no re-signing. - Orchestrator/Notifications envelopes for emission events remain pending; not required to start packaging but block notification wiring. ## Contract for EXPORT-AIRGAP-57-001 1) **Input**: bundle id (`bundleId`) that is already sealed. Export service fetches the portable archive `portable-bundle-v1.tgz` via the EvidenceLocker portable endpoint (write-once cache). 2) **Packaging**: create deterministic gzip/tar (`export-portable-bundle-v1.tgz`) with fixed mtime `2025-01-01T00:00:00Z`, PAX headers, and sorted entries: ``` export-portable-bundle-v1.tgz ├── export.json # Export job metadata (bundleId, exportId, tenant, createdAtUtc, rootHash, sourceUri, portableVersion) ├── portable-bundle-v1.tgz # Bit-for-bit copy from EvidenceLocker (no re-signing) ├── checksums.txt # SHA256 for all files (portable bundle included) + Merkle root ├── verify-export.sh # POSIX script: checksum portable bundle, call `stella evidence verify --bundle portable-bundle-v1.tgz` └── README.md # Operator instructions (ingress/egress steps, expected headers, schema links) ``` - Gzip header mtime and tar mtimes are pinned; permissions `0644`; owner/group `0`. - `checksums.txt` lists files in lexical order; first line `root `. - `verify-export.sh` uses only `tar` + `sha256sum`/`shasum`; no network calls. 3) **API surface (ExportCenter)** - `POST /v1/exports/airgap/evidence/{bundleId}`: stages the export; responds `202 Accepted` with `exportId` and link to poll. - `GET /v1/exports/airgap/evidence/{exportId}`: returns status + download link when ready; includes `rootHash`, `portableVersion`, `bundleId`. - `GET /v1/exports/airgap/evidence/{exportId}/download`: `application/gzip`, filename `export-portable-bundle-v1.tgz`, ETag = SHA256. - Auth: `export:read` for GET, `export:write` for POST; support tenant scoping identical to EvidenceLocker. 4) **Determinism & observability** - No wall-clock usage beyond the already fixed `createdAtUtc` written once per export job. - Emit structured log `{exportId,bundleId,portableVersion,rootHash}` on completion. - Metrics: counter `export.airgap.portable.completed`, histogram `export.airgap.portable.duration_ms`, gauge `export.airgap.portable.queue_depth`. 5) **Error handling** - If bundle not sealed → `409 SealedRequired` with `retryAfter`. - If portable artefact missing → trigger fetch from EvidenceLocker; return `202` with `pendingReason=PortableMaterialising`. - Verification failures of copied bundle (hash mismatch) → mark export `FAILED` and keep artefact; require operator acknowledgement. ## Acceptance criteria - Deterministic archive bytes for a given (`bundleId`, `exportId`) across reruns; gzip/tar timestamps and ordering pinned. - Export archive contains the unmodified EvidenceLocker portable bundle and top-level instructions for offline operators. - CLI verification path documented in README and script; succeeds with no network access using current `stella evidence verify`. - Status/Download endpoints documented and cover `202/404/409/500` cases; ETag and `Last-Modified` set. ## Implementation notes for developers - Reuse EvidenceLocker’s `PortableBundleVersion` constant to avoid drift; do not unzip/repack the inner portable archive. - Populate `export.json` using UTC ISO-8601; include `sourceUri` referencing the original EvidenceLocker portable endpoint used. - Store artefacts under object key `exports/{tenant}/{bundleId}/{exportId}/export-portable-bundle-v1.tgz` with write-once semantics. - Mirror logging/metrics naming with 56-001/56-002 to ease dashboard reuse. ## Open items / risks - Notifications/timeline emission remains pending on Wave 150/140 envelope drop; add once schemas land (tracked separately). - If portable bundle version bumps to v2, archive filename and `portableVersion` must be updated in tandem. ## Handoff - This prep artefact is ready to implement in `src/ExportCenter/StellaOps.ExportCenter.WebService` job + `StellaOps.ExportCenter.Worker` for queue processing. - Link back to this document from Sprint 0162 Delivery Tracker entry P4.