# Replay — gps-denied-replay-cli Dockerfile + GitHub Actions matrix + SBOM diff **Task**: AZ-403_replay_dockerfile_ci **Name**: `gps-denied-replay-cli` Dockerfile + GitHub Actions matrix entry + SBOM diff (excludes C6/C10/C11/C12) **Description**: Add the fourth Docker image `gps-denied-replay-cli`: multi-stage build (Python + C1–C5 + cpp/* + replay strategies; NO C6/C10/C11/C12; NO HTTP server). Add a GitHub Actions matrix entry building and pushing this image alongside the existing 3 images (live / research / operator). Add an **SBOM diff CI step** that builds the SBOM (via `syft` or the project's existing SBOM tooling), parses it, and asserts the absence of `c6_tile_cache`, `c10_provisioning`, `c11_tilemanager`, `c12_operator_orchestrator` packages — verifies AC-4 of the epic. The SBOM diff fails the CI job if any excluded component leaks into the replay image. Image base: same Python + CUDA base as the live image (consistency with TensorRT engines from C7) but with `BUILD_C6=OFF`, `BUILD_C10=OFF`, `BUILD_C11=OFF`, `BUILD_C12=OFF`, `BUILD_VIDEO_FILE_FRAME_SOURCE=ON`, `BUILD_TLOG_REPLAY_ADAPTER=ON`, `BUILD_REPLAY_SINK_JSONL=ON` build args. **Complexity**: 3 points **Dependencies**: AZ-402 (CLI entrypoint registered in pyproject); AZ-398 / AZ-399 / AZ-400 / AZ-401 (replay strategies); existing Dockerfile + CI plumbing for the live image (pattern to mirror); `module-layout.md` build-flag table; AZ-263, AZ-269, AZ-266 **Component**: replay-cicd (epic AZ-265 / E-DEMO-REPLAY) — Dockerfile at `docker/replay-cli/Dockerfile`; CI at `.github/workflows/build-images.yml` (or equivalent); SBOM-diff script at `ci/sbom_diff_replay.py` **Tracker**: AZ-403 **Epic**: AZ-265 (E-DEMO-REPLAY) ### Document Dependencies - `_docs/02_document/contracts/replay/replay_protocol.md` — replay binary scope (NO C6/C10/C11/C12). - `_docs/02_document/architecture.md` — § 5 binary topology; build-flag matrix. - `_docs/02_document/module-layout.md` — Build-Time Exclusion Map for the new BUILD_* flags. ## Problem Without this task, the replay binary cannot ship — there's no CI matrix entry to build the image, no Dockerfile, no SBOM verification that the binary is actually free of operator-side components. AC-4 (SBOM diff verification) is a gating item. ## Outcome - `docker/replay-cli/Dockerfile`: - Multi-stage: builder stage (compiles cpp/*) + runtime stage (Python + C1–C5 + replay strategies). - Build-args: `BUILD_C6=OFF BUILD_C10=OFF BUILD_C11=OFF BUILD_C12=OFF BUILD_VIDEO_FILE_FRAME_SOURCE=ON BUILD_TLOG_REPLAY_ADAPTER=ON BUILD_REPLAY_SINK_JSONL=ON`. - Entrypoint: `gps-denied-replay`. - No HTTP server (no exposed ports; CLI only). - `.github/workflows/build-images.yml` matrix entry for `replay-cli` (image tag, build args, push to registry). - `ci/sbom_diff_replay.py` — generates the SBOM via `syft packages dir:./ -o spdx-json` (or equivalent) on the built image, parses it, asserts the absence of `c6_tile_cache`, `c10_provisioning`, `c11_tilemanager`, `c12_operator_orchestrator` Python packages. Exit 0 on clean SBOM; exit 1 on leak (with the leaking package name printed). - CI step `replay-cli-sbom-diff` invokes the script after the image build; fails the job on script exit 1. - Documentation: `docker/replay-cli/README.md` documents the image scope + build-args. - Unit / smoke tests: `docker buildx build` of the Dockerfile succeeds locally; SBOM-diff script runs against a pre-built test image fixture. ## Scope ### Included - Dockerfile. - GitHub Actions matrix entry. - SBOM-diff script + CI step. - README for the image. - Local smoke tests. ### Excluded - Image push credentials / registry config — assumed inherited from the existing CI infrastructure. - E2E replay fixture test — owned by E2E task. ## Acceptance Criteria **AC-1: Dockerfile builds locally** — `docker buildx build -f docker/replay-cli/Dockerfile .` succeeds; final image exists and `docker run --rm gps-denied-replay --help` prints the argparse usage. **AC-2: Image scope: C1–C5 present** — `docker run --rm python -c "import gps_denied_onboard.components.c1_vio; import gps_denied_onboard.components.c2_vpr; import gps_denied_onboard.components.c2_5_rerank; import gps_denied_onboard.components.c3_matcher; import gps_denied_onboard.components.c3_5_adhop; import gps_denied_onboard.components.c4_pose; import gps_denied_onboard.components.c5_state; import gps_denied_onboard.components.c8_fc_adapter"` exits 0. **AC-3: Image scope: NO C6/C10/C11/C12** — `docker run --rm python -c "import gps_denied_onboard.components.c6_tile_cache"` exits non-zero (ImportError); same for c10, c11, c12. **AC-4: SBOM-diff script passes on a clean image** — script run against the built image exits 0. **AC-5: SBOM-diff script fails on a polluted image** — synthetic test where the image is rebuilt with `BUILD_C6=ON`; script exits 1 + prints `LEAK: c6_tile_cache present in SBOM`. **AC-6: GitHub Actions matrix entry includes replay-cli** — `.github/workflows/build-images.yml` includes a matrix entry building+pushing `replay-cli`. Verify by syntax-checking the YAML + visual review. **AC-7: NO HTTP server** — image inspection: `docker inspect ` shows NO exposed ports (`ExposedPorts: null`). `docker run --rm ss -tlnp` (after a 5 s sleep) shows no listening sockets. **AC-8: Image size sanity** — replay-cli image size ≤ 1.5× live-image size (replay re-uses live's CUDA + GTSAM + opencv layers). If exceeded, investigate. **AC-9: README accuracy** — `docker/replay-cli/README.md` documents the entrypoint command, the volume mounts (e.g., `-v /host/data:/data`), and the build-args. **AC-10: SBOM-diff script standalone testable** — invoke `python ci/sbom_diff_replay.py --sbom test-fixtures/clean-sbom.json` returns 0; with `polluted-sbom.json` returns 1. ## Non-Functional Requirements - Image build p99 ≤ 10 min on Tier-1 CI hardware (mirrors live image). - SBOM-diff script p99 ≤ 30 s. ## Constraints - Re-use existing Dockerfile patterns (stage names, base images, layer ordering) for cache locality. - `syft` (or equivalent) is the SBOM tool; pinned version in CI. - The SBOM-diff script does NOT modify the image; read-only inspection. ## Risks & Mitigation - **Risk: SBOM-diff false-positives if a dep transitively pulls in c6_tile_cache** — *Mitigation*: AC-5 fails fast; in practice, components do not depend on each other so transitive pull-in is impossible. - **Risk: Image bloat from copying cpp/* libs that aren't needed** — *Mitigation*: build-time exclusion in the cmake config (per `module-layout.md`); review image layer size in AC-8. - **Risk: CI matrix YAML drift breaks all 4 image builds** — *Mitigation*: matrix entry follows the same shape as the existing 3 entries; visual review in PR. ## Runtime Completeness - **Named capability**: replay-cli Docker image + CI build + SBOM verification. - **Production code**: real Dockerfile, real CI matrix entry, real SBOM-diff script. - **Unacceptable substitutes**: skipping the SBOM diff (defeats AC-4 of the epic — the binary scope cannot be verified). ## Contract Operationalises `_docs/02_document/contracts/replay/replay_protocol.md` — replay binary scope (NO C6/C10/C11/C12) + epic AC-4 SBOM diff.