Compare commits

...

4 Commits

Author SHA1 Message Date
Oleksandr Bezdieniezhnykh b69cf5640e [AZ-487] [AZ-488] retro: cycle 2 report + structural snapshot
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful
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>
2026-05-12 00:43:27 +03:00
Oleksandr Bezdieniezhnykh e9f4e84adb [AZ-487] [AZ-488] docs: cycle 2 deploy report
Per-cycle deploy report covering AZ-487 (JWT baseline) + AZ-488 (UAV
tile batch upload). Lists all 12 cycle-2 commits already pushed to
origin/dev, recaps Steps 11-15 gate outcomes, flags three
operator-gated risks (R1 consumer Bearer-token coordination, R2
JWT_SECRET prod-distinct verification, R3 GPS-permission claim
provisioning), documents rollback (image flip; zero schema change),
and lists deferred follow-ups (PT-07/PT-08 harness + run-perf script
JWT-attach, F1 doc-folder choice).

Advances autodev pointer to Step 17 (Retrospective).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 00:37:32 +03:00
Oleksandr Bezdieniezhnykh cbbb26bd28 [AZ-487] [AZ-488] chore: cycle 2 Step 15 skip + record JWT-attach script rot
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>
2026-05-12 00:35:18 +03:00
Oleksandr Bezdieniezhnykh 5214a4a647 [AZ-487] [AZ-488] security: cycle 2 delta audit (PASS_WITH_WARNINGS)
Step 14 (Security Audit) for cycle 2 — delta scan against the cycle-1
baseline. Verdict remains PASS_WITH_WARNINGS; no Critical/High.

Scope: JWT auth boundary (AZ-487) and UAV multipart upload + ImageSharp
decode of attacker-controlled bytes (AZ-488). Both new packages
(JwtBearer 8.0.21, ImageSharp 3.1.11 in Services.TileDownloader)
checked.

Cycle-2 delta:
* 0 Critical / 0 High
* 2 Medium: F-AUTH-2 (iss/aud not validated — by design until admin
  team publishes values, AZ-487 § Constraints), F-UAV-1 (ImageSharp
  decode now runs on attacker-controlled bytes — mitigations
  sufficient; pin to GHSA subscribe-and-bump policy).
* 4 Low: F-AUTH-1 (DEV-ONLY secret in appsettings.Development.json —
  accepted), F-AUTH-3 (rate-limit gap extends to 401 floods — folds
  into cycle-1 I3), F-UAV-2 (JsonDocument.Parse on signature-validated
  claims — bounded by Kestrel header cap), D3 (JwtBearer shares D1
  patch line).
* 1 Informational: F-UAV-3 (reject reasons disclose gate structure —
  accepted UX trade-off; documented in contract).

OWASP refresh: A01 / A07 move from N/A (with caveat) to
PASS_WITH_WARNINGS (per-tenant authz absent; iss/aud + revocation
gaps tracked).

Pre-deploy operational gate added: deploy pipeline must verify
JWT_SECRET != DEV-ONLY placeholder before promoting api.

Artifacts: dependency_scan.md, static_analysis.md, owasp_review.md,
infrastructure_review.md, security_report.md — all appended with a
"Cycle 2 Delta" section preserving cycle-1 finding IDs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 00:13:58 +03:00
11 changed files with 644 additions and 13 deletions
+145
View File
@@ -0,0 +1,145 @@
# Deploy Report — Cycle 2 (AZ-487 + AZ-488)
**Date**: 2026-05-11
**Cycle**: 2
**Scope**: JWT validation baseline (AZ-487) + UAV tile batch upload endpoint with 5-rule quality gate (AZ-488).
## What is shipping
### Code changes (committed to `dev`, pushed)
| Commit | Subject |
|--------|---------|
| `42a3cc7` | `[AZ-487] [AZ-488] Cycle 2 Step 9: JWT baseline + UAV upload task specs` |
| `8e15e53` | `chore: cycle 2 step 9 task plan artifacts + step 10 state` |
| `96cd3c4` | `[AZ-487] JWT validation baseline (HS256, all endpoints)` |
| `753be43` | `[AZ-487] fix: resolve CS0104 ambiguity in AuthN tests` |
| `f64d0d7` | `[AZ-487] fix: JWT factory + tests now pass on net8.0` |
| `11b7074` | `[AZ-487] fix: integration-test JWT factory handles negative lifetime` |
| `1802d32` | `[AZ-488] UAV tile batch upload + 5-rule quality gate` |
| `dc3dabe` | `[AZ-488] fix: seed UavUploadTests coordinate counter from wall-clock` |
| `98cdcd1` | `[AZ-487] [AZ-488] docs: cycle 2 test-spec sync` |
| `e3cd388` | `[AZ-487] [AZ-488] docs: cycle 2 doc sync (task mode)` |
| `5214a4a` | `[AZ-487] [AZ-488] security: cycle 2 delta audit (PASS_WITH_WARNINGS)` |
| `cbbb26b` | `[AZ-487] [AZ-488] chore: cycle 2 Step 15 skip + record JWT-attach script rot` |
All 12 commits on `dev`, pushed to `origin/dev` as of this report.
### Database migration
**None this cycle.** AZ-487 ships zero DDL; AZ-488 reuses the AZ-484 tile-storage schema (`source`, `captured_at`, 5-column unique index) — UAV rows insert into the existing table via `ITileRepository.InsertAsync` with `source='uav'`.
### Configuration changes (operator must verify before promoting)
| Setting | Was | Now | Source |
|---------|-----|-----|--------|
| `JWT_SECRET` (env var) | unset | **must be ≥ 32 bytes, distinct from DEV placeholder** | AZ-487 — required for API to start. App throws `InvalidOperationException` at startup on missing or short value. |
| `Jwt:Secret` (appsettings) | n/a | empty in `appsettings.json`; DEV-ONLY-… placeholder in `appsettings.Development.json` | AZ-487. Env var overrides config. |
| `UavQuality:*` (appsettings) | n/a | shipped defaults (5 KiB5 MiB, 7-day age, MaxBatchSize=100, variance=10) | AZ-488. Tunable per-env without code change. |
| `docker-compose.yml``api.environment` | — | `JWT_SECRET=${JWT_SECRET}` line added | AZ-487 |
| `docker-compose.tests.yml``integration-tests.environment` | — | same `JWT_SECRET=${JWT_SECRET}` so test runner can mint matching tokens | AZ-487 |
| `.env.example` | (no JWT line) | `JWT_SECRET=` placeholder line | AZ-487 |
| Kestrel `MaxRequestBodySize` | default (30 MB) | `MaxBatchSize × MaxBytes` (500 MB worst case) | AZ-488 — see `Program.cs` |
| `FormOptions.MultipartBodyLengthLimit` / `ValueLengthLimit` | default | raised to envelope cap | AZ-488 |
### Documentation, test-spec, audit, leftover artifacts (all committed in the commits above)
- `_docs/02_document/contracts/api/uav-tile-upload.md` v1.0.0 (new, frozen) — AZ-488.
- `_docs/02_document/architecture.md`, `glossary.md`, `data_model.md`, `module-layout.md`, `modules/api_program.md`, `modules/common_configs.md`, `modules/common_dtos.md`, `modules/tests_unit.md`, `modules/tests_integration.md`, `components/03_tile_downloader/description.md`, `ripple_log_cycle2.md` — Step 13 (Update Docs).
- `_docs/02_document/tests/blackbox-tests.md`, `security-tests.md`, `resource-limit-tests.md`, `traceability-matrix.md`, `performance-tests.md` — Step 12 (Test-Spec Sync) + Step 10 PT-08 entry.
- `_docs/05_security/` (5 files, cycle-2 deltas appended) — Step 14 (Security Audit) — **PASS_WITH_WARNINGS**.
- `_docs/_process_leftovers/2026-05-11_perf-pt07-harness.md` (updated) — PT-08 follow-on + scripts/run-performance-tests.sh JWT-attach script rot.
- `_docs/03_implementation/batch_01_cycle2_report.md`, `batch_02_cycle2_report.md`, `reviews/batch_01_cycle2_review.md`, `reviews/batch_02_cycle2_review.md` — Step 10 (Implement) per-batch + review reports.
- `_docs/02_tasks/done/AZ-487_jwt_validation_baseline.md`, `AZ-488_uav_tile_upload.md` (moved from todo/).
- `_docs/02_tasks/_dependencies_table.md` (statuses updated to `Done (In Testing)`).
## Pre-deploy gate recap
| Gate | Outcome |
|------|---------|
| Step 11 — Run Tests | **PASS** — full integration suite (`scripts/run-tests.sh --full`) green against the post-cycle-2 build. Includes the new `JwtIntegrationTests` (5 scenarios) + `UavUploadTests` (7 scenarios) plus all 213 baseline unit tests and the cycle-1 AZ-484 integration tests. Fixed mid-step: AZ-488 integration `UavUploadTests._coordinateCounter` was reset on every process start, colliding with persisted Postgres data across docker-compose runs — counter now seeded from wall-clock seconds. |
| Step 12 — Test-Spec Sync | **PASS** — appended cycle-2 ACs (AZ-487 AC-1..AC-8 + AZ-488 AC-1..AC-10), NFRs, restrictions to traceability matrix; added SEC-05..SEC-11, BT-13..BT-18, RL-05..RL-07. Coverage 47/47 ACs, 8/8 restrictions. |
| Step 13 — Update Docs | **PASS** — module-layout, common_configs, common_dtos, tests_unit, tests_integration refreshed; ripple_log_cycle2.md generated (no unexpected ripple). Earlier doc work (architecture, glossary, data_model, modules/api_program, components/03_tile_downloader, contracts/api/uav-tile-upload) was already committed during Step 10. |
| Step 14 — Security Audit | **PASS_WITH_WARNINGS** — 0 Critical, 0 High. 2 new Medium (F-AUTH-2 `iss`/`aud` not validated; F-UAV-1 / F-DEPS-UAV ImageSharp decode exposure widened — both bounded by existing mitigations and tracked as follow-ups). 4 new Low + 1 Informational, all accepted or folded into existing cycle-1 remediations. OWASP A01 / A07 moved from N/A to PASS_WITH_WARNINGS. |
| Step 15 — Performance Test | **SKIPPED** (option B at the user gate). `scripts/run-performance-tests.sh` PT-01..PT-06 currently 401 against the post-AZ-487 build because it attaches no Bearer token; PT-07 + PT-08 remain Deferred per the existing leftover. Script-rot + perf-harness work tracked in `_docs/_process_leftovers/2026-05-11_perf-pt07-harness.md`. |
| `dotnet format whitespace --verify-no-changes` | Implicitly passed via Step 11 (the `run-tests.sh` invocation runs format check ahead of tests; the cycle-2 commits all came through that gate). |
## Cycle-2-specific operational risks (the deploy operator must act on these)
### **R1 — Every existing API client BREAKS the instant AZ-487 lands**
`gps-denied-onboard`, mission planner UI, and any other satellite-provider consumer currently call the API without an `Authorization` header. The moment the AZ-487 image is promoted, every such call returns HTTP 401.
**Operator action (BEFORE promoting beyond `dev`)**:
1. Confirm with `gps-denied-onboard` team that their build attaches `Authorization: Bearer <admin-API-token>` to every outbound call to satellite-provider.
2. Confirm the same with the mission planner UI team.
3. Stage the deploy through `dev` first; run an end-to-end probe from each consumer before promoting to `stage` / `prod`.
4. No fallback / bypass flag exists by design (rejected during AZ-487 planning).
### **R2 — `JWT_SECRET` must be set to a real production value**
The API throws `InvalidOperationException` at startup if `JWT_SECRET` is missing or shorter than 32 bytes (caught by SEC-08 + unit `AddSatelliteJwt_ThrowsOnMissingSecret`). HOWEVER: an operator who copies `appsettings.Development.json` verbatim into prod (or who sets `JWT_SECRET` to the literal DEV-ONLY placeholder) would still pass the 32-byte gate. The placeholder is plainly published in this repo on every clone.
**Operator action**: the deploy pipeline (or the operator running the manual promote) must verify `JWT_SECRET` is set, is ≥ 32 bytes, AND is distinct from the `DEV-ONLY-DO-NOT-USE-IN-PROD-…` literal in `appsettings.Development.json`. Recorded as a cycle-2 security recommendation in `_docs/05_security/security_report.md`.
### **R3 — UAV upload consumers need the `GPS` permission claim**
`gps-denied-onboard` (or any client that posts to `/api/satellite/upload`) must have its admin-API-issued JWT include `permissions: ["GPS"]` (or a single string `permissions: "GPS"`). Tokens with any other permission shape return HTTP 403 (SEC-10 / AZ-488 AC-6).
**Operator action**: coordinate with the admin team to confirm UAV-producer service accounts hold the `GPS` permission. If `permissions` is missing from those accounts' issued tokens, every UAV upload returns 403 even with a valid signature.
### **R4 — Postgres data volume persistence across docker-compose runs**
Discovered mid-Step 11: the local `docker-compose.yml` Postgres uses a named volume. Tile rows persist across `docker-compose down` / `up` cycles. The AZ-488 integration tests now seed coordinates from a wall-clock counter so they don't collide with prior runs — but operators doing manual test loops on a single host should either explicitly `docker-compose down -v` or accept that prior tiles will remain in the table.
This is not new behavior introduced by cycle 2 — it just became observable when AZ-488 integration tests started inserting rows. No production change required.
## Rollback plan
This deploy ships zero schema changes, so rollback is purely an image-version flip plus an operator-side config rollback:
1. Re-deploy the pre-cycle-2 image (`registry.../azaion/satellite-provider:<previous-tag>` — last cycle-1 deploy commit was `1860965`).
2. Optional: remove the `JWT_SECRET=${JWT_SECRET}` line from the deployed `docker-compose.yml` (the previous image does not read it; harmless to leave).
3. No DB rollback needed — `tiles` table is identical before and after cycle 2.
4. Inform consumers that the auth requirement is being temporarily lifted; they may keep attaching the token (harmless) or strip it.
5. If a rollback is necessary BECAUSE the UAV upload endpoint mis-behaved, the pre-cycle-2 endpoint was a 501 stub — UAV producers must again accept that uploads don't persist until a fix is shipped.
## Post-deploy verification
After the cycle-2 image is deployed and the API is bound:
1. **JWT smoke**:
```bash
curl -s -o /dev/null -w "%{http_code}\n" "$API_URL/api/satellite/tiles/latlon?Latitude=47.461747&Longitude=37.647063&ZoomLevel=18"
# Expected: 401
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $VALID_TOKEN" \
"$API_URL/api/satellite/tiles/latlon?Latitude=47.461747&Longitude=37.647063&ZoomLevel=18"
# Expected: 200
```
2. **UAV upload smoke**: `curl -X POST -H "Authorization: Bearer $VALID_GPS_TOKEN" -F 'metadata=@m.json' -F 'files=@tile.jpg' "$API_URL/api/satellite/upload"` — expect HTTP 200 with `items[0].status == "accepted"`.
3. **Swagger Bearer button**: open `/swagger`, confirm the green Authorize button is present (AZ-487 AC-7).
4. **No regression in AZ-484 reads**: `GET /api/satellite/tiles/latlon?...` for a cell that has both `google_maps` and `uav` rows — expect the row with the higher `captured_at`. Validated by integration test `MultiSourceCoexistence_AZ484_Cycle2`; do a single live spot-check on prod data.
5. **Tail Serilog** for the first hour: alert on any unhandled exception inside the auth middleware or the upload handler (both wrap their failure paths in structured logging).
## CI/CD path
`.woodpecker/02-build-push.yml` builds and pushes on push to `dev`, `stage`, `main`. All cycle-2 commits are on `dev` and pushed to `origin/dev`, so the dev-tier image is building / has built automatically.
Promote to `stage` / `main` only after the consumer-coordination items in R1 + R3 are confirmed and the JWT-secret check in R2 is part of the promote runbook.
**Push policy**: per `git-workflow.mdc`, this autodev did NOT push beyond `dev`. Manual operator action required for `stage` / `main` promotion.
## Security caveats carried into this deploy
The cycle-2 audit (Step 14) flagged 2 new Medium findings — both bounded by mitigations and tracked as follow-ups, NOT blockers:
- **F-AUTH-2** — `iss`/`aud` not validated. Coordinate with admin team to define the values; flip `ValidateIssuer`/`ValidateAudience` to `true` in a small follow-up PBI when ready.
- **F-UAV-1 / F-DEPS-UAV** — ImageSharp 3.1.11 now decodes attacker-controlled JPEGs. Today's mitigations (magic-byte gate, size cap, scoped `try/catch`) are sufficient against current advisories. Subscribe to GHSA for `SixLabors.ImageSharp`; patch within 7 days of any new CVE.
Cycle-1 carry-overs (S1, S2, S4, D1, I3, I5) are unchanged — still flagged in `_docs/05_security/security_report.md` as the pre-public-network hardening backlog.
## What's still open for cycle 2 (NOT blockers)
- **PT-07 + PT-08 perf harness** (`_docs/_process_leftovers/2026-05-11_perf-pt07-harness.md`) — Deferred since cycle 1; cycle-2 NFRs piled on but no harness work landed. Replay at the next cycle's Step 15.
- **`scripts/run-performance-tests.sh` JWT-attach** (same leftover) — script is currently broken end-to-end against the cycle-2 build; not blocking because Step 15 is skipped.
- **F1 carry-over** — task specs reference `_docs/02_document/components/01_web_api/description.md` which doesn't exist. The relevant content went into `modules/api_program.md` and `architecture.md` for now. Needs an operator decision on whether to create the stub folder or formalize "WebApi has no `components/*` folder" as the convention. Surfaced in both cycle-2 code reviews as a Low finding.
+38
View File
@@ -50,6 +50,44 @@
## Items checked clean
- SixLabors.ImageSharp 3.1.11 — newer than the patched 3.1.7 / 3.1.5 lines (CVE-2024-41131, CVE-2025-27598). No outstanding GHSA against 3.1.11 itself.
---
## Cycle 2 Delta (2026-05-11 — AZ-487 / AZ-488)
### New packages added this cycle
| Project | Package | Version | Notes |
|---------|---------|---------|-------|
| Api | `Microsoft.AspNetCore.Authentication.JwtBearer` | 8.0.21 | Added by AZ-487 (JWT validation baseline). Same patch line as `Microsoft.AspNetCore.OpenApi 8.0.21` — see D1. |
| Services.TileDownloader | `SixLabors.ImageSharp` | 3.1.11 | Added by AZ-488 to identify + decode UAV-supplied JPEGs (`Image.Identify`, `Image.Load<L8>`). Same version as the existing Api-level reference — consistent. |
| Tests (unit + integration) | `SixLabors.ImageSharp` | 3.1.11 | Added by AZ-488 to generate test fixtures. |
### New findings
#### D3 — `Microsoft.AspNetCore.Authentication.JwtBearer 8.0.21` shares the same 8.0.21 patch line as the D1-flagged OpenApi package (Low — production-risk: **Low**, exposure: not reachable)
- **Location**: `SatelliteProvider.Api/SatelliteProvider.Api.csproj` (added by AZ-487)
- **Detail**: D1 already recommends bumping `Microsoft.AspNetCore.OpenApi` to 8.0.25 because the underlying ASP.NET Core 8.0.21 runtime ships CVE-2026-26130 (SignalR DoS, not reachable in this app). Pinning a second package in the same 8.0.21 family in cycle 2 raises the cost of *not* doing the bump: every additional package implicitly hardcodes the runtime expectation. Cycle 1 disposition (`Not exploitable — no SignalR use`) still applies; cycle 2 escalation here is purely about consistency and operator clarity.
- **Disposition**: Same as D1 — bump *both* `Microsoft.AspNetCore.OpenApi` AND the new `Microsoft.AspNetCore.Authentication.JwtBearer` reference to the latest 8.0.x patch in a single PR. No separate Jira needed; fold into the D1 hardening task.
#### F-DEPS-UAV — ImageSharp decode now runs on attacker-controlled JPEG bytes (Medium — exposure increase, not a new CVE)
- **Location**: `SatelliteProvider.Services.TileDownloader/UavTileQualityGate.cs:60-78``Image.Identify(...)` and `Image.Load<L8>(...)` on `ReadOnlyMemory<byte>` originating from `POST /api/satellite/upload`.
- **Detail**: Pre-cycle-2 the only ImageSharp call site was `GoogleMapsDownloaderV2`, which decodes responses from a known-good origin (Google's tile CDN under our API key). AZ-488 introduces a second call site that decodes **arbitrary client-supplied bytes**. ImageSharp 3.1.11 is patched against the published CVE line (CVE-2024-41131 / CVE-2025-27598 — both pre-3.1.7), so no known-CVE finding exists today. The change is in *exposure*: any future ImageSharp decode bug becomes a remote-attacker primitive on the upload endpoint.
- **Mitigation already in place** (AZ-488):
- Rule 1 (magic-byte check) runs *before* ImageSharp touches the bytes, narrowing the input to `FF D8 FF`-prefixed payloads.
- Rule 2 caps per-item bytes at 5 MiB (configurable); Kestrel + FormOptions cap the envelope at `MaxBatchSize × MaxBytes = 500 MiB`.
- Decode is wrapped in `try { … } catch (UnknownImageFormatException) { … } catch (InvalidImageContentException) { … }` so a malformed JPEG produces a structured `INVALID_FORMAT` reject, not an unhandled exception.
- **Disposition**: Accept for cycle 2 — the mitigations align with current best practice for "decode untrusted images" surfaces. Track as recurring follow-up:
- Subscribe to GHSA advisories for `SixLabors.ImageSharp` and bump aggressively (within 7 days of a patch).
- Re-evaluate whether to sandbox the decode (e.g. dedicated process pool, libvips with seccomp) the next time the upload trust boundary changes.
### Cross-version sanity (post-cycle-2)
- `SixLabors.ImageSharp` is **3.1.11** in Api, Common, Services.TileDownloader, Tests, IntegrationTests — consistent across 5 csprojs. ✓
- `Microsoft.AspNetCore.*` 8.0.21 in OpenApi + the new JwtBearer — consistent within the family but lagging one patch (8.0.25 is current). Cycle-2 D3 + cycle-1 D1 share remediation.
- No new Newtonsoft.Json / Npgsql / Dapper changes this cycle.
- Newtonsoft.Json 13.0.4 — past CVE-2024-21907 fix line (13.0.1).
- Npgsql 9.0.2 — outside the 4.x / 5.x / 6.x / 7.x / 8.x ranges affected by CVE-2024-32655 (SQL injection via protocol message size overflow). 9.0.x line was never affected.
- Dapper 2.1.35 — only "advisory" hit was a dependency-check false positive for SQLite CVE-2017-10989; not a Dapper issue.
@@ -85,3 +85,28 @@
- [x] All Dockerfiles reviewed (Api + IntegrationTests)
- [x] All CI/CD configs reviewed (`.woodpecker/01-test.yml`, `02-build-push.yml`)
- [x] All env / config files reviewed (`appsettings*.json`, `.env`, `docker-compose*.yml`)
---
## Cycle 2 Delta (AZ-487 + AZ-488)
### Infra changes this cycle
- `docker-compose.yml:32` adds `JWT_SECRET=${JWT_SECRET}` to the `api` service `environment` block (AZ-487). Sourced from the host env (or `.env`).
- `docker-compose.tests.yml:21` adds the same `JWT_SECRET=${JWT_SECRET}` to the integration-test runner so tests can mint matching tokens.
- `.env.example:18` adds an empty `JWT_SECRET=` line as a deploy-time reminder.
- `.env` (workspace root, not tracked) ships a `DEV-ONLY-CHANGE-ME-…` placeholder ≥ 32 bytes for local docker-compose use.
- No new infrastructure for AZ-488 — the upload endpoint reuses the existing `./tiles/` volume; UAV files land under `./tiles/uav/{z}/{x}/{y}.jpg` inside the same mount.
### Cycle-2 verdict — clean
- **Secret distribution**: `JWT_SECRET` flows env-var → docker-compose `environment` → Kestrel `IConfiguration` → `AddSatelliteJwt`. No checked-in production secret. The DEV-ONLY value in `.env` is also explicitly labelled and is bound to the cycle-1 S4 follow-up (rotate-and-document workflow). ✓
- **`.env` in `.dockerignore` (cycle-1 I5)**: still tracked under that remediation; cycle 2 does not add a new `.env` exposure path. The `JWT_SECRET` mirroring lives in compose, not the Dockerfile, so it doesn't bake into image layers. ✓
- **No new exposed ports**: cycle 2 changes are HTTP-layer only — endpoint registration, middleware, and a multipart handler. No new listener.
- **No new external services**: ImageSharp decode is in-process; no new outbound network call introduced by AZ-487 / AZ-488.
- **No CI workflow changes**: existing `.woodpecker/01-test.yml` continues to run unit + integration tests; new cycle-2 unit + integration tests run inside the existing workflow.
### Cycle-2 operational follow-ups (NOT findings — pre-deploy verification)
1. The deploy pipeline must verify `JWT_SECRET` is set to a ≥ 32-byte value distinct from the DEV-ONLY placeholder before promoting `api`. The application throws at startup if the value is missing or short, so a misconfigured deploy fails fast — but a deploy that *promotes* the dev placeholder verbatim would still pass the 32-byte gate. Tracked in `security_report.md` cycle-2 recommendations.
2. Coordinate with admin team on `iss`/`aud` values (F-AUTH-2). When values are defined, both the `AddSatelliteJwt` call site and `.env`/compose docs must be updated together.
+32
View File
@@ -38,3 +38,35 @@
- [x] Each FAIL has at least one specific finding with evidence
- [x] N/A categories have justification + caveat
- [x] No `security_approach.md` exists in `_docs/00_problem/` to cross-reference (project has not declared explicit security requirements; this audit treats the architecture-vision statement "internal/trusted network service" as the de-facto requirement)
---
## Cycle 2 Refresh (AZ-487 + AZ-488)
Cycle 1's A01 / A07 verdicts were `N/A (with caveat)` because the service shipped without authentication. AZ-487 (JWT validation baseline) and AZ-488 (UAV upload permission policy) materially change those verdicts. The table below supersedes the cycle-1 row for A01 and A07; all other rows remain as cycle 1 left them, with cycle-2 findings appended where applicable.
| # | Category | Cycle 1 Status | Cycle 2 Status | Cycle-2 evidence |
|---|----------|----------------|----------------|------------------|
| A01 | Broken Access Control | N/A (with caveat) | **PASS_WITH_WARNINGS** | Every endpoint requires `RequireAuthorization()` (AZ-487); `POST /api/satellite/upload` requires the `GPS` permission via `PermissionsRequirement` (AZ-488). No IDOR analysis is needed because the service has no per-user data partitioning — every authenticated principal can read every tile. **Warning**: per-tenant authorization (e.g. "this UAV may only upload over its assigned region") is *not* enforced. If a future contract demands it, A01 immediately re-opens. |
| A02 | Security Misconfiguration | FAIL (S1, S2, I1, I2) | **FAIL** (unchanged + F-AUTH-1) | Cycle-2 ships a clearly-labelled DEV-ONLY JWT secret in `appsettings.Development.json`. Production override path is correct (env-var wins); deploy gate must check `JWT_SECRET`. No new cycle-1 findings resolved. |
| A03 | Supply Chain Failures | PASS_WITH_WARNINGS (D1, D2) | **PASS_WITH_WARNINGS** (+ D3, F-DEPS-UAV) | New `JwtBearer 8.0.21` package shares the D1 patch line; new ImageSharp call site widens decoder exposure (mitigations sufficient — see static_analysis.md F-UAV-1). |
| A04 | Cryptographic Failures | N/A | **PASS** | HS256 token validation uses `Microsoft.IdentityModel`'s `SymmetricSecurityKey` with `RequireSignedTokens = true` and `RequireExpirationTime = true`. The `alg=none` bypass is blocked by `RequireSignedTokens`; algorithm-confusion is bounded because only one signing key is registered. Secret length ≥ 32 bytes enforced at startup. |
| A05 | Injection | PASS | **PASS** | No new SQL / shell / template surfaces. The new JSON parse (`PermissionsAuthorizationHandler`) runs on signature-validated token bytes — see F-UAV-2 disposition. |
| A06 | Insecure Design | FAIL (S3, S4, I3) | **FAIL** (+ F-AUTH-3, F-UAV-3) | Rate limiting still absent (now also a 401-flood vector). UAV reject reasons disclose gate structure — accepted UX trade-off, flagged for operator awareness. |
| A07 | Identification & Authentication Failures | N/A (with caveat) | **PASS_WITH_WARNINGS** (+ F-AUTH-2) | HS256 with secret ≥ 32 bytes; lifetime + signature validation; ClockSkew = 30 s. **Warning**: `ValidateIssuer = false`, `ValidateAudience = false` per the suite contract — any service that holds `JWT_SECRET` can mint tokens accepted here. Track until admin team defines `iss`/`aud`. No token revocation list — leaked tokens stay valid until `exp`. |
| A08 | Software or Data Integrity Failures | PASS | **PASS** | AZ-488 file-first-then-row write order documented; same migration / CI discipline as cycle 1. |
| A09 | Security Logging Failures | PASS_WITH_WARNINGS (I4) | **PASS_WITH_WARNINGS** (unchanged) | No new logging changes; 401 responses are not currently aggregated for alerting (out of scope for internal service). |
| A10 | Mishandling of Exceptional Conditions | PASS | **PASS** | UAV decode failures wrapped in scoped `try/catch` for `UnknownImageFormatException` / `InvalidImageContentException` — produce structured `INVALID_FORMAT` rejects, no stack-trace leak. SEC-11 test verifies reject details have no path / exception-type leakage.
### Cycle-2 cross-reference
| OWASP Cat | Cycle-2 finding | Severity | Source phase |
|-----------|-----------------|----------|--------------|
| A01 | A01 status now PASS_WITH_WARNINGS (per-tenant authz absent) | — (status note) | Phase 3 |
| A02 | F-AUTH-1 — DEV-ONLY secret in `appsettings.Development.json` | Low (accepted) | Phase 2 |
| A03 | D3 — `JwtBearer 8.0.21` shares D1 patch line | Low | Phase 1 |
| A03 | F-DEPS-UAV — ImageSharp decode exposure widened | Medium | Phase 1 |
| A06 | F-AUTH-3 — rate-limit gap now also covers 401 floods | Low (recurrence of I3) | Phase 2 |
| A06 | F-UAV-3 — reject reasons disclose gate structure | Informational (accepted) | Phase 2 |
| A07 | F-AUTH-2 — `iss`/`aud` not validated; no revocation list | Medium | Phase 2 |
| (claim handler) | F-UAV-2 — `JsonDocument.Parse` on token claim values | Low | Phase 2 |
+53 -10
View File
@@ -1,20 +1,21 @@
# Security Audit Report
**Date**: 2026-05-11
**Date**: 2026-05-11 (cycle 1 baseline) · 2026-05-11 cycle 2 refresh appended below
**Scope**: Satellite Provider — full repository (Api, Common, DataAccess, Services.*, Tests, infra)
**Trigger**: `/autodev` Step 14 (Security Audit) — feature cycle 1, post-AZ-484
**Verdict**: **PASS_WITH_WARNINGS**
**Trigger**: `/autodev` Step 14 (Security Audit) — feature cycle 1, post-AZ-484; **cycle 2 delta scan added 2026-05-11 covering AZ-487 JWT validation baseline + AZ-488 UAV tile upload endpoint**
**Verdict (current, post-cycle-2)**: **PASS_WITH_WARNINGS**
## Summary
| Severity | Count |
|----------|-------|
| Critical | 0 |
| High | 0 |
| Medium | 5 |
| Low | 5 |
| Severity | Cycle 1 | Cycle 2 delta | Current total |
|----------|---------|---------------|---------------|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 0 |
| Medium | 5 | 2 (F-AUTH-2, F-DEPS-UAV) | 7 |
| Low | 5 | 4 (F-AUTH-1, F-AUTH-3, F-UAV-2, D3) | 9 |
| Info | — | 1 (F-UAV-3) | 1 |
No Critical or High findings. The verdict is `PASS_WITH_WARNINGS` driven by 5 Medium findings, all of which are well-understood configuration / hardening gaps rather than exploitable vulnerabilities in the application logic itself. **AZ-484 (the cycle's only feature change) introduced zero new findings** — it is a pure data-layer change with no auth surface, no untrusted-input handling, and no new external dependencies.
No Critical or High findings in either cycle. The verdict remains `PASS_WITH_WARNINGS`. Cycle 2's two new Medium findings (`iss`/`aud` not validated yet; ImageSharp decode exposure widened) are both bounded by mitigations already in place and tracked as follow-ups rather than gating items. **AZ-484 (cycle 1's only feature change) introduced zero new findings** — it remained a pure data-layer change.
## OWASP Top 10:2025 Assessment
@@ -118,3 +119,45 @@ This satisfies the autodev gate to proceed to Step 15 (Performance Test). The re
- [x] Every finding has remediation guidance (in per-phase reports)
- [x] Verdict matches severity logic (no Critical/High → not FAIL; >0 findings → not PASS)
- [x] No real secret values printed in any audit artifact (S4 described without echoing the API key)
---
## Cycle 2 Delta Summary (AZ-487 + AZ-488)
### What changed in cycle 2
AZ-487 introduced a JWT validation baseline (HS256, `JWT_SECRET` env var, `.RequireAuthorization()` on every endpoint, Swagger Bearer hook). AZ-488 replaced the 501 `/api/satellite/upload` stub with a multipart batch endpoint that validates JPEGs via a 5-rule quality gate and persists accepted tiles. Two new packages were added: `Microsoft.AspNetCore.Authentication.JwtBearer 8.0.21` (Api) and `SixLabors.ImageSharp 3.1.11` (TileDownloader + Tests; consistent with the existing Api-level reference).
### Findings table (cycle-2 delta)
| # | Severity | Category | Location | Title |
|------------|---------------|------------------------------------------|-------------------------------------------------------------------------|-----------------------------------------------------------------------------|
| F-AUTH-1 | Low (accepted)| A02 — Misconfiguration | `SatelliteProvider.Api/appsettings.Development.json:14` | DEV-ONLY JWT secret committed; env-var overrides; operator must verify in prod |
| F-AUTH-2 | Medium | A07 — AuthN / Identification | `Authentication/AuthenticationServiceCollectionExtensions.cs:31-32` | `iss`/`aud` not validated (intentional — suite contract has not defined values) |
| F-AUTH-3 | Low (rec. I3) | A06 — Insecure Design | every `/api/satellite/*` endpoint | No rate limiting on 401-producing paths (extends cycle-1 I3) |
| F-UAV-1 | Medium | A03 — Supply Chain (exposure) | `Services.TileDownloader/UavTileQualityGate.cs:60-95` | ImageSharp decode now runs on attacker-controlled JPEGs (mitigations OK) |
| F-UAV-2 | Low | A07 — AuthN claim parsing | `Authentication/PermissionsRequirement.cs:84-111` | `JsonDocument.Parse` on signature-validated claim values (bounded by header cap) |
| F-UAV-3 | Informational | A06 — Insecure Design (info-disclosure) | `Services.TileDownloader/UavTileQualityGate.cs` | Reject reasons disclose gate structure (accepted UX trade-off; documented in contract) |
| D3 | Low | A03 — Supply Chain | `SatelliteProvider.Api.csproj` (new JwtBearer 8.0.21) | Shares D1 patch line; same remediation |
| F-DEPS-UAV | Medium | A03 — Supply Chain (exposure) | new ImageSharp call site in TileDownloader | Documented in dependency_scan.md cycle-2 delta |
### Verdict reconciliation
- No new Critical or High findings → cycle 2 does NOT escalate the verdict.
- Two new Medium findings — both are *follow-ups under existing remediations*, not blockers:
- F-AUTH-2 waits on the admin team defining `iss`/`aud` (already flagged in AZ-487 § Constraints).
- F-UAV-1 + F-DEPS-UAV jointly say "subscribe to ImageSharp GHSA and bump aggressively" — no immediate change needed.
- F-AUTH-1 and F-UAV-3 are explicitly accepted.
- F-AUTH-3 + D3 fold into existing cycle-1 remediations (I3 rate limiting, D1 8.0.x patch bump).
**Current verdict: PASS_WITH_WARNINGS** (cycle 2 satisfies the autodev Step-14 gate; proceed to Step 15).
### New / refreshed cycle-2 recommendations
- **Pre-deploy gate (operational, NOT code)**: `deploy/SKILL.md` must verify `JWT_SECRET` is set to a ≥ 32-byte value distinct from the DEV-ONLY placeholder. Cycle-2 deploys without this verification step are gated.
- **Coordinate with admin team**: confirm expected `iss`/`aud` values; flip `ValidateIssuer` / `ValidateAudience` to `true` as soon as those values land. Track under AZ-487 § Constraints follow-up.
- **Bump 8.0.x ASP.NET Core packages together**: the next D1 hardening commit must bump both `Microsoft.AspNetCore.OpenApi` AND `Microsoft.AspNetCore.Authentication.JwtBearer` to ≥ 8.0.25.
- **ImageSharp subscribe-and-bump policy**: add to the runbook — patch within 7 days of any `SixLabors.ImageSharp` GHSA. Reconsider sandboxing if the upload endpoint is exposed beyond the trust boundary documented in architecture.md § 7.
- **Cycle-2 hardening backlog (Low priority)**:
- Pass `JsonDocumentOptions { MaxDepth = 8 }` and a max-claim-length check to `PermissionsAuthorizationHandler.TryReadJsonArray`.
- Document in `architecture.md` that reject-reason codes are NOT a security boundary.
+50
View File
@@ -19,6 +19,10 @@
| Sensitive data in logs | passwords, API keys, tokens, PII in log statements | **Clean**`GlobalExceptionHandler.cs` logs only `Method`, `Path`, `correlationId`; client gets a generic 500 + correlationId. `CorsConfigurationValidator` warning (`PermissiveDefaultWarning`) does not include secrets. There is a deliberate test fixture `GlobalExceptionHandlerTests.cs:23` that uses `"Connection string Host=secret-db;Password=hunter2 failed at line 42"` to verify the handler does NOT echo exception messages back — this is a positive control, not a finding |
| Verbose error responses | stack traces or internal details returned to clients | **Clean**`GlobalExceptionHandler` returns RFC 7807 ProblemDetails with `Detail = "An unexpected error occurred. Use the correlationId to look up the server log entry."` |
| Input validation | numeric ranges, geo coordinates, enum-like strings | See finding S3 |
| Hardcoded credentials (cycle 2 delta) | `Jwt:Secret` value in `appsettings*.json` | `appsettings.Development.json` ships a clearly-tagged DEV-ONLY placeholder; `appsettings.json` ships `""`. `JWT_SECRET` env-var overrides both. See cycle-2 finding F-AUTH-1. |
| Authentication / authorization (cycle 2 delta) | endpoint-level Authorize, custom requirement handlers, claim parsing | `Program.cs` applies `.RequireAuthorization()` on every existing endpoint and the GPS-permission policy on the new `/api/satellite/upload`. `PermissionsAuthorizationHandler` uses `string.Equals(..., Ordinal)` — no substring / case-confusion bypass. See cycle-2 findings F-AUTH-2 .. F-AUTH-4. |
| Multipart binary input (cycle 2 delta) | uploaded bytes flowing into image decode / file write | `UavTileQualityGate` runs magic-byte check before ImageSharp, wraps decode in scoped `try/catch` for `UnknownImageFormatException` / `InvalidImageContentException`. File path is built from integer coords only via `UavTileUploadHandler.BuildUavTileFilePath`. See cycle-2 finding F-UAV-1. |
| Untrusted JSON via claims (cycle 2 delta) | `JsonDocument.Parse(claim.Value)` in `PermissionsAuthorizationHandler` | Tokens are signature-validated *before* the handler runs, so the JSON parsed here is already framework-validated bytes from a verified token. Token size is bounded by Kestrel header limits. See cycle-2 finding F-UAV-2. |
## Findings
@@ -82,6 +86,52 @@
- Add `.env.example` to the repo with `GOOGLE_MAPS_API_KEY=replace-with-your-own-key-from-cloud-console` and reference it in the README setup section.
- Configure Google Cloud key restrictions: HTTP referrer allowlist (for browser keys) or IP allowlist (for server keys), and per-API quotas. Optional: per-developer keys.
---
## Cycle 2 Delta Findings (AZ-487 + AZ-488)
### F-AUTH-1 — Dev JWT secret is committed to `appsettings.Development.json` (Low — accepted by design)
- **Location**: `SatelliteProvider.Api/appsettings.Development.json:14` — `"Secret": "DEV-ONLY-DO-NOT-USE-IN-PROD-replace-with-real-secret-via-JWT_SECRET-env-var"`.
- **Description**: A 73-byte placeholder labelled DEV-ONLY ships in the repo. The value is clearly tagged; `ResolveSecretOrThrow` in `AuthenticationServiceCollectionExtensions.cs:43` reads `JWT_SECRET` from the environment first and only falls back to config when it is unset, so a production deploy with `JWT_SECRET` set overrides it.
- **Impact**: Cosmetic only — the placeholder is not a usable production secret (it is published on every git clone and would be rejected by any token verifier already in the wild). A careless operator who copies the file verbatim into prod and forgets to set `JWT_SECRET` would still pass the ≥32-byte gate, so the secret would *work* locally — that is the dependency to monitor.
- **Disposition**: Accept. Mitigation: the `DEV-ONLY-DO-NOT-USE-IN-PROD` prefix is the operator-readable warning; the deploy skill must verify `JWT_SECRET` is set before promotion.
### F-AUTH-2 — JWT issuer / audience are not validated (Medium — by design, until admin team defines values)
- **Location**: `SatelliteProvider.Api/Authentication/AuthenticationServiceCollectionExtensions.cs:31-32` — `ValidateIssuer = false`, `ValidateAudience = false`.
- **Description**: Per the suite contract `suite/_docs/10_auth.md`, expected `iss` / `aud` values are not yet defined. The validator therefore accepts any HS256 token signed with the correct shared secret — including tokens minted by other services in the suite that share the secret. This is a horizontal-trust risk: any service that holds `JWT_SECRET` can mint tokens accepted by satellite-provider as if they came from the admin API.
- **Impact**: Bounded by the secret-distribution policy. Within the trust boundary documented in cycle 1's A01 caveat ("internal/trusted-network service") this is acceptable.
- **Remediation (follow-up, NOT this cycle)**: When the admin team publishes `iss` / `aud` values, flip `ValidateIssuer = true` + `ValidIssuer = "<admin-iss>"` and the audience equivalent in `AddSatelliteJwt`. AZ-487 § Constraints already flags this as a small follow-up.
### F-AUTH-3 — No rate limiting on 401-producing paths (Low — recurrence of cycle-1 I3)
- **Location**: every `/api/satellite/*` endpoint after the AZ-487 `.RequireAuthorization()` middleware.
- **Description**: An attacker can flood `Authorization: Bearer <random>` requests; each one triggers an HMAC verification (cheap, but non-zero) and an HTTP 401 response. This re-uses the cycle-1 I3 finding ("no inbound rate limiting on any HTTP endpoint") — the JWT layer didn't introduce a new vulnerability, but it did add a new cheap-to-trigger 401 surface that magnifies I3.
- **Disposition**: Track under existing I3 remediation (wire `Microsoft.AspNetCore.RateLimiting`). No separate Jira.
### F-UAV-1 — ImageSharp decode on attacker-controlled bytes (Medium — exposure increase, mitigations sufficient today)
- **Location**: `SatelliteProvider.Services.TileDownloader/UavTileQualityGate.cs:60-95` — `Image.Identify` (Rule 3) and `Image.Load<L8>` + `Mutate(ctx => ctx.Resize)` (Rule 5).
- **Description**: Pre-AZ-488, ImageSharp only decoded responses from the Google Maps tile CDN (trusted origin). AZ-488 added a second call site that decodes arbitrary `POST /api/satellite/upload` payloads. Current ImageSharp 3.1.11 is patched (see cycle-2 dependency-scan finding F-DEPS-UAV); the change here is *exposure*, not a present vulnerability.
- **Mitigations in place**:
- Rule 1 magic-byte gate runs before any ImageSharp call (`FF D8 FF` prefix required).
- Rule 2 caps per-item size at 5 MiB; Kestrel + FormOptions cap the envelope at `MaxBatchSize × MaxBytes`.
- Decode is wrapped in `try { … } catch (UnknownImageFormatException) { … } catch (InvalidImageContentException) { … }` — malformed JPEGs produce a structured `INVALID_FORMAT` reject; no unhandled exception reaches the client.
- **Remediation**: Subscribe to `SixLabors.ImageSharp` GHSA advisories; bump within 7 days of a patch. Sandboxing (separate process / libvips + seccomp) is not warranted at the current trust boundary but should be reconsidered if the endpoint is exposed publicly. Recorded as recurring follow-up.
### F-UAV-2 — `JsonDocument.Parse` invoked on token-supplied claim values (Low — bounded by Kestrel header limits)
- **Location**: `SatelliteProvider.Api/Authentication/PermissionsRequirement.cs:84-111` — `JsonDocument.Parse(claim.Value)` when the `permissions` claim arrives as a JSON-array string.
- **Description**: `JsonDocument.Parse` has no built-in depth or size limit. A maliciously-shaped permissions claim (e.g. deeply-nested array) would consume CPU/heap during parsing. The token has already passed HS256 signature validation by the time the handler runs, so this is only exploitable by a party that holds `JWT_SECRET` — i.e. another suite service or an admin-team principal — and only inside the issued-token-size window (bounded by Kestrel's `MaxRequestHeadersTotalSize`, default 32 KiB).
- **Disposition**: Accept. The combination of `RequireSignedTokens = true` + header-size cap + ordinal-only string comparison makes a practical exploit prohibitive. Future hardening: pass `JsonDocumentOptions { MaxDepth = 8 }` to `JsonDocument.Parse` and reject claims longer than e.g. 8 KiB before parsing.
### F-UAV-3 — Reject reasons disclose gate structure (Informational — accepted trade-off)
- **Location**: `SatelliteProvider.Services.TileDownloader/UavTileQualityGate.cs` — each rule returns a distinct enum code.
- **Description**: A client (or attacker who can present a `GPS`-permission token) can map the gate by probing inputs (1×1 black image → `WRONG_DIMENSIONS`; 1 KB JPEG → `SIZE_OUT_OF_BAND`; etc.). The thresholds are also documented in the public contract `_docs/02_document/contracts/api/uav-tile-upload.md`.
- **Disposition**: Accept — UX (helping clients self-correct) outweighs the information-hiding benefit, especially since the contract is public anyway. Flagged to keep operators aware: rule thresholds are NOT a security boundary; do not move secrets into reject details.
## Self-verification
- [x] All production source directories scanned (Api, Common, DataAccess, Services.TileDownloader, Services.RegionProcessing, Services.RouteManagement)
+189
View File
@@ -0,0 +1,189 @@
# Retrospective — Cycle 2 (AZ-487 + AZ-488, 2026-05-11)
**Cycle**: 2 (Phase B feature cycle, two tasks)
**Tasks**: AZ-487 (JWT validation baseline, 2 SP) + AZ-488 (UAV tile upload + 5-rule quality gate, 8 SP — user-accepted over-cap)
**Mode**: cycle-end (autodev Step 17)
**Previous retro**: `retro_2026-05-11.md` (cycle 1, AZ-484)
## 1. Implementation Metrics
| Metric | Value | Δ vs cycle 1 |
|--------|-------|--------------|
| Tasks implemented | 2 (AZ-487, AZ-488) | +1 |
| Batches | 2 (batch 01 + batch 02 of cycle 2) | +1 |
| Avg tasks / batch | 1.0 | unchanged |
| Complexity points delivered | 10 SP (2 + 8) | +5 SP |
| Avg complexity / batch | 5.0 SP | unchanged |
| Tasks at-or-below 5 SP cap | 1 of 2 (AZ-487) | AZ-488 above cap |
| Source files modified (production) | 6 | +2 |
| Source files added (production) | 11 (JWT extension, permission requirement, UAV gate + handler + DTOs + config + request envelope) | +8 |
| Test files added | 11 (4 JWT-side unit, 1 permission unit, 3 UAV-gate/handler/path unit, 1 image factory, 1 UAV integration suite, 1 JWT integration suite) | +10 |
| Doc files added | 4 (`uav-tile-upload.md` contract, `ripple_log_cycle2.md`, `batch_01_cycle2_report.md`, `batch_02_cycle2_report.md`, plus 2 review files, deploy report) | +6 |
| Migrations added | 0 (AZ-488 reuses AZ-484 schema) | -1 |
| Frozen contracts produced | 1 (`uav-tile-upload v1.0.0`) | unchanged trend (cycle 1 also produced 1) |
| Total source diff in cycle | +2,488 / -80 lines across 28 files | — |
| Total doc diff in cycle | +1,811 / -64 lines across 35 files | — |
## 2. Quality Metrics
| Metric | Value | Δ vs cycle 1 |
|--------|-------|--------------|
| Code review pass rate | 2/2 = 100% (both PASS_WITH_WARNINGS, neither FAIL) | unchanged |
| Code review findings — Critical | 0 | unchanged |
| Code review findings — High | 0 | unchanged |
| Code review findings — Medium | 0 | unchanged |
| Code review findings — Low | 6 (2 in batch 01, 4 in batch 02 — but batch-02 F1 is the same item as batch-01 F1 carried over, so 5 distinct items) | +5 |
| Distinct findings (deduped) | 5 (F1 doc-path missing, F2 dev JWT secret committed, batch-02 F2 mutable JpegMagicBytes, F3 UAV path divergence, F4 byte[].ToArray() per write) | +5 |
| Code review FAIL count | 0 | unchanged |
| Auto-fix attempts during code review | 1 (batch 02 in-flight build fix: removed unused `using Microsoft.AspNetCore.Http;`) | +1 |
| **Post-code-review test-failure iterations** | **5** (CS0104 ambiguity, IDX12401 unit-side, IDX12401 integration-side, uniform-grey test, `_coordinateCounter` collision) | +3 vs cycle 1 (which had 2) |
| Security audit verdict | PASS_WITH_WARNINGS | unchanged |
| Security findings introduced by cycle 2 | 2 new Medium (F-AUTH-2 iss/aud unvalidated, F-UAV-1+F-DEPS-UAV ImageSharp decode surface) + 4 new Low + 1 Informational | +2 Medium |
## 3. Structural Metrics (snapshot: `structure_2026-05-11_cycle2.md`)
| Metric | Value | Δ vs cycle 1 |
|--------|-------|--------------|
| Components | 6 | 0 |
| ProjectReference edges | 12 | 0 (no new cross-component edges) |
| Cycles in import graph | 0 | 0 (still clean DAG) |
| Avg ProjectReferences / component | 2.0 | 0 |
| Architecture violations newly introduced | 0 | — |
| Architecture violations resolved | 0 | — |
| **Net architecture delta** | **0** | second clean cycle in a row |
| Frozen contracts | 2 | +1 (`uav-tile-upload v1.0.0`) |
| Contract coverage % | ~29% | +15 pp |
| External NuGet edges added | +2 (JwtBearer 8.0.21, ImageSharp 3.1.11) | — |
## 4. Efficiency Metrics
| Metric | Value | Δ vs cycle 1 |
|--------|-------|--------------|
| Blocked task count | 0 | unchanged |
| Tasks completed first attempt (post-review) | 0 of 2 — both required test-gate fix iterations | unchanged (0 of 1 in cycle 1) |
| Tasks requiring multiple post-code-review fix commits | 2 of 2 — AZ-487 had 3 separate `fix:` commits; AZ-488 had 1 wallclock fix + 1 in-flight build fix | +1 (cycle 1 had 1 of 1) |
| First-attempt-runtime-pass rate | 0 / 2 = 0% | unchanged (cycle 1 had 0/1) |
| Most-findings batch | batch 02 (4 findings, all Low) | — |
| Spec accuracy ("doc paths exist") | 0 of 2 task specs accurate — both referenced `_docs/02_document/components/01_web_api/description.md` which doesn't exist | new pattern this cycle (was AZ-484 specific in cycle 1) |
| Performance gate scenarios run | 0 of 7 active (PT-01..PT-06) + 0 of 2 deferred (PT-07, PT-08) — Step 15 skipped by user, `run-performance-tests.sh` doesn't attach JWT | 0 → 0 (still no perf runs landing) |
## 5. Patterns Identified
### Pattern 1 — Duplicate JWT mint helpers diverged then both needed identical fixes
`SatelliteProvider.Tests/TestUtilities/JwtTokenFactory.cs` and `SatelliteProvider.IntegrationTests/JwtTestHelpers.cs` were authored as two near-identical copies of the same logic during AZ-487. They were not the same file because the unit and integration test projects don't share a reference. Both contained the same `Expires < NotBefore` bug for negative-lifetime tokens; both needed the same fix in separate commits (`f64d0d7` for unit-side, `11b7074` for integration-side).
This is a classic copy-paste-diverge pattern. The unit-side helper is more mature (has happy + tampered + expired variants); the integration-side helper started as a subset and inherited the same bug. The fix-twice cost was small *this time* (one extra commit), but if the divergence had been a security-relevant difference the consequences would be larger.
### Pattern 2 — Pre-existing test-side bugs unmasked by downstream test pressure
The AZ-487 batch report shipped with status "test gate not yet run" because the implement skill committed before Step 11. Three separate AZ-487 test bugs (CS0104, IDX12401 unit, IDX12401 integration) did not surface until AZ-488 layered new tests on top and ran the full suite. The bugs were *introduced* by AZ-487 but only *observable* during AZ-488's test gate.
The fix flow worked correctly (separate `fix:` commits, no scope creep), but the metric "AZ-487 batch report: 8/8 ACs covered, PASS_WITH_WARNINGS" is misleading — at that point the new tests had not actually been executed end-to-end. A more honest framing: "AZ-487 declared done while its own tests had compile errors masked by build resolution order."
### Pattern 3 — Cycle 1 retro Action 2 not executed; perf gate now has 3 deferred items
Cycle 1 Action 2 ("couple NFR additions in test-spec with runner-script updates in the same step") was approved and the lesson appended to LESSONS.md. Cycle 2 added PT-08 to `performance-tests.md` under the *Deferred* branch sanctioned by that lesson — which is correct by the letter of the rule. But the underlying harness work (`scripts/run-performance-tests.sh` PT-07 implementation) still has not landed, and cycle 2 added a *third* perf-side item (the runner script doesn't attach JWT tokens against the new auth-required endpoints).
The improvement action helped us stop *adding undocumented Unverified* scenarios. It did NOT help us *reduce the backlog*. The PT-07 leftover is now 1 cycle old; PT-08 was born deferred; the JWT-attach rot was discovered during the cycle 2 Step 15 gate. The perf gate is currently 0 actual scenarios verified for 2 cycles running.
### Pattern 4 — Task-spec doc-path drift repeated, no decision made
Both AZ-487 and AZ-488 task specs referenced `_docs/02_document/components/01_web_api/description.md` — a folder that has never existed. Both code reviews flagged it as F1 (Low / Style). Both batches worked around it by updating `modules/api_program.md` instead. The decision item (create the stub folder vs. formalize the WebApi-lives-in-modules convention) is a 1-turn decision; until it's made, every cycle that touches WebApi will repeat the same finding and the same workaround.
### Pattern 5 — Integration test state leakage from persistent Postgres volume
`UavUploadTests` failed mid-test-run with `duplicate key value violates unique constraint "idx_tiles_unique_location_source"` because `_coordinateCounter` reset to 0 on every test-runner process start, while the Postgres named volume in `docker-compose.yml` persists tile rows across `docker-compose down` cycles. The wallclock-seeded counter (`dc3dabe`) makes per-run coordinates unique enough to avoid collision, but it's a workaround — the test isn't *isolated*, it just *probabilistically doesn't collide*.
The real fixture-isolation options are: (a) `docker-compose down -v` in `scripts/run-tests.sh`, (b) explicit DELETE-from-tiles at integration test startup, or (c) a per-test transaction with rollback. Option (b) is the smallest fix; option (a) is the easiest. Option (c) is the proper TDD answer but probably overkill for this codebase.
### Pattern 6 — 8 SP task came with measurable friction even with explicit user override
AZ-488 was 8 SP (over the 5 SP user-rule cap), and the cycle landed cleanly — but at observable cost: 1 in-flight build fix (auto-fix during implement), 1 wallclock-counter fix during test-run, 1 PT-08 NFR carried as a Deferred entry, 1 in-flight DTO-layering refactor (Common vs Api placement). None of these were *failures*. All of them were small, in-isolation harmless edits. But this is exactly the friction profile that the 5 SP guidance is designed to flag, and the cumulative cost should be visible in retros so future "should I split or accept the user override?" decisions are calibrated.
This is NOT a recommendation to never accept user-overrides on SP cap — the cycle 2 user-override was correct given AZ-488's natural unit-of-work shape. It IS a recommendation to track the over-cap friction so we can see if the pattern persists across cycles.
## 6. Comparison vs. previous retro
| Metric | Cycle 1 | Cycle 2 | Direction |
|--------|---------|---------|-----------|
| First-attempt-runtime-pass rate | 0/1 | 0/2 | unchanged at 0% |
| Code review pass rate | 100% | 100% | unchanged |
| Test-failure iterations post-review | 2 | 5 | **worsened** — but cycle 2 had a 2-batch surface area (5/2 ≈ 2.5 per batch, slightly higher than cycle 1's 2/1) |
| Net architecture delta | 0 | 0 | unchanged (good) |
| Contracts coverage % | 14% | 29% | **improved** +15 pp |
| Security findings introduced | 0 | 2 Medium | **worsened** — but both are bounded by existing mitigations |
| Performance scenarios run | 0/7 | 0/8 | unchanged at 0% (Action 2 didn't move the needle) |
**Effectiveness check on cycle 1's top-3 actions**:
- **Cycle 1 Action 1 (persisted-enum DB-roundtrip rule)** — landed in LESSONS.md and `coderule.mdc`. AZ-484-style enum bugs *did not recur* in cycle 2 (cycle 2 didn't introduce any new persisted enums, so the rule wasn't exercised but also wasn't violated). **Verdict**: cannot evaluate — no triggering work in cycle 2.
- **Cycle 1 Action 2 (NFR + runner same step)** — landed in LESSONS.md. Cycle 2 PT-08 was added as Deferred (legal per the rule). But the underlying harness work didn't progress; one new perf-rot item appeared (JWT-attach). **Verdict**: rule prevented one new failure mode, did not reduce the existing backlog.
- **Cycle 1 Action 3 (periodic doc-base-rate refresh)** — recorded as backlog item, never scheduled. The pre-existing `tests_unit.md` staleness was incidentally refreshed during cycle 2's Step 13 doc sync (we walked the file as part of touching it). **Verdict**: opportunistic fix happened; the recurring task was not scheduled.
## 7. Top 3 Improvement Actions (ranked by impact)
### Action 1 — Consolidate JWT token-mint helpers into one shared utility
**Why**: Pattern 1. Same bug existed in two near-identical files (`SatelliteProvider.Tests/TestUtilities/JwtTokenFactory.cs` and `SatelliteProvider.IntegrationTests/JwtTestHelpers.cs`); both needed independent fixes. The two files will continue to drift, and the next time they drift on something security-relevant (claim handling, signing algorithm, audience binding) the cost will be larger than one extra commit.
**Concrete action**:
- Move the canonical `JwtTokenFactory` to a shared location that both unit and integration test projects can reference. Two viable shapes:
- **Option A (preferred)**: create a new tiny test-support project `SatelliteProvider.TestSupport` (class library, no test framework) holding `JwtTokenFactory`, `UavTileImageFactory`, and similar. Reference it from both `SatelliteProvider.Tests` and `SatelliteProvider.IntegrationTests`. Cost: ~1 PBI, ~3 SP.
- **Option B (cheaper)**: have `SatelliteProvider.IntegrationTests` add a `ProjectReference` to `SatelliteProvider.Tests` and consume the existing `TestUtilities/JwtTokenFactory.cs` directly. Cost: ~1 SP. Downside: integration tests acquire a dependency on unit tests, slightly unusual.
- Either way, delete `IntegrationTests/JwtTestHelpers.cs::MintValidToken` and have the integration tests call the unified factory.
- Add a code-review checklist item to `.cursor/skills/code-review/SKILL.md` Phase 6 (Cross-Task Consistency): "If two test projects contain the same helper logic, flag as a Medium finding for consolidation."
**Owner**: whoever picks up the next test-infrastructure PBI. Recommend scheduling as `AZ-XXX Unify test JWT mint helper` at 3 SP.
**Estimated impact**: removes the dual-fix tax on every future JWT-side change; prevents drift on the surface that mints test credentials.
### Action 2 — Schedule PT-07 harness work as actual feature work, not a leftover
**Why**: Pattern 3 + Comparison checkpoint. Cycle 1 Action 2 made the spec-side honest (no more silent Unverified scenarios) but did nothing to drain the backlog. Cycle 2 added a 3rd deferred item (the script-rot on `run-performance-tests.sh`). The perf gate has been 0-of-N for two cycles running and is now actively misleading — anyone reading the gate banner sees "skipped, will check next cycle" without realizing "next cycle" never arrives.
**Concrete action**:
- Promote `_docs/_process_leftovers/2026-05-11_perf-pt07-harness.md` items into a real Jira PBI (e.g. `AZ-XXX Perf harness: implement PT-07 + PT-08 + JWT-attach in run-performance-tests.sh`). 3 SP estimate.
- The PBI's exit criteria: PT-07 + PT-08 + at least one of PT-01..PT-06 actually return a real measurement (not `Unverified`) in `_docs/06_metrics/perf_<date>.md`.
- After the PBI lands, delete the leftover file. Until it lands, *do not accept new Deferred-status NFR entries* — either the harness exists or the spec entry doesn't get added. Update `.cursor/skills/test-spec/SKILL.md` accordingly.
**Owner**: next planning loop. Sequence it before any AC that has a hard latency or throughput requirement.
**Estimated impact**: turns the perf gate from theatre into a real gate. Reduces the surface area where regressions can hide.
### Action 3 — Reset integration-test DB state between runs (real fix, not workaround)
**Why**: Pattern 5. The wallclock-seed for `_coordinateCounter` is a workaround that lets cycle 2 ship but doesn't fix the fundamental issue: integration tests are not isolated from the persistent Postgres volume. The next test class that inserts into `tiles` will have to invent its own collision-avoidance scheme. Eventually two tests will collide despite both using wallclock seeds (e.g. parallel test execution, fast back-to-back runs in CI).
**Concrete action**:
- Add a `truncate_or_reset_test_data` step to `SatelliteProvider.IntegrationTests/Program.cs` startup that explicitly clears `tiles`, `regions`, `routes`, `route_points`, `route_regions` (in FK-safe order) when `ASPNETCORE_ENVIRONMENT=Testing` (or when an explicit `--reset-state` flag is passed). The test runner has direct Postgres access already (used by `UavUploadTests` for seeding).
- Alternative: extend `scripts/run-tests.sh` to call `docker-compose down -v` between `--full` runs by default, with an explicit `--keep-state` flag for debugging. Choose this if data-reset-in-runner is too invasive.
- Document the chosen pattern in `_docs/02_document/modules/tests_integration.md` so future integration-test authors don't reinvent collision-avoidance.
**Owner**: same PBI as Action 2 ("Test infrastructure hardening: perf harness + DB isolation") OR a separate 2 SP PBI.
**Estimated impact**: enables parallel test execution in the future; eliminates the recurring "why are my inserts colliding?" debug loop.
## 8. Recommended Rule / Skill updates
| Target file | Change |
|-------------|--------|
| `.cursor/skills/code-review/SKILL.md` (Phase 6) | Add the duplicate-helper detection rule from Action 1 |
| `.cursor/skills/test-spec/SKILL.md` | Tighten the cycle-1 Action 2 rule: *"Deferred-status entries are allowed at most once per NFR. If a Deferred NFR has not landed by the end of the next cycle in which it was deferred, the spec MUST be locked to that NFR and the harness work scheduled as a real PBI before any new NFR is accepted as Deferred."* |
| `.cursor/skills/implement/SKILL.md` (test-gate step) | Add: *"A batch report's 'AC test coverage' claim is provisional until Step 11 (Run Tests) has executed end-to-end. Mark the batch as 'tests-not-yet-exercised' in its report header if the tests were committed but not run."* — guards against Pattern 2. |
| (optional) `.cursor/rules/coderule.mdc` § Testing | Add a note that integration-test data isolation is required, not optional — link to Action 3's chosen pattern when implemented. |
## 9. Decision items carried over (operator)
These are not part of the top-3 improvement actions because they're 1-turn decisions, not multi-cycle initiatives:
- **F1 (cycle 1 + cycle 2 carry)**: `_docs/02_document/components/01_web_api/description.md` doesn't exist. Choose: (a) create stub folder that defers to `modules/api_program.md`, (b) formalize "WebApi has no `components/*` folder" in the module-layout doc and update all task spec templates to point at `modules/api_program.md`. Recommendation: (b) — `modules/api_program.md` is already the de-facto canonical location.
- **R2 (cycle 2 security report)**: confirm with admin team the expected `iss` / `aud` values for production JWT tokens; flip `ValidateIssuer` / `ValidateAudience` to `true` in a small follow-up PBI when the values are known.
- **R3 (cycle 2 deploy report)**: coordinate with `gps-denied-onboard` and mission-planner-UI teams to attach Bearer tokens BEFORE promoting cycle-2 image past `dev`.
## 10. Self-verification
- [x] All cycle 2 implementation artifacts parsed (2 batch reports, 2 reviews, 0 cumulative review since K=3 not reached, 1 deploy report)
- [x] All metric categories computed
- [x] Structural snapshot written (`structure_2026-05-11_cycle2.md`); cycle 1 baseline (`structure_2026-05-11.md`) used for delta calc
- [x] Comparison vs previous retro (`retro_2026-05-11.md`) included
- [x] Top 3 actions are concrete, owner-named, SP-estimated
- [x] Suggested rule/skill updates are file-specific
- [x] Cycle-1 action effectiveness explicitly checked
@@ -0,0 +1,92 @@
# Structural Snapshot — 2026-05-11 (post-cycle 2, AZ-487 + AZ-488)
Cycle 2 delta against `structure_2026-05-11.md` (post-cycle 1 baseline). Source of truth: `_docs/02_document/module-layout.md` + on-disk `*.csproj` graph + `_docs/02_document/contracts/`.
## Components
| Layer | Component | csproj | Cycle 2 delta |
|-------|-----------|--------|---------------|
| 1 (Foundation) | Common | `SatelliteProvider.Common` | +`Configs/UavQualityConfig`, +`DTO/UavTileMetadata`, +`DTO/UavTileBatchUploadResponse` (incl. `UavTileUploadResultItem`, `UavTileUploadStatus`, `UavTileRejectReasons`, `UavTileBatchMetadataPayload`) |
| 1 (Foundation) | DataAccess | `SatelliteProvider.DataAccess` | unchanged (AZ-488 reuses AZ-484 schema; no new repository methods) |
| 3 (Application) | TileDownloader | `SatelliteProvider.Services.TileDownloader` | +`UavTileQualityGate` (impl `IUavTileQualityGate`), +`UavTileUploadHandler` (impl `IUavTileUploadHandler`), +`SixLabors.ImageSharp 3.1.11` NuGet ref |
| 3 (Application) | RegionProcessing | `SatelliteProvider.Services.RegionProcessing` | unchanged |
| 3 (Application) | RouteManagement | `SatelliteProvider.Services.RouteManagement` | unchanged |
| 4 (API / Entry) | WebApi | `SatelliteProvider.Api` | +`Authentication/AuthenticationServiceCollectionExtensions`, +`Authentication/PermissionsRequirement` (+ handler + `SatellitePermissions`), +`DTOs/UavTileBatchUploadRequest`, -`DTOs/UploadImageRequest` (deleted), +`Microsoft.AspNetCore.Authentication.JwtBearer 8.0.21` NuGet ref |
**Component count**: 6 (unchanged from cycle 1).
## Cross-Component Import Edges (compile-time `ProjectReference`)
12 ProjectReference edges, **unchanged** from cycle 1. No new cross-component edges. No new cross-sibling Layer-3 edges. Verified via `grep "ProjectReference Include" **/*.csproj`.
## Source-import sites — cycle 2 delta
| Importer | Imports from | Cycle 2 delta |
|----------|--------------|---------------|
| TileDownloader | `Common.Configs` | +1 site (`UavTileQualityGate` consumes `UavQualityConfig`) |
| TileDownloader | `Common.DTO` | +2 sites (`UavTileQualityGate` returns reject reasons; `UavTileUploadHandler` returns batch response shape) |
| TileDownloader | `SixLabors.ImageSharp` | +1 NEW external dependency edge (Image decode + L8 pixel access + Resize) |
| WebApi | `Common.Configs` | +1 site (`Program.cs` binds `UavQualityConfig` and `MapConfig` for body-size math) |
| WebApi | `Common.DTO` | +1 site (`Program.cs` accepts `UavTileBatchUploadRequest` + produces `UavTileBatchUploadResponse`) |
| WebApi | `Services.TileDownloader` | +1 site (`Program.cs` resolves `IUavTileUploadHandler`) |
| WebApi | `Microsoft.AspNetCore.Authentication.JwtBearer` | +1 NEW external dependency edge (JWT bearer middleware) |
**Internal-graph topology unchanged**; only call-site density and external NuGet edges grew.
## Graph properties
- **Cycles in component import graph**: 0 (clean DAG — unchanged)
- **Average ProjectReferences per component**: 12 / 6 = 2.0 (unchanged)
- **Max in-degree**: Common (still highest at 5)
- **Max out-degree**: WebApi (still highest at 5)
## Architecture violations
- **Newly introduced this cycle**: 0 (per batch_01_cycle2_review Phase 7 + batch_02_cycle2_review Phase 7)
- **Resolved this cycle**: 0
- **Net delta**: 0 (good — second clean cycle in a row)
## Contracts
| Contract | Path | Status | Δ this cycle |
|----------|------|--------|--------------|
| tile-storage v1.0.0 | `_docs/02_document/contracts/data-access/tile-storage.md` | frozen (cycle 1) | unchanged |
| **uav-tile-upload v1.0.0** | `_docs/02_document/contracts/api/uav-tile-upload.md` | **frozen (this cycle)** | +1 NEW |
**Contract count**: 2 (was 1).
**Contract coverage %**: ~29% (2 frozen contracts / ~7 candidate contract surfaces, vs. ~14% post-cycle-1). Improvement of +15 pp.
Remaining unspecified contract surfaces (rough enumeration):
- HTTP endpoints other than `/api/satellite/upload`: `tiles/latlon`, `tiles/mgrs`, `request` (region), `region/{id}`, `route`, `route/{id}` (5 surfaces).
- `Common.Interfaces` (`ITileService`, `IRegionService`, `IRouteService`, `IRegionRequestQueue`) — single in-process consumer; lower priority.
## Shared / Cross-Cutting (Common subfolders)
| Folder | Used by ≥2 components? | Cycle 2 delta |
|--------|------------------------|---------------|
| Common/DTO | yes | +UAV batch types now consumed by WebApi (request) + TileDownloader (response/handler return) |
| Common/Enums | yes | unchanged (TileSource still used; no new enums) |
| Common/Configs | yes | +`UavQualityConfig` consumed by TileDownloader + WebApi |
| Common/Interfaces | yes | unchanged |
| Common/Utils | yes | unchanged |
| Common/Exceptions | yes | unchanged |
| Common/Imaging | no — `TileGridStitcher` still only used by RouteManagement | **watch (cycle 3)** — borderline status carried from cycle 1; if no second consumer emerges, consider relocating to RouteManagement. |
## External NuGet edges added this cycle
| Component | Package | Version | Rationale |
|-----------|---------|---------|-----------|
| WebApi | `Microsoft.AspNetCore.Authentication.JwtBearer` | 8.0.21 | AZ-487 JWT bearer validation |
| TileDownloader | `SixLabors.ImageSharp` | 3.1.11 | AZ-488 Rule 3 (Image.Identify) + Rule 5 (Image.Load<L8> + Resize) |
| Tests | `Microsoft.AspNetCore.Authentication.JwtBearer` | 8.0.21 | unit-side `AuthenticationServiceCollectionExtensionsTests` |
| Tests | `SixLabors.ImageSharp` | 3.1.11 | `UavTileImageFactory` generates test JPEGs |
| IntegrationTests | `SixLabors.ImageSharp` | 3.1.11 | integration test image factories |
All version pins match the project's existing `Microsoft.AspNetCore.*` 8.0.21 line. ImageSharp 3.1.11 is the same line documented in cycle 1's `_docs/05_security/dependency_scan.md`.
## Auto-lesson triggers checked
- [x] Net Architecture delta this cycle (0) — no `architecture` lesson triggered
- [x] No structural metric regressed by >20% — no `architecture` / `dependencies` lesson triggered
- [x] Contract coverage % increased from ~14% to ~29% — no `architecture` lesson triggered
- [x] New external NuGet edges introduce decode + crypto attack surface — covered by cycle 2 security audit's `F-AUTH-2` and `F-UAV-1` Medium findings; NOT a new auto-lesson on top of the audit report
+6
View File
@@ -37,6 +37,12 @@ If the enum's wire string happens to match a member name case-insensitively (e.g
## Ring buffer (last 15 entries — newest at top)
- [2026-05-11] [testing] Test helpers shared across unit + integration projects must live in one consolidated location — duplicate near-identical copies will diverge and require parallel fixes (cycle 2: `JwtTokenFactory.cs` and `JwtTestHelpers.cs` had the same `Expires < NotBefore` bug fixed in separate commits).
Source: _docs/06_metrics/retro_2026-05-11_cycle2.md
- [2026-05-11] [process] Deferred-status NFR entries are allowed at most ONCE per NFR — if a Deferred NFR has not landed by the end of the cycle that follows the one in which it was deferred, the harness work must be promoted to a real PBI before any new NFR is accepted as Deferred (cycle 2 inherited cycle 1's PT-07 + added PT-08 + JWT-attach script-rot).
Source: _docs/06_metrics/retro_2026-05-11_cycle2.md
- [2026-05-11] [testing] Integration tests must explicitly reset DB state at startup — relying on wallclock seeds or "tests probably won't collide" is a workaround, not isolation; the persistent Postgres volume in docker-compose makes test data accumulation the default state (cycle 2: `UavUploadTests._coordinateCounter` collision was patched with a wallclock seed instead of a real DB-reset hook).
Source: _docs/06_metrics/retro_2026-05-11_cycle2.md
- [2026-05-11] [testing] Persisted enums need a Dapper read-roundtrip integration test — unit-testing the type handler in isolation does not prove read-side behavior (see L-001).
Source: _docs/06_metrics/retro_2026-05-11.md
- [2026-05-11] [process] NFR test-spec additions must include the runner-script implementation in the same step, or be tagged "Deferred — harness work tracked in <ticket>"; otherwise scenarios accumulate as Unverified across cycles.
+3 -3
View File
@@ -2,14 +2,14 @@
## Current Step
flow: existing-code
step: 14
name: Security Audit
step: 9
name: New Task
status: not_started
sub_step:
phase: 0
name: awaiting-invocation
detail: ""
retry_count: 0
cycle: 2
cycle: 3
tracker: jira
auto_push: true
@@ -31,6 +31,17 @@ When the next cycle's autodev runs, before any new tracker write or before re-en
The AZ-488 commit added PT-08 (UAV tile batch upload latency) to `_docs/02_document/tests/performance-tests.md` with Status `Deferred` because it reuses the same harness expansion as PT-07 (baseline capture + p95 ratio). When PT-07's runner-script scenario is implemented in step 1 above, add the PT-08 scenario in the **same commit** — the integration-test fixtures already exist (`SatelliteProvider.IntegrationTests/UavUploadTests` happy-path JWT + `UavTileImageFactory.CreateRandomJpeg`). After PT-08 runs, flip the Status line in `performance-tests.md` from `Deferred` to active. This keeps cycle 1 retro Action 2 satisfied for both NFRs.
## AZ-487 follow-on: scripts/run-performance-tests.sh attaches no Bearer token (cycle 2 carry-over)
Cycle 2's Step 15 (Performance Test) skip-decision uncovered an additional latent blocker: `scripts/run-performance-tests.sh` calls every `/api/satellite/*` endpoint without an `Authorization` header. Post-AZ-487 every such call returns HTTP 401 — the script is currently broken end-to-end, not just for PT-07/PT-08. Whoever picks up the harness work in step 1 above MUST also:
1. Read `JWT_SECRET` from the host env (the same value used by `docker-compose.yml`).
2. Mint a short-lived HS256 token at the top of the script (mirror `SatelliteProvider.IntegrationTests/JwtTestHelpers.MintValidToken` — small `python3 -c` or `jwt` CLI call; do not commit the dev placeholder secret).
3. Add the token to every `curl` invocation via `-H "Authorization: Bearer $JWT"`.
4. Skip-the-test-cleanly when `JWT_SECRET` is unset rather than running and failing on every call.
This is purely script work; no production code needs to change. Tracking it here so PT-01..PT-06 are runnable again the same cycle PT-07/PT-08 are activated.
## Tracker action (none required this cycle)
This leftover does NOT require a Jira ticket on its own — it tracks deferred process work, not user-visible scope. If the perf comparison reveals a regression next cycle, that finding will create a Jira bug; until then there is nothing to file.