- AZ-503 (3 SP, epic AZ-483) — Tile identity → UUIDv5 deterministic id;
integer-only UPSERT with COALESCE(flight_id) per-flight separation;
content_sha256 column; POST /api/satellite/tiles/inventory bulk-list
endpoint; HTTP/2 at Kestrel edge. Cross-workspace handoff from
gps-denied-onboard (AZ-304 / AZ-316 counterpart). Supersedes the
AZ-484 UPSERT-conflict-key portion.
- AZ-504 (1 SP, epic AZ-483) — Fix scripts/run-performance-tests.sh
lines 416-417: grep -o | wc -l + set -o pipefail kills PT-08 when
rejected=0. Closes the replay obligation for the cycle-3 perf-harness
leftover (leftover deletion gated on green full perf run, AC-4).
Updates _dependencies_table.md with cycle 5 entries and records
replay attempt #4 against the perf-cycle3 leftover (PBI opened —
leftover still stays until AZ-504 lands and full perf run is green).
State advanced to Step 10 (Implement).
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds retro_2026-05-12_cycle4.md, structure_2026-05-12_cycle4.md, and
the deploy_cycle4.md report that was dropped from the Steps 12-15
sync commit. Appends 3 new lessons to LESSONS.md (12/15 ring buffer)
on transitive major-version bumps, exposed pre-existing bugs, and
single-task-cycle metric framing. State advances to cycle 5 / step 9
(awaiting next New Task invocation).
Co-authored-by: Cursor <cursoragent@cursor.com>
Coordinated cross-cutting bump: 9 csproj TFMs net8.0 -> net10.0;
global.json sdk.version 8.0.0 -> 10.0.0; all Dockerfiles + scripts/
+ .woodpecker on mcr.microsoft.com/dotnet/{sdk,aspnet,runtime}:10.0;
all Microsoft.AspNetCore.* (8.0.25) and Microsoft.Extensions.* (9.0.10)
packages -> 10.0.7. Serilog.AspNetCore retained at 8.0.3 (10.0.0
requires Serilog.Sinks.File >= 7.0.0; out of AZ-500 scope per "no
unrelated package bumps") -- documented in AGENTS.md. Swashbuckle
9.x bumped to 10.1.7 to track Microsoft.OpenApi 2.x; Program.cs +
ParameterDescriptionFilter.cs refactored for the 2.x namespace
(Microsoft.OpenApi), OpenApiSecuritySchemeReference, JsonSchemaType
enum, and IOpenApiSchema dictionary properties. Fixed implicit AC-5
prereq: scripts/run-performance-tests.sh PERF_DLL path bin/Release/
net8.0 -> net10.0. Docs sync: architecture.md + AGENTS.md.
ACs verified: AC-1..AC-4 + AC-7 + AC-8 by grep + build; AC-6 by
./scripts/run-tests.sh --full (271/271 unit tests + full integration
suite green); AC-5 short bootstrap-smoke (PERF_REPEAT_COUNT=2
PERF_UAV_BATCH_SIZE=2) succeeded at the bootstrap step (no exit 3),
PT-01..PT-07 PASS. PT-08 surfaced a pre-existing grep-pipefail bug
in run-performance-tests.sh:417 -- not an SDK problem; recorded as
follow-up in the perf-cycle3 leftover. Code review verdict:
PASS_WITH_WARNINGS (2 Medium deferred per scope discipline:
WithOpenApi ASPDEPR002 deprecation x8, CS8604 nullable in
ParameterDescriptionFilter.cs; both targeted at follow-up PBIs).
Co-authored-by: Cursor <cursoragent@cursor.com>
Closes Step 9 (New Task) of cycle 4. AZ-500 spec defines the
.NET 8 -> .NET 10 migration (TFM bump on 9 csprojs, global.json
SDK pin to 10.0.0, both Dockerfiles + run-tests.sh + woodpecker
to mcr.microsoft.com/dotnet/*:10.0, Microsoft.AspNetCore.* and
Microsoft.Extensions.* to the 10.x line, Serilog.AspNetCore to
10.x or documented 8.0.3 fallback, plus arch.md + AGENTS.md doc
sync). Closes the cycle-3 perf-harness leftover via AC-5
(bootstrap smoke after migration).
Also logs the cycle-4 perf-leftover replay attempt that
discovered the host-SDK / project-SDK mismatch and rolls the
state file from cycle 3 -> cycle 4 (Step 9 done -> Step 10
ready).
Co-authored-by: Cursor <cursoragent@cursor.com>
Cycle-3 retrospective:
- 6 tasks (AZ-491..AZ-496), 5 batches, 18 SP delivered.
- 100% code review pass rate (5/5 PASS_WITH_WARNINGS, 0 FAIL).
- 0 Critical/High/Medium review findings; 7 distinct Low.
- Security audit PASS_WITH_WARNINGS: 0 new Medium, 3 Low (all
test-only or operator-CLI), 2 Informational, 1 False Positive.
- Net Architecture delta: **-3** (F-AUTH-2 + D1 + D3 RESOLVED;
only new findings are Low test-side surfaces). First
net-negative cycle on record.
- 5 of 6 tasks completed first attempt (no post-review fix
commits). Cycle-2's 2 prior-retro actions all translated to
closed work (AZ-491 from Action 1, AZ-492 from Action 2,
AZ-493 from Action 3).
Top 3 cycle-4 improvement actions surfaced:
1. Execute the perf harness to capture PT-07/PT-08 baseline.
2. Bump TestSupport JWT pins 7.0.3 → 7.1.2+ (D4 NU1902 cleanup).
3. Add `workspace:` tag to cross-repo ACs in task-spec writing
and render them separately in the traceability matrix.
3 new ring-buffer lessons appended to _docs/LESSONS.md:
- [process] Option-B forcing functions for cross-team blockers.
- [process] ACs prescribing a measurement should also prescribe
the collection path.
- [process] Cross-repo-write ACs need workspace tags.
Structural snapshot at structure_2026-05-12_cycle3.md records the
new SatelliteProvider.TestSupport project (+2 ProjectReference edges
into it; no production-layer dependents) and the AZ-496 package
bumps (8.0.21 → 8.0.25).
Cycle 3 COMPLETE. State advanced to Step 9 (New Task) for cycle 4
per existing-code flow Re-Entry After Completion.
Co-authored-by: Cursor <cursoragent@cursor.com>
Step 11 (Run Tests) is recorded as PASS based on the implement skill's
internal Step 16 gate (./scripts/run-tests.sh --full, all-green) per
test-run/SKILL.md § Functional Mode — same runner, immediately
preceding invocation, no value in a second run.
Step 12 (Test-Spec Sync, cycle-update mode):
- traceability-matrix.md: rows added for AZ-491 AC-1..AC-6,
AZ-493 AC-1..AC-6, AZ-495 (doc convention), AZ-496 AC-1..AC-N
(dependency bump); AZ-494 AC-1/AC-2 rows now cross-reference
new SEC-12 / SEC-13 blackbox IDs.
- security-tests.md: SEC-12 (wrong iss returns 401) and SEC-13
(wrong aud returns 401) appended for AZ-494.
- environment.md: Environment Variables table extended with
GOOGLE_MAPS_API_KEY, JWT_SECRET, JWT_ISSUER, JWT_AUDIENCE,
INTEGRATION_TEST_DB_RESET. Closes a cycle-2 oversight where
JWT_SECRET was never recorded.
Step 13 (Update Docs, task mode):
- tests_unit.md: consolidated the duplicate
AuthenticationServiceCollectionExtensionsTests entry that
spanned AZ-487 + AZ-494 into one coherent block.
- ripple_log_cycle3.md created: per-task source files +
every doc that was touched (architecture, module-layout,
api_program, tests_unit, tests_integration, traceability,
performance-tests, security-tests, environment, security_report,
owasp_review, deploy_cycle2, retro_2026-05-11_cycle2). Notes
which docs were intentionally NOT touched and the open
cross-repo doc ripple (AC-7).
Autodev state advanced to Step 13 completed. Next: Step 14 Security
Audit (optional gate).
Co-authored-by: Cursor <cursoragent@cursor.com>
Final cumulative review for batches 04-05 (PASS_WITH_WARNINGS, 4 Low
findings, all non-blocking). Combined with the prior 01-03 cumulative,
this closes the per-cycle batch coverage with two PASS_WITH_WARNINGS
verdicts.
scripts/run-tests.sh --full green: format check + 13 cycle-3 unit
tests (including the 4 new AZ-494 fail-fast cases for missing /
empty iss / aud) + the full integration suite (including the 2 new
WrongIssuer / WrongAudience 401 assertions).
Fixed a stale "leave blank to fall back" comment in .env.example
that contradicted the "REQUIRED" line right above it; the integration
runner reads env vars directly with no appsettings fallback so blank
values now fail-fast.
Advanced _docs/_autodev_state.md to mark Step 10 (Implement) status:
completed.
Co-authored-by: Cursor <cursoragent@cursor.com>
Cycle-2 retrospective covering AZ-487 + AZ-488. Captures six patterns
(duplicate JWT helpers diverged then both broke; pre-existing
test bugs unmasked by downstream test pressure; cycle 1 perf-NFR
action stopped adding scenarios but did not drain backlog; doc-path
F1 carried over twice with no decision; integration test DB
isolation = wallclock workaround; 8 SP friction observable even
with user override). Top-3 improvement actions: consolidate JWT
mint helpers, promote PT-07/PT-08/JWT-attach to real PBI, real
integration DB-reset hook.
LESSONS.md ring buffer now holds 6 entries (testing x3, process x2,
estimation x1).
Structural snapshot: 6 components / 12 PR edges unchanged; contract
coverage 14% -> 29%; new external NuGet edges (JwtBearer 8.0.21 +
ImageSharp 3.1.11) tied to cycle-2 security findings.
Autodev pointer advances to cycle 3 / Step 9 New Task.
Co-authored-by: Cursor <cursoragent@cursor.com>
Step 15 (Performance Test) — skipped per gate (option B). Recording two
deferred items in the existing perf leftover:
* PT-07 + PT-08 remain Deferred. Both NFRs depend on the same
baseline-capture harness that has not landed; the integration-test
fixtures needed for PT-08 already exist (UavUploadTests +
UavTileImageFactory), so PT-08 attaches to the same harness as PT-07
when implemented.
* scripts/run-performance-tests.sh PT-01..PT-06 currently return 401
against the post-AZ-487 build because they attach no Bearer token.
Script must mint an HS256 token from JWT_SECRET at script start
before any curl call. Tracked in the leftover so PT-01..PT-06 are
runnable again the same cycle PT-07/PT-08 are activated.
No code change in this commit — leftover + state advance only.
Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces the 501 stub at POST /api/satellite/upload with a multipart
batch endpoint that ingests UAV-captured tiles, runs each item through
a 5-rule quality gate, and persists accepted tiles via the AZ-484
multi-source storage path with source='uav'.
Quality gate (in fixed order, first failure wins): JPEG format
(content-type + magic), size band 5 KiB-5 MiB, exact 256x256
dimensions, captured-at age (no future >30 s skew, no older than
7 days), luminance variance on 32x32 downsample. Closed reject-reason
enumeration in v1.0.0 contract.
Authorization: custom PermissionsRequirement / PermissionsAuthorization
Handler that reads the JWT `permissions` claim (tolerates both
repeated-string and JSON-array shapes). Endpoint protected by
RequiresGpsPermission policy; 401 without token, 403 without GPS perm.
Persistence: file-first to ./tiles/uav/{z}/{x}/{y}.jpg, then
ITileRepository.InsertAsync UPSERT (per-source UPSERT contract from
AZ-484). Per-item failures reported in response without aborting the
batch. Kestrel MaxRequestBodySize and FormOptions limits set to
MaxBatchSize x MaxBytes (default 100 x 5 MiB = 500 MiB).
New frozen contract: _docs/02_document/contracts/api/uav-tile-upload.md
v1.0.0. PT-08 NFR added to performance-tests.md as Deferred (harness
work tracked in PT-07 leftover, per AZ-488 § Risk 4).
Tests: 11 quality-gate unit tests, 5 handler unit tests, 3 file-path
unit tests, 12 permission-handler unit tests, 7 integration tests
(AC-1..AC-6, AC-8). All 253 unit tests + smoke integration suite
green.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds Microsoft.AspNetCore.Authentication.JwtBearer 8.0.21 and the
SatelliteProvider.Api.Authentication.AddSatelliteJwt extension that
validates HS256 tokens against a shared JWT_SECRET (>=32 bytes, fail
fast at startup). Every minimal-API endpoint now carries
.RequireAuthorization(); the middleware chain is UseExceptionHandler ->
UseHttpsRedirection -> UseCors -> UseAuthentication -> UseAuthorization
-> endpoints. Swagger UI gets a Bearer security definition so the
Authorize button works.
Test infrastructure: JwtTokenFactory (unit) and JwtTestHelpers
(integration) mint deterministic tokens against the same secret; the
integration test runner attaches a default Bearer token to its shared
HttpClient so existing tests continue to exercise protected endpoints.
JwtIntegrationTests adds AC-1..AC-4 and AC-7 (Swagger advertises
Bearer) end-to-end; AuthenticationServiceCollectionExtensionsTests
covers AC-5 (missing/empty/short secret fail-fast) plus env-var
precedence; JwtTokenFactoryTests covers AC-6 (claims pass through
the JwtSecurityTokenHandler.ValidateToken path JwtBearer uses).
docker-compose and scripts/run-tests.sh now propagate JWT_SECRET to
the api and integration-tests containers, with a >=32-byte guard.
.env.example documents the required keys; .env stays gitignored.
Code review verdict: PASS_WITH_WARNINGS (2 Low findings surfaced
in _docs/03_implementation/reviews/batch_01_cycle2_review.md).
Cross-component coordination: gps-denied-onboard and the mission
planner UI must attach Bearer tokens before this lands in dev.
Co-authored-by: Cursor <cursoragent@cursor.com>
Carries forward new-task research + solution drafts under
_docs/02_task_plans/uav-batch-upload/ that were not included in
the Step 9 task-spec commit (42a3cc7). Also marks the autodev
state as Step 10 in_progress for cycle 2 implementation.
Co-authored-by: Cursor <cursoragent@cursor.com>
Created two PBIs for cycle 2 under epic AZ-483 (multi-source tile
storage + UAV upload). Splits the originally-planned single AZ-485
into 2 cohesive tasks because the combined scope was ~10 SP and
JWT auth is independently shippable:
- AZ-487 (2 SP) JWT validation baseline. Adds HS256 JwtBearer
middleware against JWT_SECRET env var per the suite-level auth
contract (suite/_docs/10_auth.md). Applies .RequireAuthorization()
on all existing endpoints. Skips iss/aud validation (suite doc
does not specify). No /users/me endpoints. Hard prerequisite for
AZ-488.
- AZ-488 (8 SP, over-cap user-accepted) UAV tile upload endpoint
with batch + 5-rule quality gate. Replaces the 501 stub. Multipart
batch DTO, 5 quality rules (format, size band, dimensions,
captured_at age 7d, blank/uniform variance heuristic). UAV files
land at ./tiles/uav/{z}/{x}/{y}.jpg; google_maps grandfathered
at bare ./tiles/{z}/{x}/{y}.jpg. Per-source UPSERT via the
AZ-484 ITileRepository.InsertAsync. Sync 200 with per-item
results. Requires GPS permission claim. Produces frozen contract
uav-tile-upload.md v1.0.0.
Both Jira tickets created and linked. Dependencies table updated.
Autodev state advanced to cycle 2 Step 10 (Implement).
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>
Step-9 (new-task) cycle 1 artifacts for the AZ-483 multi-source tile
storage epic. AZ-485 (UAV upload + quality gate) deferred to a future
Step-9 loop and recorded as planned in the dependencies table.
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>
Batch 24 of 03-code-quality-refactoring run; closes the run.
AZ-375 (C22): GoogleMapsDownloaderV2.DownloadTilesGridAsync now
builds a HashSet<(int X, int Y, int Z)> once from existingTiles
and tests Contains((x, y, zoomLevel)) per cell. Removes the per-cell
FirstOrDefault tolerance scan and the unused _processingConfig
.LatLonTolerance reference at this site.
AZ-377 (C24): promote Earth + tile-pixel constants to a single
home. GeoUtils now exposes EarthRadiusMeters, EarthEquatorial
CircumferenceMeters, MetersPerDegreeLatitude as public const.
MapConfig adds DefaultTileSizePixels (const) wired as the
TileSizePixels property default. TileRepository and Google
MapsDownloaderV2 read those constants instead of duplicating
the literals 6378137, 40075016.686, 111000.0, and 256.
Tests: +6 new (DownloaderRefactorTests, extended GeoUtils
RefactorTests). 200/200 unit tests pass.
Cumulative K=3 review (batches 22-24): PASS_WITH_WARNINGS,
4 Low findings only — see
_docs/03_implementation/reviews/cumulative_review_22-24.md.
Tooling fix: scripts/run-tests.sh --unit-only path now restores
before testing (was failing on SixLabors resolution in clean
container). Stripped stray BOM from MapConfig.cs to satisfy the
.editorconfig charset gate.
Updates _dependencies_table.md to reflect all 27 03-code-quality-
refactoring tasks done; updates _autodev_state.md to refactor
phase 5 (test-sync).
Co-authored-by: Cursor <cursoragent@cursor.com>
Batch 23 of refactor 03-code-quality-refactoring (4 tasks, 5 SP):
- AZ-376 (C23): Delete unused FindExistingTileAsync from
ITileRepository / TileRepository. No callers; method also took the
obsolete `version` arg removed by C06/AZ-357.
- AZ-378 (C25): Repository _logger discipline.
TileRepository.GetTilesByRegionAsync now emits LogWarning when the
query exceeds SlowQueryThresholdMs (500 ms). RegionRepository and
RouteRepository drop the unused ILogger<TRepo> field, parameter, and
using; Program.cs DI registrations updated.
- AZ-379 (C26): Extract `private const string ColumnList` per repo
(TileRepository, RegionRepository, RouteRepository); SELECTs use
$@"SELECT {ColumnList} FROM ..." (C# 10+ const interpolation).
INSERT/UPDATE/DELETE unchanged; route_points single-site SELECT left
inline.
- AZ-380 (C27): Delete dead alias GeoUtils.CalculatePolygonDiagonalDistance.
Tests: +9 new (RepositoryRefactorTests x8, GeoUtilsRefactorTests x1)
covering each AC via reflection / file-content assertions; pattern
mirrors ToolingConfigurationTests (b22) and AcceptanceCriteriaRT2Tests
(b19). Unit suite 181 -> 190, all green. dotnet format clean.
Code review: PASS_WITH_WARNINGS (3 Low findings, all informational or
out-of-scope for this batch). See
_docs/03_implementation/reviews/batch_23_review.md.
Cumulative review counter 2/3; next K=3 review fires after batch 24.
Co-authored-by: Cursor <cursoragent@cursor.com>
Pure whitespace-only cleanup uncovered by the new format gate from the
previous commit. Verified via `git diff -w --stat`: only 4 files differ
when whitespace is ignored, and those differ only by the BOM byte.
Cleanup kinds applied across 22 source files:
- BOM removal (MapConfig.cs, SatTile.cs, GeoUtils.cs,
IntegrationTests/Program.cs)
- CRLF -> LF (IntegrationTests/Program.cs)
- Trailing whitespace on blank lines (Common, Api, DataAccess,
IntegrationTests, Services.RegionProcessing,
Services.TileDownloader)
- Final newline added (RoutePoint.cs, GeoPoint.cs, others)
After this commit `dotnet format whitespace SatelliteProvider.sln
--verify-no-changes` exits 0; AC-1 is enforceable from `scripts/
run-tests.sh` going forward.
Also lands the batch 22 report, code-review report
(PASS_WITH_WARNINGS, 2 Low findings — both deferred per spec),
dependency-table status update (AZ-372 -> Done (In Testing)), task
archive (todo/ -> done/), and autodev state update.
Co-authored-by: Cursor <cursoragent@cursor.com>
F1 (Low/Maintainability): module-layout.md docs stale on DataAccess
project reference after AZ-370; tracked for refactor Phase 7.
F2 (Low/Maintainability): redundant builder.Services.AddHttpClient()
in Program.cs after AZ-374; deferred per batch 21 design note.
No Critical/High findings; auto-chain to next batch (AZ-372).
Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces bare strings with two enums in Common/Enums/:
RegionStatus { Queued, Processing, Completed, Failed }
RoutePointType { Start, End, Action, Intermediate }
Adds a Dapper EnumStringTypeHandler<T> (DataAccess/TypeHandlers/)
that round-trips enums to/from lowercase strings, registered once
at startup via DapperEnumTypeHandlers.RegisterAll(). DataAccess now
references Common (project ref) so entities can carry the enum types.
Sites converted: RegionService (5), RouteProcessingService (3),
RoutePointGraphBuilder (4), entity Status/PointType columns. Log
message and summary file format preserved via .ToLowerInvariant().
API JSON contract preserved by adding JsonStringEnumConverter with
JsonNamingPolicy.CamelCase to the http JSON options — single-word
enum members serialize to the same lowercase strings as before.
DTO renamed: Common.DTO.RegionStatus -> RegionStatusResponse to
free the RegionStatus name for the new enum (forced by the task's
explicit enum name); the renamed DTO has no public-API impact at
the JSON wire level. Stale doc references updated.
AC RT2 in _docs/00_problem/acceptance_criteria.md now lists all 4
point types (start/end/action/intermediate).
Tests: 171 / 171 unit + 5 / 5 smoke green (was 141 + 5; +30 new tests
covering type handler round-trip, set/parse, unknown-value rejection,
idempotent registration, and the AC RT2 doc check).
Co-authored-by: Cursor <cursoragent@cursor.com>