AZ-491 (3 SP): eliminate the cycle-2 duplicate of JWT-minting logic
that existed in both SatelliteProvider.Tests/TestUtilities/
JwtTokenFactory.cs (unit-side) and SatelliteProvider.IntegrationTests/
JwtTestHelpers.cs (integration-side), where the same Expires <
NotBefore bug needed parallel fixes in commits f64d0d7 + 11b7074.
Option A chosen: new SatelliteProvider.TestSupport class library
(no test framework) holds the canonical JwtTokenFactory.Create /
CreateExpired / TamperSignature. Both Tests and IntegrationTests
consume it via ProjectReference; production projects (Api, Common,
DataAccess, Services.*) cannot depend on it. The notBefore-shift
workaround is preserved with an inline regression-prevention comment
back-referencing the cycle-2 fix commits.
SatelliteProvider.IntegrationTests/JwtTestHelpers.cs is stripped to
runner-only concerns: ResolveSecretOrThrow, AttachDefaultAuthorization,
and the DefaultSubject = "integration-tests" constant. Call sites in
Program.cs, JwtIntegrationTests.cs, and UavUploadTests.cs (10 sites)
switched to JwtTokenFactory.* with JwtTestHelpers.DefaultSubject
explicitly passed for the runner subject - behavior parity preserved.
Dockerfile for IntegrationTests gets the new TestSupport csproj
in its pre-restore COPY layer. Api Dockerfile unchanged (TestSupport
is NOT a production dependency).
A new code-review SKILL.md Phase 6 checklist row flags near-identical
helper logic across test projects as a Medium / Maintainability
finding with explicit cycle-2 retro back-reference, so this whole
pattern stops at one occurrence.
module-layout.md adds a TestSupport Shared/Cross-Cutting entry
documenting the production-isolation invariant. tests_unit.md +
tests_integration.md updated to describe the consolidated layout.
sln updated.
Test-suite gate (AC-2 + AC-3) deferred to Step 16 Final Test Run
per implement-skill convention. Per-batch review verdict:
PASS_WITH_WARNINGS with 1 Low (pre-existing 7.0.3 version pin
preserved verbatim from cycle-2 IntegrationTests csproj for parity;
not blocking; deferred bump).
Co-authored-by: Cursor <cursoragent@cursor.com>
AZ-495 (1 SP): formalize the modules-only documentation convention for
the WebApi component. _docs/02_document/module-layout.md now carries an
explicit Documentation Layout section anchoring WebApi docs at
modules/api_program.md; the components/06_web_api/ folder is
intentionally absent. .cursor/skills/new-task/SKILL.md Step 4 directs
future agents at the correct path. Cycle-1 + cycle-2 F1 findings in the
two batch-review files are marked RESOLVED with back-reference to
AZ-495. Cycle-2 retrospective decision-item list F1 updated.
AZ-496 (2 SP): bump Microsoft.AspNetCore.OpenApi and JwtBearer in
SatelliteProvider.Api.csproj from 8.0.21 to 8.0.25, closing CVE-
2026-26130 (SignalR DoS - not reachable in this app, but the runtime
patch is the recommended hardening per cycle-1 D1 + cycle-2 D3).
SatelliteProvider.Tests.csproj has no direct JwtBearer reference - it
consumes JwtBearer transitively via ProjectReference to Api, so no
edit needed there. Dockerfiles use floating mcr.microsoft.com/
dotnet/aspnet:8.0 / sdk:8.0 / runtime:8.0 tags which auto-resolve to
>= 8.0.25 on rebuild. Security artifacts (dependency_scan.md,
security_report.md) and current-state docs (module-layout.md,
architecture.md, modules/api_program.md, modules/tests_unit.md)
updated to reflect 8.0.25.
Batch report + code review report (verdict PASS_WITH_WARNINGS with 2
Low findings, neither blocking) written under _docs/03_implementation.
Test suite gate deferred to Step 16 (Final Test Run) per implement
skill convention. Patch-level bump within .NET 8 LTS; regression risk
very low.
Co-authored-by: Cursor <cursoragent@cursor.com>
Two integration-test failures uncovered after the initial commit:
1) GetTilesByRegionAsync outer ORDER BY referenced 'updated_at' but
the inner DISTINCT ON subquery aliased it to 'UpdatedAt' (Postgres
folds to 'updatedat'). DISTINCT ON already guarantees one row per
(latitude, longitude, ...) so the third tiebreak was unreachable;
removed it.
2) Dapper 2.1.35 silently bypasses SqlMapper.TypeHandler<T> for enum
types during read deserialization (Dapper issue #259). The
TileSourceTypeHandler worked for writes but reads fell through to
Enum.TryParse, which cannot map 'google_maps' to GoogleMaps.
Pivoted: TileEntity.Source is now a string (the wire value).
TileSource enum stays as the public producer surface in
Common.Enums; TileSourceConverter (Common.Enums) provides
ToWireValue / FromWireValue / IsValidWireValue at the boundary.
TileSourceTypeHandler deleted; registration removed from
DapperEnumTypeHandlers.RegisterAll.
tile-storage.md Inv-5 amended to document the storage choice.
_docs/LESSONS.md L-001 records the Dapper bypass for future cycles.
Full suite passes (213 unit + integration suite incl. AZ-484
AC-1..AC-5, security SEC-01..SEC-04, AZ-356/362/357).
Co-authored-by: Cursor <cursoragent@cursor.com>
Add per-source tile rows to support multi-provider imagery (Google
Maps + future UAV). Migration 013 (transactional) introduces
source/captured_at columns, backfills existing rows to
(source='google_maps', captured_at=created_at), and replaces the
4-column unique index with a 5-column index that includes source.
TileRepository:
- ColumnList includes source + captured_at
- GetByTileCoordinatesAsync returns most-recent row across sources
(ORDER BY captured_at DESC, updated_at DESC, id DESC)
- GetTilesByRegionAsync uses DISTINCT ON to pick the most-recent
tile per cell, restoring caller-facing row order
- Insert/Update upsert on the new 5-column conflict key
TileSource enum lives in Common.Enums. Snake_case wire format
(google_maps, uav) is enforced by a focused TileSourceTypeHandler
because the generic ToLowerInvariant pattern would emit
"googlemaps", violating contract v1.0.0.
TileService stamps Source=GoogleMaps + CapturedAt=UtcNow on every
new tile. Tile-storage contract is now frozen at v1.0.0.
AC coverage 7/7. New unit + integration tests cover all ACs;
existing 200 unit + 5 smoke tests preserved.
Co-authored-by: Cursor <cursoragent@cursor.com>
Phase 6 (Verification): smoke run green (format gate + 200/200
unit + integration smoke). verification_report.md captures
metric deltas vs Phase 0 baseline; all 5 ACs met, all 4
constraints honored, 0 regressions.
Phase 7 (Documentation):
- module-layout.md: corrected DataAccess->Common dependency
(was mistakenly documented as "Imports from: (none)" by
prior AZ-315 baseline; csproj reference + 7 import sites
have actually been there since AZ-309).
- architecture_compliance_baseline.md: F5 entry revised to
reflect the actual layering invariant (one-way: Common
MUST NOT import from DataAccess, but DataAccess MAY
import from Common).
- 00_discovery.md: added "Updates Since Baseline" section
enumerating the AZ-309 split + AZ-350 27-change run +
AZ-372 tooling additions; original tree kept as a
2026-05-10 snapshot.
FINAL_report: complete run summary (10 batches, 27 tasks,
3 K=3 cumulative reviews, baseline->final metric table,
remaining items, lessons learned).
Autodev state: advance Step 8 -> Step 9 (New Task);
sub_step reset to phase 0 awaiting-invocation.
Co-authored-by: Cursor <cursoragent@cursor.com>
Phase C of architecture coupling refactor (epic AZ-309). Closes the
last baseline finding (F5 — DataAccess incorrectly documented as
importing Common) and synchronizes the rest of _docs/02_document/
with the post-split project layout from AZ-312/313/314:
- module-layout.md: per-component sections for the three new csprojs
with explicit ProjectReferences and the no-cross-sibling-reference
invariant the split enforces.
- architecture.md: components and internal-communication tables
updated to show calls flow through Common interfaces.
- architecture_compliance_baseline.md: F1..F5 marked Resolved with
task IDs and commit refs; baseline summary now 0 findings.
- diagrams/components.md, components/03_tile_downloader/description.md,
modules/{common_interfaces,services_tile_service,
services_google_maps_downloader,tests_unit}.md updated for the
split, RateLimitException relocation, and new ITileService methods.
Documentation-only batch — no code, no tests, no build changes.
Epic AZ-309 complete (6 tasks across 3 batches).
Co-authored-by: Cursor <cursoragent@cursor.com>