# Phase 6 — Verification Report **Date**: 2026-05-14 **Run**: `01-testability-refactoring`, cycle 1 **Verdict**: PASS ## 1. Static build | Step | Command | Result | |------|---------|--------| | .NET build (host) | `dotnet build src/Azaion.Annotations.csproj -c Debug --no-restore` | 0 errors, 39 pre-existing CS8632 warnings (all `?`-on-non-nullable-context — none introduced by this batch). | | .NET build (containerised) | `dotnet run --project src/Azaion.Annotations.csproj` inside `mcr.microsoft.com/dotnet/sdk:10.0` | App compiled successfully and reached `Application started.` (see smoke logs). | | Lint | `ReadLints` on `src/Auth/JwtExtensions.cs` and `src/Services/FailsafeProducer.cs` | 0 new lint issues. | ## 2. Smoke run ### 2.1 Stack Smoke compose: `_docs/04_refactoring/01-testability-refactoring/smoke-compose.yml`. Topology — postgres + rabbitmq (with streams plugin) + python:3.12-alpine serving a stub JWKS over HTTP + annotations app running directly via `mcr.microsoft.com/dotnet/sdk:10.0` with the repo bind-mounted at `/repo`. Why not the production Dockerfile: `src/Dockerfile` has a build-context bug (uses `WORKDIR /src` + `COPY . .` then `dotnet publish` with no project arg, which fails because the .csproj lives one level deeper). That bug is OUT OF SCOPE for the testability refactor and will be fixed in autodev Step 6 when the full e2e harness comes online. ### 2.2 Probes | Probe | Expected | Observed | |-------|----------|----------| | Annotations container reaches `healthy` | ≤ 90 s | 15 s | | Hosting environment | `E2ETest` | `info: Microsoft.Hosting.Lifetime[0] Hosting environment: E2ETest` | | `GET /health` (anonymous) | 200 OK | 200 OK, multiple requests during runtime | | `GET /annotations` with `Authorization: Bearer dummy.invalid.token` (protected) | 401 (token rejected by validator) | 401 Unauthorized, `WWW-Authenticate: Bearer error="invalid_token"` | ### 2.3 Failure signatures — the two we fixed | Signature | Looking for | Found | |-----------|-------------|-------| | `IDX20108` ("The address specified ... is not valid as per HTTPS scheme") | 0 occurrences — proves C01 is active | **0 occurrences** | | `IPAddress.Parse` `FormatException` ("An invalid IP address was specified") | 0 occurrences — proves C02 is active | **0 occurrences** | ### 2.4 Failure signatures — unrelated to this batch | Signature | Severity | Why it's present | |-----------|----------|------------------| | `FailsafeProducer ... CreateProducerException: StreamDoesNotExist` | Expected | The smoke stack does not declare the `azaion.detections` stream; the seed step that creates streams lives in `e2e/seed/run.sh` and only runs as part of the full e2e harness. The producer reaches the broker (which proves C02), then fails because the stream is missing. Would fail identically with a literal-IP `RABBITMQ_HOST`. | | `IDX10400: Unable to decode '...' as Base64url encoded string` | Expected | The smoke probe deliberately sends `dummy.invalid.token`, which is not valid base64url. This is the JWT library's own rejection of a malformed token — NOT the IPAddress.Parse FormatException nor the IDX20108 we fixed. It is the desired 401 path. | ## 3. Functional behavior unchanged for the non-test paths | Concern | Method | Result | |---------|--------|--------| | HTTPS-only enforcement preserved under non-test envs | Code review of `JwtExtensions.cs:30-37`: `requireHttpsForJwks` is `false` only when `ASPNETCORE_ENVIRONMENT == "E2ETest"` (case-insensitive). | ✓ Passed | | Literal-IP `RABBITMQ_HOST` still works | Code review of `FailsafeProducer.cs:ResolveHostAddress`: `IPAddress.TryParse(host, out var literal)` short-circuits before DNS. | ✓ Passed | | Token validation pipeline unchanged | `TokenValidationParameters` block, algorithm pinning (ES256), signature/issuer/audience/lifetime checks all identical to pre-change code. | ✓ Passed | ## 4. Verdict PASS. Both surgical changes (C01, C02) behave exactly as specified by their task acceptance criteria. No regression observed in the unchanged paths. Production safety preserved (HTTPS-required when not in `E2ETest`; literal-IP path unchanged). Tear-down: `docker compose -f smoke-compose.yml down -v` completed cleanly; no orphaned volumes or networks remain. The `smoke-compose.yml` file is intentionally retained as a verification artifact under `_docs/04_refactoring/01-testability-refactoring/` — it is NOT part of the test harness or the production stack.