Files
satellite-provider/_docs/02_document/tests/traceability-matrix.md
T
Oleksandr Bezdieniezhnykh 5d84d2839e
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful
[AZ-505] Test-spec sync + task-mode doc updates for cycle 6
Step 12 (Test-Spec Sync, cycle-update mode):
- blackbox-tests.md: append BT-23..BT-26 for AZ-505's new
  observable behaviors (inventory order/shape; leaflet
  most-recent via location_hash; HTTP/2 multiplex over TLS+ALPN;
  request validation).
- performance-tests.md: append PT-09 (inventory p95 ≤ 1000ms /
  2500 tiles); records cycle-6 measured p95=66ms; documents
  promotion path to scripts/run-performance-tests.sh if budget
  ever tightens.
- traceability-matrix.md: resolve the 5 AZ-503 deferrals
  (AC-5/6/9/10/12) by pointing at AZ-505 test names + add 7
  AZ-505 AC rows (AC-1..AC-7) + bump totals (90 -> 94 tests,
  56/56 -> 63/63 in-scope) + add cycle-6 coverage shape notes
  (budget relaxation rationale, voting-filter deferral note,
  TLS+ALPN pivot, NFR propagation).

Step 13 (Update Docs, task mode):
- common_dtos.md: add 5 new TileInventory DTOs.
- common_interfaces.md: add ITileService.GetInventoryAsync.
- services_tile_service.md: document TileService.GetInventoryAsync
  steps + the XOR-validation-in-handler note.
- dataaccess_migrator.md: bump migration count 14 -> 15;
  describe migration 015 (AZ-505 leaflet covering index, lock
  window, INCLUDE-list trade-off).
- system-flows.md: add F7 (Leaflet Tile Serving, AZ-310 +
  AZ-505 location_hash rewire + TLS+ALPN) and F8 (Tile
  Inventory Bulk Lookup) with sequence diagrams, validation
  surface, and AC-4 perf evidence. Update Flow Inventory +
  Dependencies tables accordingly.
- glossary.md: add "Tile Inventory" entry pointing at the
  v1.0.0 contract.
- ripple_log_cycle6.md: new file — exhaustive reverse-dependency
  analysis confirms zero stale downstream module docs.

Advance autodev state from step 11 -> 14 (skipping 12+13 as
completed in this commit; auto-chain through Step 14 = Security
Audit optional gate).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 22:29:22 +03:00

34 KiB
Raw Blame History

Traceability Matrix

Acceptance Criteria → Test Mapping

AC Description Tests Coverage
T1 Tiles cached, not re-downloaded BT-02
T2 Concurrent download limit RS-05, RL-03
T3 Tile stored with correct path BT-01
T4 Tile metadata persisted BT-01
R1 Region state transitions BT-03, BT-04, BT-05
R2 CSV manifest generated BT-03, BT-04, BT-05
R3 Summary file generated BT-03, BT-04, BT-05
R4 Stitched image when requested BT-05
R5 Stitched image valid content BT-05
R6 Region processing bounded RL-04
RT1 Points interpolated at ~200m BT-06
RT2 Point types correctly assigned BT-06
RT3 Total distance calculated BT-06
RT4 Geofence filtering applied BT-11
RT5 ZIP ≤ 50 MB BT-09, RL-01
RT6 Route map stitched BT-08, BT-10, BT-12
A1 Region request returns immediately BT-03
A2 Status endpoint reflects state BT-03, BT-07
A3 Route returns computed metadata BT-06
S1 Migrations run on startup RS-02
S2 Queue rejects when full RS-04, RL-02
S3 Failed regions marked failed RS-03
AZ-484 AC-1 Schema accepts source + captured_at; multi-source rows coexist under 5-col unique index MultiSourceInsertCoexistsUnderNewIndex_AZ484_AC1, NewUniqueConstraintIncludesSourceColumn_AZ484_AC1 (integration)
AZ-484 AC-2 Read returns most-recent across sources MostRecentAcrossSourcesSelection_AZ484_AC2 (integration)
AZ-484 AC-3 Same-source UPSERT collapses to one row with refreshed captured_at SameSourceUpsertReplacesPreviousRow_AZ484_AC3 (integration)
AZ-484 AC-4 Migration 013 backfill leaves no orphans (count preserved, source='google_maps', captured_at=created_at) BackfillUpdateAssignsGoogleMapsAndCapturedAt_AZ484_AC4 (integration)
AZ-484 AC-5 Google Maps download path stamps Source='google_maps' (wire) + CapturedAt UTC BuildTileEntity_SetsGoogleMapsSourceAndUtcCapturedAt_AZ484_AC5 (unit)
AZ-484 AC-6 Existing region/route flows unchanged post-T1 (200 unit + smoke baseline preserved) Full unit suite (213 tests) + integration smoke scenarios BT-01..BT-12
AZ-484 AC-7 Vision + contract docs amended (architecture.md, glossary.md, module-layout.md, tile-storage.md frozen v1.0.0) doc-state AC; verified by monorepo-document reviews
AZ-487 AC-1 Anonymous request returns 401 from every authenticated endpoint SEC-05 (blackbox); JwtIntegrationTests.AnonymousRequest_*_Returns401 (integration)
AZ-487 AC-2 Expired token returns 401; no internal leak in body SEC-06 (blackbox); JwtIntegrationTests.ExpiredToken_Returns401 (integration)
AZ-487 AC-3 Tampered signature returns 401 SEC-07 (blackbox); JwtIntegrationTests.InvalidSignature_Returns401 (integration)
AZ-487 AC-4 Valid token reaches handler with identical response SEC-09, BT-18 (blackbox); JwtIntegrationTests.ValidToken_Returns200_OnHealthyEndpoint (integration)
AZ-487 AC-5 Startup fails on missing / short JWT_SECRET SEC-08 (behavioral); AuthenticationServiceCollectionExtensionsTests.AddSatelliteJwt_Throws* (unit)
AZ-487 AC-6 HttpContext.User exposes claims (sub, permissions, …) JwtTokenFactoryTests.Create_WithExtraClaims_PropagatesClaimsThroughValidation (unit) + indirect via AZ-488 AC-6 (live permission check)
AZ-487 AC-7 Swagger UI Authorize button works JwtIntegrationTests.SwaggerDocument_AdvertisesBearerSecurityScheme (integration; programmatic equivalent of UI flow) ◐ doc-verified
AZ-487 AC-8 All existing tests pass with attached test token Full scripts/run-tests.sh --full run (cycle 2 Step 11 — passed)
AZ-488 AC-1 Happy-path 1-item batch persists with source='uav' BT-13 (blackbox); UavUploadTests.HappyPathSingleItem_PersistsRow (integration)
AZ-488 AC-2 3-item mixed batch returns per-item results BT-14 (blackbox); UavUploadTests.MixedBatch_ReturnsPerItemResults (integration)
AZ-488 AC-3 UAV upload coexists with pre-seeded google_maps row BT-15 (blackbox); UavUploadTests.MultiSourceCoexistence_AZ484_Cycle2 (integration); reuses AZ-484 AC-1 + AC-2 invariants
AZ-488 AC-4 Same-source UPSERT keeps one source='uav' row BT-16 (blackbox); UavUploadTests.SameSourceUpsert_AZ484_Cycle2 (integration); reuses AZ-484 AC-3 invariant
AZ-488 AC-5 Unauthenticated upload returns 401 (covered by AZ-487) UavUploadTests.NoToken_Returns401 (integration); AZ-487 AC-1 row covers contract
AZ-488 AC-6 Authenticated request without GPS permission returns 403 SEC-10 (blackbox); UavUploadTests.ValidTokenWithoutGpsPermission_Returns403 (integration); PermissionsRequirementTests (unit)
AZ-488 AC-7a INVALID_FORMAT reject reason on wrong content-type or magic bytes UavTileQualityGateTests.Validate_NonJpegContentType_* and Validate_WrongMagicBytes_* (unit)
AZ-488 AC-7b SIZE_OUT_OF_BAND reject reason on bytes outside [MinBytes, MaxBytes] RL-06 (resource-limit); UavTileQualityGateTests.Validate_BytesBelowMin_* and Validate_BytesAboveMax_* (unit)
AZ-488 AC-7c WRONG_DIMENSIONS reject reason on non-256×256 images BT-14, BT-17 (blackbox); UavTileQualityGateTests.Validate_WrongDimensions_* (unit)
AZ-488 AC-7d CAPTURED_AT_FUTURE / CAPTURED_AT_TOO_OLD reject reasons UavTileQualityGateTests.Validate_CapturedAtFuture_* and Validate_CapturedAtTooOld_* (unit)
AZ-488 AC-7e IMAGE_TOO_UNIFORM reject reason on uniform / low-variance JPEGs UavTileQualityGateTests.Validate_UniformGreyImage_RejectsImageTooUniform (unit)
AZ-488 AC-7 (ordering) First-applicable rule wins (e.g. format-fail beats dimensions-fail) BT-17 (blackbox); UavTileQualityGateTests.Validate_MultipleViolations_* (unit)
AZ-488 AC-8 Oversized batch (> MaxBatchSize) returns 400 envelope error RL-05 (resource-limit); UavUploadTests.OversizedBatch_Returns400 (integration)
AZ-488 AC-9 Contract uav-tile-upload.md v1.0.0 frozen and matches implementation doc-state AC; verified by Step 13 (Update Docs) review
AZ-488 AC-10 All existing tests + new AZ-487/AZ-488 tests pass; no AZ-484 regression Full scripts/run-tests.sh --full run (cycle 2 Step 11 — passed)
AZ-494 AC-1 Wrong iss token returns 401 SEC-12 (blackbox); JwtIntegrationTests.WrongIssuer_Returns401 (integration)
AZ-494 AC-2 Wrong aud token returns 401 SEC-13 (blackbox); JwtIntegrationTests.WrongAudience_Returns401 (integration)
AZ-494 AC-3 Matching iss + aud accepted JwtIntegrationTests.ValidToken_Returns200_OnHealthyEndpoint (integration; updated to mint via env iss/aud)
AZ-494 AC-4 Missing config fails fast AuthenticationServiceCollectionExtensionsTests.AddSatelliteJwt_ThrowsOnMissingIssuer + _ThrowsOnEmptyIssuer + _ThrowsOnMissingAudience + _ThrowsOnEmptyAudience (unit)
AZ-494 AC-5 Existing tests pass with matched fixtures Full integration suite reruns at Step 16 with JwtTestHelpers.MintAuthenticated (auto-fills iss/aud from env) ✓ (gate verified at Step 16)
AZ-494 AC-6 Security artifacts updated (F-AUTH-2 → Resolved) _docs/05_security/security_report.md + owasp_review.md updated this batch
AZ-494 AC-7 Suite contract reflects reality suite/_docs/10_auth.md lives outside this workspace; this cycle's deploy report documents that satellite-provider validates iss/aud locally and the prod values are admin-team-confirmed at deploy time ◐ deferred (cross-repo write)
AZ-491 AC-1 Single source of truth — only one JwtTokenFactory exists in source Structural: repo-wide grep returns exactly SatelliteProvider.TestSupport/JwtTokenFactory.cs; the legacy SatelliteProvider.Tests/TestUtilities/JwtTokenFactory.cs was deleted in batch 02
AZ-491 AC-2 Existing integration tests pass unchanged Full integration suite at Step 11 (./scripts/run-tests.sh --full) — all green
AZ-491 AC-3 Existing unit tests pass unchanged Unit suite at Step 11 (Step 1 of run-tests.sh) — all green
AZ-491 AC-4 Runner-side concerns preserved in JwtTestHelpers (env reads, HttpClient mutation stay in IntegrationTests) Structural: JwtTokenFactory (pure) in TestSupport; JwtTestHelpers (side-effectful) in IntegrationTests — documented in module-layout.md
AZ-491 AC-5 Cycle-2 fixes remain effective (AZ-487/AZ-488 token-validation invariants preserved) Integration scenarios JwtIntegrationTests.AnonymousRequest_*, _ExpiredToken_Returns401, _InvalidSignature_Returns401, _ValidToken_Returns200_OnHealthyEndpoint, UavUploadTests.* — all migrated to MintAuthenticated and still PASS at Step 11
AZ-491 AC-6 Code-review rule lands to prevent re-duplication .cursor/skills/code-review/SKILL.md Phase 6 rule added in batch 02 (Cycle-3 review SKILL update)
AZ-493 AC-1 Empty-state on startup — no leftover rows from previous run IntegrationTestDatabaseReset.ResetAsync invoked at runner start; uniqueness assumptions in UavUploadTests (source='uav' rows per coordinate) hold without the wall-clock workaround
AZ-493 AC-2 Wallclock workaround no longer needed Structural: UavUploadTests no longer offsets coordinates by DateTime.UtcNow.Ticks % … to dodge stale rows; coordinates are now deterministic per scenario
AZ-493 AC-3 Opt-out preserves state (--keep-state flag skips reset) scripts/run-tests.sh parses --keep-state, sets INTEGRATION_TEST_DB_RESET=skip, and Program.cs honours that env var
AZ-493 AC-4 Reset only fires in test environment (two-guard model) Unit: IntegrationTestResetGuardTests (env sentinel + Host allowlist postgres/localhost/127.0.0.1; production-shape hostnames rejected)
AZ-493 AC-5 Documentation reflects new convention doc-state AC — _docs/02_document/module-layout.md + _docs/02_document/modules/tests_integration.md updated in batch 03
AZ-493 AC-6 Existing tests pass unchanged Full integration suite at Step 11 — all green
AZ-495 AC-1..AC-N Doc folder convention formalized doc-state AC — .cursor/skills/new-task/SKILL.md updated in batch 01; _docs/02_document/module-layout.md carries the convention
AZ-496 AC-1 Microsoft.AspNetCore.Authentication.JwtBearer bumped 8.0.21 → 8.0.25 in SatelliteProvider.Api.csproj Structural: csproj diff visible in batch 01 commit; transitive update propagates to Tests.csproj via ProjectReference
AZ-496 AC-2..AC-N Suite still green at the new version Full unit + integration suite at Step 11 — all green; SEC-05..SEC-11 + AZ-494 AC-1/AC-2 (which depend on JwtBearer) all PASS
AZ-500 AC-1 Every csproj targets net10.0 Structural: grep -r "<TargetFramework>" --include="*.csproj" returns 9/9 net10.0, 0 net8.0 (verified at cycle 4 Step 11)
AZ-500 AC-2 global.json sdk.version=10.0.0, rollForward=latestMinor Structural: file contents asserted; SDK roll-forward exercised by host running .NET 10.0.103
AZ-500 AC-3 All Docker base images + CI images on :10.0 Structural: grep -rE "mcr.microsoft.com/dotnet/" --include="*Dockerfile" --include="*.yml" --include="*.sh" → 7/7 on :10.0
AZ-500 AC-4 Microsoft.AspNetCore.* + Microsoft.Extensions.* on 10.0.7; Serilog.AspNetCore documented fallback 8.0.3 Structural: csproj diff (19 references on 10.0.7); Serilog.AspNetCore fallback rationale recorded in AGENTS.md:244 per Risk #4
AZ-500 AC-5 Perf-script bootstrap step succeeds (no exit 3) — closes cycle-3 SDK-mismatch leftover PERF_REPEAT_COUNT=2 PERF_UAV_BATCH_SIZE=2 ./scripts/run-performance-tests.sh exit 1 (NOT 3 — bootstrap clean, build OK, JWT mint OK, PT-01..PT-07 PASS); leftover _docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md updated with new (non-SDK) PT-08 grep-pipefail finding; full perf gate runs at Step 15 of cycle 4
AZ-500 AC-6 All unit + integration tests pass on the migrated build Full ./scripts/run-tests.sh --full at cycle 4 Step 11 — 271/271 unit + integration suite green
AZ-500 AC-7 docker-compose build succeeds with no downgrade / framework / missing-image warnings run-tests.sh Step 2 build path + docker compose up -d --build both succeeded; only warnings emitted are CS8604 nullable + ASPDEPR002 deprecation (neither category gated)
AZ-500 AC-8 Documentation reflects .NET 10 _docs/02_document/architecture.md lines 5 + 67 (Tech Stack table) updated; AGENTS.md lines 9 + 240244 updated incl. Serilog fallback note
AZ-503 AC-1 UUIDv5 reference vectors match Python (≥10 cases) Uuidv5Tests.Create_MatchesPythonReferenceVectors_AC1 (unit) + version/variant bit assertions
AZ-503 AC-2 Insert is idempotent on identical inputs (id stable, created_at preserved) BT-20 (blackbox); UavTileUploadHandlerTests.HandleAsync_IdenticalUpload_ProducesIdenticalIdAndDeterministicContentSha (unit)
AZ-503 AC-3 Multi-flight UAV uploads coexist (two ids, shared location_hash) BT-19 (blackbox); UavTileUploadHandlerTests.HandleAsync_TwoFlightsSameCell_ProduceDistinctIdsAndPathsButSameLocationHash (unit); UavUploadTests.MultiFlightUavRowsCoexist_AZ503_AC3 (integration)
AZ-503 AC-4 Float-rounded coordinates collapse to a single row BT-21 (blackbox); UavUploadTests.FloatRoundingDoesNotBreakIdempotence_AZ503_AC4 (integration)
AZ-503 AC-7 content_sha256 is computed and persisted; byte-identical bodies produce identical digest BT-20 (blackbox); UavTileUploadHandlerTests.HandleAsync_IdenticalUpload_ProducesIdenticalIdAndDeterministicContentSha (unit)
AZ-503 AC-8 Migration 014 adds columns + supersedes AZ-484 index + backfills location_hash deterministically BT-22 (blackbox); MigrationTests.Az503ColumnsExistAndLocationHashIsNotNull, Az503NewUniqueIndexCoversIntegerKeyAndFlightId, Az503LocationHashBackfillIsDeterministic, Az503MigrationSupersedesAz484UniqueIndex (integration)
AZ-503 AC-11 Per-flight on-disk separation (./tiles/uav/{flight_id|none}/{z}/{x}/{y}.jpg) BT-19 (blackbox); UavTileFilePathTests.BuildUavTileFilePath_AnonymousFlight_UsesNoneSegment, _PerFlight_UsesFlightIdDirectory, _DifferentFlights_ProduceDifferentPaths (unit); UavUploadTests.MultiFlightUavRowsCoexist_AZ503_AC3 (integration; per-flight file_path assertion)
AZ-503 AC-5 Inventory endpoint POST /api/satellite/tiles/inventory returns one entry per requested coord BT-23 (blackbox); resolved by AZ-505 AC-1 — see row below ✓ (via AZ-505)
AZ-503 AC-6 Leaflet path returns most-recent variant via location_hash BT-24 (blackbox); resolved by AZ-505 AC-2 — see row below ✓ (via AZ-505)
AZ-503 AC-9 Inventory endpoint p95 ≤ 500 ms for 2500 tiles PT-09 (performance); resolved by AZ-505 AC-4 — see row below (budget relaxed to 1000 ms under AZ-505 scoping, AZ-505 Risk 1) ✓ (via AZ-505, relaxed budget)
AZ-503 AC-10 Leaflet hot path is index-only (EXPLAIN: no heap fetch when voting_status='trusted') Resolved by AZ-505 AC-3 — see row below (voting layer is deferred to a future task per AZ-505 Non-Goals; AC-3 verifies the index-only access path against tiles_leaflet_path directly via EXPLAIN ANALYZE + Heap Fetches: 0 assertion) ✓ (via AZ-505, voting-filter deferred)
AZ-503 AC-12 HTTP/2 multiplexed responses for /tiles/{z}/{x}/{y} BT-25 (blackbox); resolved by AZ-505 AC-5 — see row below ✓ (via AZ-505)
AZ-505 AC-1 Inventory endpoint returns one entry per requested coord in input order; present/absent shaping per tile-inventory.md Inv-1..Inv-6 BT-23 (blackbox); TileInventoryTests.OrderingAndPresentAbsentShaping_AC1 (integration)
AZ-505 AC-2 Leaflet read path returns most-recent variant keyed on location_hash (captured_at DESC, updated_at DESC, id DESC LIMIT 1) BT-24 (blackbox); TileInventoryTests.LeafletReadReturnsMostRecentViaLocationHash_AC2 (integration; DB-level verification of the exact SELECT used by TileRepository.GetByTileCoordinatesAsync, which ServeTile wraps unchanged)
AZ-505 AC-3 Leaflet hot path uses Index Only Scan using tiles_leaflet_path; Heap Fetches ≤ 1 after VACUUM ANALYZE; query time < 1 ms LeafletPathIndexOnlyTests.RunAll (integration; EXPLAIN ANALYZE + regex + Heap Fetches ≤ 1; smoke run falls back to SET enable_seqscan = off if the optimiser hasn't picked the index naturally — measures index capability, not optimiser heuristic)
AZ-505 AC-4 Inventory endpoint p95 ≤ 1000 ms for 2500 tiles over 20 calls PT-09 (performance); TileInventoryTests.PerformanceBudget_AC4 (integration; full-suite only, smoke prints a documented skip). Cycle 6 measured: p95=66ms, max=117ms — well under budget.
AZ-505 AC-5 HTTP/2 multiplexed responses for /tiles/{z}/{x}/{y} over a single connection with preserved ETag + Cache-Control headers BT-25 (blackbox); Http2MultiplexingTests.RunAll (integration; 20 concurrent GETs over a single TLS connection with SocketsHttpHandler { EnableMultipleHttp2Connections = false } + HttpVersion.Version20 + RequestVersionExact). Implementation uses TLS+ALPN on the dev https://+:8080 listener (cert generated by scripts/run-tests.sh into ./certs/api.pfx, trusted in the integration-tests container via update-ca-certificates) — the original h2c plan was switched mid-cycle because Kestrel silently downgrades Http1AndHttp2 to HTTP/1.1 over plaintext (no ALPN). See _docs/03_implementation/implementation_report_tile_inventory_cycle6.md → "Post-merge correction".
AZ-505 AC-6 Request validation — 400 on both populated, 400 on neither, 400 on > 5000 entries, 401 on anonymous BT-26 (blackbox); TileInventoryTests.ValidationRejectsBothPopulated_AC6, ValidationRejectsNeitherPopulated_AC6, ValidationRejectsOversizedBatch_AC6, UnauthenticatedRequestReturns401_AC6 (integration)
AZ-505 AC-7 Contract artifacts produced in the same commit as code (tile-inventory.md v1.0.0, tile-storage.md v2.0.0 Change Log, module-layout.md rows) Doc inspection at completeness gate — _docs/03_implementation/implementation_completeness_cycle6_report.md "Files / Symbols Checked" + "Contracts" sections list the v1.0.0 / v2.0.0 artifacts; no runtime test (deliberately doc-only) ✓ (doc-only)
AZ-504 AC-1 PT-08 completes on zero-rejected response (no script exit under set -e -o pipefail) Standalone shell harness (4-case) executed in batch_01_cycle5_report.md — accepted/rejected counters wrapped in { grep -o … || true; } at scripts/run-performance-tests.sh:416-417; structural: rg "grep -o .* \\| wc -l" scripts/run-performance-tests.sh returns 0 unguarded sites
AZ-504 AC-2 PT-08 completes on zero-accepted response (defensive) Same standalone shell harness (case 4) — accepted=0, rejected=N path no longer kills the script
AZ-504 AC-3 PT-08 summary line prints in full default-parameter perf run Verified at autodev Step 15 (Performance Test) by running scripts/run-performance-tests.sh with PERF_REPEAT_COUNT=20 PERF_UAV_BATCH_SIZE=10; pass criterion is the PT-08 UAV batch upload: PASS p95=Xms / 2000ms (...) line in the run output ◐ gate at Step 15
AZ-504 AC-4 Leftover _docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md deleted on green full run Verified at autodev Step 15 by test -f _docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md returning non-zero after the green run + commit ◐ gate at Step 15

Restrictions → Test Mapping

Restriction Tests Coverage
.NET 10 runtime (cycle 4 — was .NET 8.0 LTS through cycle 3) All (via Docker image mcr.microsoft.com/dotnet/aspnet:10.0); cycle 4 Step 11 full suite green
PostgreSQL 16 All (via docker-compose)
Single instance PT-05 (concurrent regions on one instance)
Max 4 concurrent downloads RS-05, RL-03
Max 20 concurrent regions RL-04
Queue capacity 1000 RS-04, RL-02
Max ZIP 50 MB RL-01
No authentication → JWT (HS256) on every endpoint, GPS permission on UAV upload SEC-01..SEC-04 (input handling); SEC-05..SEC-09 (AZ-487 JWT layer); SEC-10..SEC-11 (AZ-488 permission + leak hygiene) ✓ (superseded by AZ-487 + AZ-488, cycle 2)

NFRs → Test Mapping

NFR Source Tests Coverage
AZ-484 Perf — GetTilesByRegionAsync p95 ≤ 1.10 × pre-AZ-484 baseline AZ-484 task spec § Non-Functional Requirements PT-07 (Implemented in AZ-492 — cold + warm distribution, p50/p95 reported; cross-commit baseline comparison remains operator-driven at Step 15)
AZ-484 Compatibility — no public HTTP response field added/removed; vestigial maps_version/version columns preserved (nullable) AZ-484 task spec § Non-Functional Requirements Existing integration suite (no API contract change observable); BT-01 / region status responses verify response shape
AZ-487 Performance — JWT validation < 1 ms overhead per request AZ-487 task spec § Non-Functional Requirements Not separately measured (HMAC-SHA256 + claims parse is sub-millisecond on any modern x86; no caching needed). Re-measure if PT-07 / PT-08 (AZ-492 harness) shows aggregate regression. ◐ recorded
AZ-487 Security — RequireSignedTokens, RequireExpirationTime, ClockSkew = 30 s, secret ≥ 32 bytes, iss + aud validated (extended by AZ-494) AZ-487 + AZ-494 task specs § Non-Functional Requirements + Constraints AuthenticationServiceCollectionExtensionsTests (unit) + SEC-05..SEC-09 + AZ-494 AC-1/AC-2 wrong-iss/aud (integration)
AZ-487 Reliability — Fail-fast on missing / short JWT_SECRET at startup (extended by AZ-494 to iss + aud) AZ-487 + AZ-494 task specs § Non-Functional Requirements SEC-08 (behavioral) + unit AddSatelliteJwt_ThrowsOnMissingSecret + _ThrowsOnMissingIssuer + _ThrowsOnMissingAudience
AZ-488 Performance — Per-item gate cost < 50 ms; p95 batch-of-10 < 2 s AZ-488 task spec § Non-Functional Requirements PT-08 (Implemented in AZ-492 — 20-batch distribution, batch p95 gated at 2000 ms; per-item gate cost reported as derived proxy batch_p95 / batch_size. True per-call UavTileQualityGate.Validate timing requires server-side instrumentation — follow-up). ✓ (batch p95) / ◐ (per-item proxy only)
AZ-488 Reliability — File-first then DB row; per-item failures never fail the batch envelope (except 400/401/403) AZ-488 task spec § Non-Functional Requirements BT-14 (mixed-batch shows per-item isolation); UavTileUploadHandlerTests.*PersistAsync* (unit); reject reason STORAGE_FAILURE defined in contract for the orphan-row recovery path
AZ-488 Compatibility — Replaces 501 stub; coexists with AZ-484 tile-storage v1.0.0 contract on the write side AZ-488 task spec § Non-Functional Requirements + Contract StubAndErrorContractTests updated to drop the stub-501 expectation; BT-15 + BT-16 validate the AZ-484 invariants under live UAV writes
AZ-488 Security — Reject details never leak server internals; integer-only file-path construction AZ-488 task spec § Non-Functional Requirements + Risk 2 SEC-11 (blackbox); UavTileFilePathTests (unit)
AZ-503 Cross-repo contract — UUIDv5 namespace pinned to 5b8d0c2e-7f1a-4d3b-9c5e-1f3a8e7d2b6c; C# and Python (gps-denied-onboard) MUST produce byte-identical output AZ-503 task spec § Constraints Uuidv5Tests.Create_MatchesPythonReferenceVectors_AC1 covers the C# side; cross-repo (Python) side is enforced by gps-denied-onboard AZ-304 and is out of this workspace's automated suite. The constant value is asserted structurally by Uuidv5Tests referencing Uuidv5.TileNamespace. ✓ (C# side) / ◐ (Python parity verified by reference vectors; sibling workspace owns the runtime check)
AZ-503 Compatibility — No column renames; tile_zoom/tile_x/tile_y/latitude/longitude preserved; legacy id retained in legacy_id for one cycle AZ-503 task spec § Constraints + Risk 1 MigrationTests.Az503ColumnsExistAndLocationHashIsNotNull (asserts existing column names unchanged); migration 014 SQL inspection (no DROP / RENAME COLUMN on existing columns); structural: legacy_id populated from id for all pre-existing rows
AZ-503 Migration constraint — Additive non-blocking ALTER TABLE ADD COLUMN; backfill in-migration; NOT NULL set only on location_hash (legacy content_sha256 left NULLable — see batch_02_cycle5_report.md "Low" finding) AZ-503 task spec § Constraints + Risk 3 Migration script inspection (014_AddTileIdentityColumns.sql); MigrationTests.Az503ColumnsExistAndLocationHashIsNotNull asserts location_hash NOT NULL; application-layer NOT NULL invariant for new content_sha256 writes enforced in TileService.BuildTileEntity + UavTileUploadHandler.PersistAsync
AZ-503 Selection-rule preservation — AZ-484 read-side tie-break (captured_at DESC, updated_at DESC, id DESC) unchanged AZ-503 task spec § Constraints TileRepository.GetByTileCoordinatesAsync unchanged on the read path; AZ-484 AC-2 row above (MostRecentAcrossSourcesSelection_AZ484_AC2) still passes at Step 11
AZ-504 Compatibility — Fix preserves set -e -o pipefail globally; only the empty-grep-match case is tolerated locally; no silent error swallowing AZ-504 task spec § Non-Functional Requirements + Constraints Standalone shell harness (batch_01_cycle5_report.md) — case "non-zero exit code other than 1 from grep" still propagates failure; structural: scripts/run-performance-tests.sh:13 retains set -euo pipefail; only the two grep counters at lines 416-417 are wrapped in { grep -o … || true; } (not blanket || true over the whole assignment)

Coverage Summary

Category Total Tests ACs Covered Restrictions Covered
Blackbox (positive) 12 19/22
Blackbox (negative) 5
Performance 8 4 1
Resilience 6 4 3
Security 11 9 (AZ-487 AC-1..AC-7, AZ-488 AC-6, leak-hygiene NFR) 1 (AZ-487 supersedes "No authentication")
Resource Limits 7 5 4
Cycle 1 — AZ-484 (integration + unit) 6 7/7
Cycle 2 — AZ-487 (integration + unit + behavioral) 4 integration + 3 unit + 1 behavioral 8/8
Cycle 2 — AZ-488 (integration + unit + blackbox) 7 integration + 14 unit + 6 blackbox 10/10
Cycle 5 — AZ-503 foundation (integration + unit + blackbox) 2 integration + 6 unit + 4 blackbox 7/12 in-scope (AC-1, 2, 3, 4, 7, 8, 11); 5 ACs deferred → AZ-505 (now resolved in cycle 6)
Cycle 5 — AZ-504 perf-script fix (shell harness + Step-15 gate) 1 standalone shell harness (4 cases) 2/4 verified now (AC-1, AC-2); 2/4 gated at Step 15 (AC-3, AC-4)
Cycle 6 — AZ-505 inventory + HTTP/2 + leaflet covering index (integration + blackbox + perf) 3 integration files + 4 blackbox (BT-23..BT-26) + 1 perf (PT-09) 7/7 (AC-1..AC-7; AC-7 is doc-only). Also resolves the 5 AZ-503 deferrals (AC-5, 6, 9, 10, 12).
Total 94 63/63 in-scope (100%); 2 AZ-504 ACs gated at Step 15 8/8 (100%)

Coverage shape notes (Cycle 5 — AZ-503 foundation):

  • AZ-503 was split mid-cycle (Option C, autodev Step 10 batch 2): 7 of 12 original ACs land here; 5 (AC-5, AC-6, AC-9, AC-10, AC-12) are deferred to AZ-505 with a Blocks link in Jira and an entry in _docs/02_tasks/_dependencies_table.md. The deferred rows above are marked ◐ deferred → AZ-505 so the matrix surfaces the scope boundary explicitly.
  • AZ-503 introduces no new HTTP route or wire-protocol change beyond the optional metadata.flightId field on the existing POST /api/satellite/upload. The 4 new BT scenarios (BT-19..BT-22) describe the new observable behaviors (multi-flight coexistence, deterministic id+content_sha256 on re-upload, float-rounded collapse, migration shape) without inventing a new endpoint surface.
  • The cross-repo UUIDv5 namespace constant (5b8d0c2e-7f1a-4d3b-9c5e-1f3a8e7d2b6c) is pinned in SatelliteProvider.Common/Utils/Uuidv5.cs and verified C#-side by 10 Python-generated reference vectors in Uuidv5Tests. The Python side runtime check is owned by sibling workspace gps-denied-onboard (AZ-304) — this matrix records the C# side as and the Python side as (out-of-workspace).

Coverage shape notes (Cycle 5 — AZ-504 perf-script fix):

  • AZ-504 is a one-line harness bug fix; ACs are verified by a standalone shell harness (4 cases under set -e -o pipefail) embedded in batch_01_cycle5_report.md, not by the normal unit / integration suite. There is no production code path to add a test against — the bug is entirely in scripts/run-performance-tests.sh.
  • AC-3 (PT-08 prints summary in full default-parameter run) and AC-4 (cycle-3 perf-harness leftover deleted on green full run) are gated at autodev Step 15 (Performance Test). The matrix marks them ◐ gate at Step 15 rather than until that step runs.

Coverage shape notes (Cycle 2):

  • AZ-487 AC-7 (Swagger UI Authorize) is verified programmatically (SwaggerDocument_AdvertisesBearerSecurityScheme) rather than via a real UI flow; marked ◐ doc-verified. The end-to-end browser-UI Authorize-button check remains a manual smoke before deploy.
  • AZ-487 perf NFR (< 1 ms JWT overhead) remains ◐ recorded; not separately gated. AZ-488 perf NFR (PT-08) moved from ◐ recorded (Deferred) to for batch p95 — see PT-08 row above. AZ-484 perf NFR (PT-07) moved from ◐ recorded to — see PT-07 row above. The harness work landed in AZ-492 (cycle 3) along with the Authorization: Bearer … attach that AZ-487 silently broke for the perf script.

Coverage shape notes (Cycle 4 — AZ-500 .NET 8 → .NET 10 migration):

  • All 8 AZ-500 ACs are infrastructure-level (TFM/SDK pin/Docker base/package version/build/test-suite/doc) and are verified by re-running the existing test suite, the build pipeline, and grep over manifests. No new test cases were added — the contract being tested is "the previous 78 tests still pass on the new toolchain", which Step 11 confirmed (271 unit + integration green). Total counts above are unchanged.
  • AZ-500 AC-5 (perf-script bootstrap) demoted the cycle-3 SDK-mismatch leftover to a script-bug leftover (PT-08 grep-pipefail at scripts/run-performance-tests.sh:417). The full PT-01..PT-08 perf gate moves to cycle 4 Step 15 (Performance Test). The PT-07 / PT-08 coverage rows above remain because they reflect the harness's measurement capability, not the per-cycle measurement run.
  • AZ-500 NFRs (Compatibility / Performance / Reliability / Security) propagate to existing rows rather than introducing new gates: Compatibility ⇒ cycle-3 architecture-compliance baseline (verified by Step 11 suite); Performance ⇒ Step 15 perf gate (PT-07/PT-08); Reliability ⇒ no dotnet restore failures in the migrated state (Step 11 build path); Security ⇒ Step 14 dependency-scan re-run.
  • Restriction ".NET 8.0 runtime" was rewritten to ".NET 10 runtime" — this is a supersession (toolchain bump) not a new gate, so no Choose was needed per cycle-update rule 3.

Coverage shape notes (Cycle 6 — AZ-505 inventory + HTTP/2 + leaflet covering index):

  • AZ-505 resolves the five AZ-503 deferrals (AC-5, AC-6, AC-9, AC-10, AC-12) and adds two strictly new ACs (AC-6 request validation, AC-7 contract-artifacts-in-same-commit). The deferred AZ-503 rows above are rewritten from ◐ deferred → AZ-505 to ✓ (via AZ-505) and now point at the cycle-6 test entries — the AZ-503 contract is preserved, the implementation just landed one cycle later.
  • AZ-503 AC-9's original 500 ms p95 budget for 2500 tiles was relaxed to 1000 ms during AZ-505 scoping (AZ-505 Risk 1 documents the trade-off: the inventory result set projects columns beyond tiles_leaflet_path's INCLUDE list, so a bounded heap fetch is unavoidable). The cycle-6 measured p95 is 66 ms — 15× under the relaxed budget — so the relaxation is conservative, not load-bearing.
  • AZ-503 AC-10 originally specified Heap Fetches: 0 when voting_status='trusted'. The voting layer is deferred to a future task per tile-inventory.md v1.0.0 Non-Goals (voting / trust-promotion filtering). AZ-505 AC-3 verifies the index-only access path against tiles_leaflet_path directly via EXPLAIN ANALYZE + Heap Fetches ≤ 1 assertion, which is the AC-10 intent minus the voting filter. When voting lands, AC-10's voting_status='trusted' predicate will be re-verified by the voting task.
  • AZ-505 AC-5 originally specified h2c (HTTP/2 over plaintext). Kestrel was switched to TLS+ALPN on https://+:8080 during the cycle-6 Run Tests step because HttpProtocols.Http1AndHttp2 silently downgrades to HTTP/1.1 over plaintext (no ALPN). The functional gate (multiplexing semantics) is unchanged — the test still asserts HttpResponseMessage.Version == 2.0 over 20 concurrent GETs on a single connection. The deployment caveat (dev cert vs. production TLS termination at the ingress) is documented in tile-inventory.md Non-Goals.
  • AZ-505 NFRs propagate as follows: Performance (AC-3, AC-4) ⇒ PT-09 entry (full PT-09 row in performance-tests.md); Compatibility (existing GET /tiles/{z}/{x}/{y} byte-identical) ⇒ no new test — the AZ-484 / AZ-503-foundation selection rule is unchanged, and the test that exercised it under the old (z, x, y)-keyed SELECT now exercises it under the location_hash-keyed SELECT via AC-2; Security (JWT + RequireAuthorization()) ⇒ AC-6 anonymous-401 case, BT-26.
  • Cycle-update rule check: no NFR conflicts surfaced. The 500 ms → 1000 ms perf budget relaxation between AZ-503 AC-9 and AZ-505 AC-4 is not a conflict in the cycle-update sense — AZ-503 AC-9 was explicitly deferred (◐ deferred → AZ-505) so AZ-505 owns the binding budget; AZ-503's number was a pre-implementation estimate. The matrix records both numbers and the rationale so the budget history stays auditable.