Phase 6 smoke (Docker, _docs/04_refactoring/01-testability-refactoring/
smoke-compose.yml):
- Annotations app boots clean under ASPNETCORE_ENVIRONMENT=E2ETest.
- /health 200 OK; /annotations with bearer returns 401 with the
JWT library's own malformed-token rejection.
- 0 IDX20108 occurrences in logs (C01 verified).
- 0 IPAddress.Parse FormatException occurrences; FailsafeProducer
reaches the broker via Docker DNS (C02 verified).
- Full smoke report in verification.md.
Phase 7 docs:
- architecture.md: retire Open Risks §6 (testability blocker
resolved). Update the constraints block to describe the
ASPNETCORE_ENVIRONMENT-gated RequireHttps behavior.
- components/06_platform/description.md: one-liner on JwtExtensions
JWKS gating.
- components/02_annotations-realtime-sync/description.md: one-liner
on FailsafeProducer host resolution accepting literal IP or DNS.
- tests/test-data.md: refresh the JWKS URL configuration section to
point at the resolved implementation instead of the open risk.
Task housekeeping:
- _docs/02_tasks/todo/01_*.md -> done/
- _docs/02_tasks/todo/02_*.md -> done/
- _docs/_autodev_state.md: advance to Step 5 (Refactor Backlog Triage).
Tracker IDs remain placeholders pending Atlassian MCP availability —
real IDs to be assigned per
_docs/_process_leftovers/2026-05-14_testability-tracker.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
4.4 KiB
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.