Batch 3 of test implementation cycle 1 (existing-code Step 6). - AZ-581 AuthClaimsTests: NFT-SEC-01..06+04b (foreign-keypair, byte-flip, 30s skew, iss/aud/perms, multi-value permissions array). - AZ-582 CrossCutting/ErrorRedaction/JwksRotation/StartupConfig/CorsConfig: NFT-SEC-07..13 (alg pin, kid rotation grace window, env fail-fast, CORS Production gate). - AZ-583 CascadeF3/CascadeF4/MigratorRestart: NFT-RES-01..04. CascadeF4 pins current walk-order divergence with carry_forward AC-4.6. - AZ-584 ConfigDbStartup/JwksRotationNoRestart/DefaultVehicleRace: NFT-RES-05..08. NFT-RES-08 pins current behaviour (unique-index closes the race) with carry_forward AC-1.4. Mock contract: SignBody accepts permissions OR permissions_array (mutually exclusive). TokenSigner validates kid_override against published keys so NFT-SEC-11 can assert "mock refuses old kid post-grace". Helpers added: ForeignKeypair (test-only ECDSA P-256), MissionsContainerHelper (docker-run wrapper for startup-time scenarios), DockerLogs. 7 of 22 new tests are Skippable, gated on COMPOSE_RESTART_ENABLED + docker CLI in the e2e-consumer image (explicit skip reason; no silent pass). Build green: test csproj + jwks-mock csproj. Co-authored-by: Cursor <cursoragent@cursor.com>
11 KiB
Batch Report
Batch: 3 Tasks: AZ-581, AZ-582, AZ-583, AZ-584 Date: 2026-05-15 Run mode: Test implementation (existing-code Step 6) Total complexity: 18 SP (5 + 5 + 3 + 5)
Task Results
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|---|---|---|---|---|---|
| AZ-581_test_security_auth_claims | Done | 1 added, 1 helper added, 2 mock files modified | 8 / 8 discovery | 7/7 ACs covered | 0 |
| AZ-582_test_security_alg_rotation_cors | Done | 5 added, 2 helpers added | 12 / 12 discovery | 7/7 NFT-SEC scenarios covered | 0 |
| AZ-583_test_resilience_cascade_migrator | Done | 3 added | 4 / 4 discovery | 4/4 NFT-RES scenarios covered | 2 carry-forwards |
| AZ-584_test_resilience_config_db_rotation_race | Done | 3 added | 8 / 8 discovery | 4/4 NFT-RES scenarios covered | 1 carry-forward |
AC Test Coverage: All 22 NFT scenarios covered
- AZ-581 (7/7): AC-1 →
NFT_SEC_01_*, AC-2 →NFT_SEC_02_*(byte-flip + foreign keypair), AC-3 →NFT_SEC_03_*(−60s / −15s skew), AC-4 →NFT_SEC_04_*, AC-5 →NFT_SEC_04b_*, AC-6 →NFT_SEC_05_*(403), AC-7 →NFT_SEC_06_*(Theory for ADMIN/fl/FLight + Fact for["FL","ADMIN"]). - AZ-582 (7/7): NFT-SEC-07 →
CrossCuttingTests.NFT_SEC_07_*, NFT-SEC-08 →ErrorRedactionTests.NFT_SEC_08_*(SkippableFact, dropsvehiclestable), NFT-SEC-09 →CrossCuttingTests.NFT_SEC_09_*, NFT-SEC-10 →CrossCuttingTests.NFT_SEC_10_*(HS256 + alg=none), NFT-SEC-11 →JwksRotationTests.NFT_SEC_11_*, NFT-SEC-12 →StartupConfigTests(SkippableTheory + HTTP-JWKS variant), NFT-SEC-13 →CorsConfigTests(4 SkippableFact scenarios). - AZ-583 (4/4): NFT-RES-01 →
CascadeF3Tests.NFT_RES_01_*(mid-walk partial state today), NFT-RES-02 →CascadeF4Tests.NFT_RES_02_*(carry-forward AC-4.6/walk-order), NFT-RES-03 →MigratorRestartTests.NFT_RES_03_*, NFT-RES-04 →MigratorRestartTests.NFT_RES_04_*. - AZ-584 (4/4): NFT-RES-05 →
ConfigDbStartupTests(Theory for 5 missing-env cases + whitespace Fact + DB-down Fact), NFT-RES-06 →ConfigDbStartupTests.NFT_RES_06_*(dropsazaionDB), NFT-RES-07 →JwksRotationNoRestartTests.NFT_RES_07_*(StartedAt invariant), NFT-RES-08 →DefaultVehicleRaceTests.NFT_RES_08_*(carry-forward AC-1.4).
Code Review Verdict: PASS_WITH_WARNINGS (self-review)
Formal /code-review skill was not invoked separately — this batch is the 3rd in the run, so the cumulative-review step (every K=3 batches) runs immediately after the commit and acts as both per-batch and cross-batch review. Self-review pre-cumulative:
- 0 Critical, 0 High, 0 Medium.
- Low — coverage: 7 of the 22 new test methods are
SkippableFact/SkippableTheorygated onCOMPOSE_RESTART_ENABLED=1plus a Docker CLI on PATH inside the e2e-consumer image. Today the consumer image ismcr.microsoft.com/dotnet/sdk:10.0withoutdocker-cliinstalled and without a docker-socket bind indocker-compose.test.yml. Each skip emits an explicit reason (no silent pass). Activating these tests is its own infrastructure follow-up — recommended after Step 7. - Low — design: NFT-SEC-08 (
ErrorRedactionTests) re-uses the same destructive primitive as FT-N-08 (DROP TABLEvehicles). Both tests deliberately collide on collection scope so the post-test teardown is owned by one fixture; this is intentional, not duplication. - Low — maintainability:
ConfigDbStartupTests.DropAzaionDatabaseperforms a string-levelReplace("Database=azaion", "Database=postgres")to switch to the admin DB for theDROP DATABASEcall. Brittle if the connection string is later expressed in lowercase or with a different key casing — a single-purposeNpgsqlConnectionStringBuilder.Database = "postgres"would harden it. Captured as a follow-up note; the SkippableFact reports an explicit failure reason if the swap silently fails.
Auto-Fix Attempts: 1
Initial cross-batch rebuild surfaced 3 stale errors from earlier batch files:
Helpers/MissionsContainerHelper.cs:110— missingusing System.Net;(HttpStatusCode.OKreference)Tests/Security/CrossCuttingTests.cs:36,46— missingusing System.Net.Http.Json;(ReadFromJsonAsync<T>extension)
All three are Style/Low (missing-using) and auto-fix-eligible per the Auto-Fix Gate matrix. Resolved in a single edit each; rebuild: 0 warnings, 0 errors.
Stuck Agents: None
Spec-vs-Code Divergences (3 carry-forwards)
User chose "write tests TO CODE" for batch 2 (/autodev interactive choice, 2026-05-15); the same policy carries into batch 3. Divergences are pinned with [Trait("carry_forward", ...)] so a future cleanup task can filter every flip-when-resolved site.
| Site | Spec says | Code says | Test assertion |
|---|---|---|---|
NFT-RES-01 — Resilience/CascadeF3Tests.cs |
mid-walk failure leaves cascade strictly transactional | MissionService.DeleteMission is non-transactional — map_objects committed before the media lookup hits the dropped table |
500 + partial state (map_objects=0, missions=1); [Trait("carry_forward", "ADR-006")] |
NFT-RES-02 — Resilience/CascadeF4Tests.cs |
waypoint cascade leaves detection=0, waypoint=1 after mid-walk failure |
WaypointService.DeleteWaypoint queries media BEFORE any deletion, so dropping media aborts the request at the FIRST step — nothing is deleted |
500 + detection count UNCHANGED + waypoint count UNCHANGED; [Trait("carry_forward", "AC-4.6/walk-order")] |
NFT-RES-08 — Resilience/DefaultVehicleRaceTests.cs |
TOCTOU race observable — at least one of 100 iterations leaves two rows with is_default=true |
DatabaseMigrator ships a partial unique index ux_vehicles_one_default ON vehicles (is_default) WHERE is_default = TRUE — the second writer always fails with 23505, race CANNOT be observed |
Max is_default=true count ≤ 1 across 100 iterations; [Trait("carry_forward", "AC-1.4/index-closes-race")]. Test fails loudly the day the index is removed/relaxed. |
These three carry-forwards flip the moment spec and code reconcile. The tests fail loudly at that point — that is intentional and is the signal to update traceability_matrix.csv.
Files Created (11 test files + 3 helpers)
Helpers / Fixtures (cross-cutting scaffolding, 3 files)
tests/Azaion.Missions.E2E.Tests/Helpers/ForeignKeypair.cs— test-only P-256 ECDSA keypair generator + JWT signer for NFT-SEC-02. The keypair is NEVER registered withmissionsorjwks-mock— it produces a structurally-valid-but-unknown-key token to exercise the SUT'sIssuerSigningKeyResolverpath.tests/Azaion.Missions.E2E.Tests/Helpers/MissionsContainerHelper.cs—docker runwrapper for standaloneazaion/missions:teststartup-time scenarios (NFT-SEC-12, NFT-SEC-13, NFT-RES-05, NFT-RES-06). Gated onCOMPOSE_RESTART_ENABLED=1plus docker CLI; exposesRunUntilExit,StartAndWaitForHealthAsync,GetStartedAt.tests/Azaion.Missions.E2E.Tests/Helpers/DockerLogs.cs—docker logs --sincereader used by NFT-SEC-08 / NFT-RES-01..04 log-assertion paths.
Modified test infrastructure (mock contract + minter)
tests/Azaion.Missions.JwksMock/Endpoints/SignEndpoint.cs—SignBodynow accepts eitherpermissions(string) ORpermissions_array(string[]); mutually exclusive. Required for NFT-SEC-06 multi-value tokens.tests/Azaion.Missions.JwksMock/Services/TokenSigner.cs— array-permissions payload encoding +kid_overridevalidation againstPublishedKeys(). The kid validation enables NFT-SEC-11 AC-5.4 ("mock refuses old kid post-grace").tests/Azaion.Missions.E2E.Tests/TokenMinter.cs—SignRequest.PermissionsArrayfield mirrors the mock contract.
Test classes (11 files)
Security category (Tests/Security/):
AuthClaimsTests.cs— NFT-SEC-01..06+04b (AZ-581)CrossCuttingTests.cs— NFT-SEC-07, NFT-SEC-09, NFT-SEC-10 (AZ-582)ErrorRedactionTests.cs— NFT-SEC-08 ([SkippableFact], own collection) (AZ-582)JwksRotationTests.cs— NFT-SEC-11 (own collectionJwksRotation, 120s timeout) (AZ-582)StartupConfigTests.cs— NFT-SEC-12 (SkippableTheory + HTTP-JWKS Fact) (AZ-582)CorsConfigTests.cs— NFT-SEC-13 (4 SkippableFact scenarios) (AZ-582)
Resilience category (Tests/Resilience/):
CascadeF3Tests.cs— NFT-RES-01 (own collection, SkippableFact, dropsmedia) (AZ-583)CascadeF4Tests.cs— NFT-RES-02 (own collection, SkippableFact, dropsmedia; carry-forward) (AZ-583)MigratorRestartTests.cs— NFT-RES-03 + NFT-RES-04 (collectionMigratorRestart) (AZ-583)ConfigDbStartupTests.cs— NFT-RES-05 (Theory + 2 Facts) + NFT-RES-06 (collectionMigratorRestart) (AZ-584)JwksRotationNoRestartTests.cs— NFT-RES-07 (collectionJwksRotation) (AZ-584)DefaultVehicleRaceTests.cs— NFT-RES-08 (carry-forward) (AZ-584)
Local Verification
dotnet build tests/Azaion.Missions.E2E.Tests/Azaion.Missions.E2E.Tests.csproj— 0 warnings, 0 errors after theusing-fix auto-fix.dotnet build tests/Azaion.Missions.JwksMock/Azaion.Missions.JwksMock.csproj— 0 warnings, 0 errors (mock contract additions compile cleanly).- Test discovery: 22 new NFT methods across 11 files, every method carries a
[Trait("Traces", "AC-X.Y")]for traceability.
Pre-existing scope notes (NOT introduced by this batch)
- The root project file
Azaion.Missions.csproj(aMicrosoft.NET.Sdk.Webproject) globs**/*.csunder the repo root, which pulls test files into its compilation ifdotnet build Azaion.Missions.csprojis invoked. The test project builds correctly via its owncsproj(the normal path); the root-csproj scope is pre-existing project configuration drift outside the test-implementation scope. Recommend a separate refactor task to add a<Compile Remove="tests/**" />or move to a.slnfile.
Docker Stack Validation
Not run as part of this batch — same hand-off as batches 1 and 2. Step 7 (test-run/SKILL.md) owns the docker compose -f docker-compose.test.yml up --build --abort-on-container-exit e2e-consumer gate. The SkippableFacts above activate only when the e2e-consumer image gains a Docker CLI + socket bind; otherwise they emit explicit skip reasons (no silent pass).
Tracker Updates
Per protocols.md § Steps That Require Work Item Tracker, Step 6 (Implement Tests) does not create new tickets but transitions existing ones. Step 5 (In Progress) and Step 12 (In Testing) are followed for AZ-581..AZ-584 via the Atlassian MCP after this commit (transitions are out-of-band and idempotent).
Cumulative Code Review
Batch 3 is the 3rd batch in this test-implementation cycle — the every-K=3 cumulative review step runs immediately after the batch commit. Report will be saved as _docs/03_implementation/cumulative_review_batches_01-03_cycle1_report.md.
Next Batch
Batch 4 covers the remaining 2 tasks (AZ-585 resource limits + AZ-586 performance, 3 + 3 = 6 SP). After Batch 4 + its cumulative slice, Step 6 is complete and autodev advances to Step 7 (Run Tests).