···11+# MM-66 Human Test Plan
22+33+**Feature:** Docker image derived from Nix build
44+**Implementation plan:** `docs/implementation-plans/2026-03-08-MM-66/`
55+**Generated:** 2026-03-08
66+77+## Prerequisites
88+99+- A Linux system (x86_64-linux or aarch64-linux) with:
1010+ - Nix installed (with flakes enabled)
1111+ - Docker daemon running
1212+- The repository checked out at commit `7bb5376fbec84feab775f57cb4c4d2fb02307686` or later
1313+- Run the automated checks first: `bash tests/verify-mm66.sh` — all checks must pass before proceeding
1414+1515+## Phase 1: Flake Output Verification (AC1.1, AC1.2)
1616+1717+| Step | Action | Expected |
1818+|------|--------|----------|
1919+| 1.1 | Run `nix flake show --accept-flake-config 2>/dev/null \| grep docker-image` | Output includes a line showing `docker-image` under the `x86_64-linux` packages section (e.g., `docker-image: package 'docker-image.tar.gz'` nested under `packages` > `x86_64-linux`) |
2020+| 1.2 | Inspect the same output for `aarch64-linux` | Output includes a line showing `docker-image` under the `aarch64-linux` packages section |
2121+| 1.3 | Confirm no `docker-image` appears under `aarch64-darwin` or `x86_64-darwin` sections | No `docker-image` lines appear in Darwin system sections (cross-validates AC1.3 on Linux) |
2222+2323+## Phase 2: Image Build (AC2.1, AC2.2)
2424+2525+| Step | Action | Expected |
2626+|------|--------|----------|
2727+| 2.1 | Run `nix build .#docker-image --accept-flake-config` on x86_64-linux | Command exits with status 0. A `result` symlink appears in the project root pointing to a path like `/nix/store/...-docker-image.tar.gz` |
2828+| 2.2 | Run `ls -lh result` | The file is a gzipped tarball (`.tar.gz`). Note the file size for later comparison with AC4.1 |
2929+| 2.3 | (If aarch64-linux or binfmt available) Run `nix build .#packages.aarch64-linux.docker-image --accept-flake-config` | Command exits with status 0. A `result` symlink appears for the aarch64 image. If no aarch64 system or binfmt is available, skip and note as untested |
3030+3131+## Phase 3: Image Load and Inspect (AC2.3, AC2.4)
3232+3333+| Step | Action | Expected |
3434+|------|--------|----------|
3535+| 3.1 | Run `docker load < result` | Output contains `Loaded image: relay:latest` |
3636+| 3.2 | Run `docker images relay` | Output shows at least one row with REPOSITORY `relay` and TAG `latest` |
3737+3838+## Phase 4: Image Contents Validation (AC3.1, AC3.2, AC3.3)
3939+4040+| Step | Action | Expected |
4141+|------|--------|----------|
4242+| 4.1 | Run `docker run --rm relay:latest` | Container exits. There must be NO errors like `no such file or directory` or `error while loading shared libraries: libsqlite3.so`. A non-zero exit code is acceptable because the relay binary is a stub with no configuration to connect to |
4343+| 4.2 | Run `docker inspect relay:latest \| grep -E 'SSL_CERT_FILE'` | Output shows an environment variable line containing `SSL_CERT_FILE=/nix/store/...-nss-cacert-.../etc/ssl/certs/ca-bundle.crt` (the exact Nix store hash will vary) |
4444+| 4.3 | Run `docker inspect relay:latest \| grep -E 'TZDIR'` | Output shows an environment variable line containing `TZDIR=/nix/store/...-tzdata-.../share/zoneinfo` (the exact Nix store hash will vary) |
4545+4646+## Phase 5: Image Size (AC4.1)
4747+4848+| Step | Action | Expected |
4949+|------|--------|----------|
5050+| 5.1 | Run `docker images relay --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"` | The SIZE column shows a value under 50 MB. The image should be relatively small since it is a minimal Nix closure containing only the relay binary, sqlite, cacert, and tzdata |
5151+5252+## Phase 6: Scope Boundary (AC5.1)
5353+5454+| Step | Action | Expected |
5555+|------|--------|----------|
5656+| 6.1 | Run `docker run --rm relay:latest` (same as step 4.1) | The container exits immediately. It does NOT attempt to start an HTTP server, listen on any port, or block waiting for connections. This confirms the relay is a packaging-only stub |
5757+| 6.2 | Run `docker run --rm -d relay:latest && sleep 2 && docker ps \| grep relay` | No running container appears in `docker ps` output — the relay exited immediately, confirming no long-running server process |
5858+5959+## End-to-End: Full Build-to-Run Pipeline
6060+6161+**Purpose:** Validate the complete workflow a developer or CI system would follow to build and verify the Docker image from a clean state.
6262+6363+1. Start from a clean state: `docker rmi relay:latest 2>/dev/null; rm -f result`
6464+2. Run automated checks: `bash tests/verify-mm66.sh` — verify exit code 0
6565+3. Build the image: `nix build .#docker-image --accept-flake-config`
6666+4. Verify the result symlink exists: `ls -lh result`
6767+5. Load into Docker: `docker load < result`
6868+6. Verify loaded: `docker images relay` — shows `relay:latest`
6969+7. Run the container: `docker run --rm relay:latest` — exits without linker errors
7070+8. Inspect environment: `docker inspect relay:latest | jq '.[0].Config.Env'` — shows both `SSL_CERT_FILE` and `TZDIR`
7171+9. Check size: `docker images relay --format '{{.Size}}'` — under 50 MB
7272+10. Clean up: `docker rmi relay:latest; rm -f result`
7373+7474+**Expected:** All steps succeed. The pipeline demonstrates that the Nix flake produces a valid, loadable, runnable Docker image with correct environment configuration and acceptable size.
7575+7676+## Human Verification Required
7777+7878+| Criterion | Why Manual | Steps |
7979+|-----------|-----------|-------|
8080+| AC1.1 | Requires Linux Nix evaluation to confirm x86_64-linux output is present | Phase 1, Step 1.1 |
8181+| AC1.2 | Requires Linux Nix evaluation to confirm aarch64-linux output is present | Phase 1, Step 1.2 |
8282+| AC2.1 | `nix build` of docker-image only works on Linux; macOS has no `docker-image` output | Phase 2, Step 2.1 |
8383+| AC2.2 | Requires aarch64-linux system or binfmt QEMU support for cross-arch build | Phase 2, Step 2.3 |
8484+| AC2.3 | Requires Docker daemon to load the image tarball | Phase 3, Step 3.1 |
8585+| AC2.4 | Requires Docker daemon to list loaded images | Phase 3, Step 3.2 |
8686+| AC3.1 | Requires Docker daemon to run the container and verify no linker errors | Phase 4, Step 4.1 |
8787+| AC3.2 | Requires Docker daemon to inspect image config for SSL_CERT_FILE | Phase 4, Step 4.2 |
8888+| AC3.3 | Requires Docker daemon to inspect image config for TZDIR | Phase 4, Step 4.3 |
8989+| AC4.1 | Requires Docker daemon to check loaded image size | Phase 5, Step 5.1 |
9090+| AC5.1 | Requires Docker daemon to confirm relay is a stub (exits immediately, no server) | Phase 6, Steps 6.1-6.2 |
9191+9292+## Traceability
9393+9494+| Acceptance Criterion | Automated Test | Manual Step |
9595+|----------------------|----------------|-------------|
9696+| AC1.1 | — | Phase 1, Step 1.1 |
9797+| AC1.2 | — | Phase 1, Step 1.2 |
9898+| AC1.3 | `tests/verify-mm66.sh` lines 16-51 | Phase 1, Step 1.3 (cross-validation) |
9999+| AC2.1 | — | Phase 2, Step 2.1 |
100100+| AC2.2 | — | Phase 2, Step 2.3 |
101101+| AC2.3 | — | Phase 3, Step 3.1 |
102102+| AC2.4 | — | Phase 3, Step 3.2 |
103103+| AC3.1 | — | Phase 4, Step 4.1 |
104104+| AC3.2 | — | Phase 4, Step 4.2 |
105105+| AC3.3 | — | Phase 4, Step 4.3 |
106106+| AC3.4 | `tests/verify-mm66.sh` lines 56-65 | — |
107107+| AC4.1 | — | Phase 5, Step 5.1 |
108108+| AC5.1 | — | Phase 6, Steps 6.1-6.2 |