Closes the cycle-8 Medium DoS finding. Without the cap, an
authenticated caller could submit millions of bbox polygons in a
single 500 MiB request (Kestrel global limit) and saturate the
FluentValidation allocator on the validator hot path; each polygon
is ~90 bytes of JSON, so the body limit is not a useful gate.
Realistic use is 1-10 polygons per route — 50 leaves 5x headroom
while bounding the worst-case allocation.
Layers:
- CreateRouteRequestValidator: MaxPolygons = 50 + Must(...) chained
before RuleForEach so the count error fires at "geofences.polygons"
(not the leaf path).
- Unit: Validate_GeofencePolygonsTooMany_FailsCountRule.
- Integration: GeofencePolygonsTooMany_Returns400 (51 valid bbox
polygons -> HTTP 400 + errors["geofences.polygons"]).
- Contract: route-creation.md -> v1.0.1 patch (tightening an
existing range). New Inv-10, new geofence-polygons-too-many
test case, changelog row.
- Test spec: BT-29 sub-case 9b + AZ-809 AC-1b row in the
traceability matrix.
- Security report: F-AZ809-1 marked RESOLVED in cycle 8; verdict
remains PASS_WITH_WARNINGS (Lows + carry-overs unchanged).
Co-authored-by: Cursor <cursoragent@cursor.com>
Phase 12 of autodev existing-code flow — cycle-update mode of the
test-spec skill. Append cycle-8 coverage to the documentation suite
without rewriting any pre-cycle-8 content.
blackbox-tests.md
- Add 4 new BT entries (BT-28..BT-31) — one per cycle-8 endpoint:
- BT-28: Region request endpoint strict validation + OSM rename
(AZ-808 + AZ-812; 11 sub-cases through the new `RegionRequest
Validator` + the AZ-795 deserializer infra; sub-case `pos` proves
the new `lat`/`lon` names accepted, sub-case `9` proves the old
`latitude`/`longitude` rejected by `UnmappedMemberHandling.Disallow`).
- BT-29: Create route endpoint nested + cross-field validation
(AZ-809; 15 sub-cases covering nested per-point validators,
geofence cross-field invariants, and the `createTilesZip` /
`requestMaps` cross-field rule; advisory ACs 9 + 10 explicitly
NOT tested per spec).
- BT-30: UAV upload metadata multipart validation (AZ-810; 14
sub-cases across the three-layer composition: deserializer,
FluentValidation, envelope cross-field; documents the unique
`errors["metadata"]` vs `errors["metadata.items[i].field"]` key
convention for multipart endpoints).
- BT-31: GET tiles/latlon query-param validation + unknown-param
rejection (AZ-811; 8 sub-cases; sub-cases 4b + 4c prove the
novel `UnknownQueryParameterEndpointFilter` rejects both
legacy and hostile unknown query keys).
traceability-matrix.md
- Append 41 AC rows (AZ-808 AC-1..AC-8, AZ-809 AC-1..AC-10,
AZ-810 AC-1..AC-9, AZ-811 AC-1..AC-9, AZ-812 AC-1..AC-6).
- Update Coverage Summary: cycle-8 row added; Total moves from
126 tests / 75 ACs to 167 tests / 116 ACs.
- Add "Coverage shape notes (Cycle 8 ...)" section explaining the
multipart enforcement shape (AZ-810), the new query-param filter
(AZ-811), the AZ-808 + AZ-812 same-cycle coordination, and the
AZ-810 AC-9 process annotation (false-PASS by source tracing →
bound to green full-suite re-run after the test-data coord-clamp
fix in commit b763da3).
- AZ-809 AC-9 + AC-10 marked as `◐ advisory (not tested)` —
naming-consistency concerns surfaced for parent-suite team
decision.
State
- Advance autodev to Step 13 (Update Docs), sub_step phase 0
awaiting-invocation.
No production code change; no contract change; no test code change.
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>
Option B per user decision: production ships with empty Jwt.Issuer /
Jwt.Audience in appsettings.json so the API process refuses to start
unless JWT_ISSUER + JWT_AUDIENCE env vars are supplied. Development
ships with grep-friendly DEV-ONLY- placeholders so local + docker
flows keep working unchanged.
AuthenticationServiceCollectionExtensions flips ValidateIssuer +
ValidateAudience to true and wires ValidIssuer / ValidAudience via a
new ResolveRequiredOrThrow helper that all three required values
(secret, iss, aud) now share. JwtTokenFactory.Create + CreateExpired
gain optional iss / aud parameters (default null) so existing call
sites compile unchanged. JwtTestHelpers adds MintAuthenticated /
MintExpired wrappers that resolve iss + aud from env, plus
ResolveIssuerOrThrow / ResolveAudienceOrThrow. PerfBootstrap.MintToken
+ Program.cs JWT bootstrap migrated to the new surface so the perf
harness and the integration runner both validate against the same
contract.
Adds 4 fail-fast unit tests (missing/empty issuer + audience), 2
negative integration scenarios (WrongIssuer_Returns401,
WrongAudience_Returns401), and re-tags every existing integration
mint site via MintAuthenticated.
Compose, .env.example, run-tests.sh, run-performance-tests.sh all
load + export JWT_ISSUER + JWT_AUDIENCE alongside JWT_SECRET.
Resolves F-AUTH-2 (security_report.md + owasp_review.md). AC-7
(cross-repo suite/_docs/10_auth.md write) deferred — outside this
workspace; tracked in deploy_cycle2.md R3 follow-up.
Co-authored-by: Cursor <cursoragent@cursor.com>
Drains all three deferred perf-harness items in one batch:
- PT-01..PT-06 now carry Authorization: Bearer minted via the canonical
SatelliteProvider.TestSupport.JwtTokenFactory (AZ-491) — no third copy
of JWT logic in the shell.
- PT-07 implemented as cold + warm dual-pass distribution (N=20 each),
reports p50/p95 for both passes and fails if warm p95 >= cold p95.
- PT-08 implemented as 20-batch upload distribution with batch p95 gated
at the AZ-488 2000 ms target; per-item gate cost reported as derived
proxy (batch_p95 / batch_size).
New SatelliteProvider.IntegrationTests/PerfBootstrap.cs adds two CLI
short-circuit subcommands (--mint-only and --gen-uav-fixture <path>)
invoked by the shell so the perf script never inlines the JWT or
JPEG-fixture logic. The dispatch sits at the top of Program.cs Main
and runs before any HTTP / DB / readiness setup.
performance-tests.md PT-07 + PT-08 flip from Deferred to Implemented.
traceability-matrix.md PT-07 + PT-08 rows move from recorded to covered
(PT-08 partial due to per-item proxy — flagged Low in batch-4 review).
_docs/_process_leftovers/2026-05-11_perf-pt07-harness.md deleted; the
leftovers directory is now empty.
Closes cycle-2 retro Action 2; LESSONS.md [process] rule about Deferred
NFRs remains in force as a guardrail.
Also includes the previously-uncommitted cumulative review report for
cycle-3 batches 01-03 (generated at the end of batch 3 but not staged).
Co-authored-by: Cursor <cursoragent@cursor.com>