Files
satellite-provider/_docs/03_implementation/deploy_cycle2.md
T
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

13 KiB
Raw Blame History

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.ymlapi.environment JWT_SECRET=${JWT_SECRET} line added AZ-487
docker-compose.tests.ymlintegration-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:
    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-2iss/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.