Files
satellite-provider/_docs/03_implementation/deploy_cycle3.md
T
Oleksandr Bezdieniezhnykh 65cdfae970 [AZ-491] [AZ-492] [AZ-493] [AZ-494] [AZ-495] [AZ-496] Cycle 3 Step 15 skip + Step 16 deploy report
Step 15 (Performance Test): SKIPPED. User skipped the optional gate
question. Per meta-rule.mdc, performance tests require explicit
approval; a skipped question is not approval. Recorded as leftover at
_docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md
for replay at next /autodev invocation.

Step 16 (Deploy): COMPLETED. Produced deploy_cycle3.md mirroring the
cycle-2 shape. Covers:
  - 9 cycle-3 commits + zero DB migration
  - Config changes (JWT_ISSUER/JWT_AUDIENCE env vars w/ fail-fast,
    8.0.25 package bumps, new TestSupport project)
  - Pre-deploy gate recap (Steps 11-15)
  - Cycle-3 operational risks R1-R4 (admin-team iss/aud confirm,
    cross-repo doc deferral, cycle-2 R1/R3 carry-overs, test-runner
    log line)
  - Rollback plan, post-deploy verification (incl. wrong-iss / wrong-
    aud smoke probes), CI/CD push policy
  - Resolved this cycle: F-AUTH-2, D1, D3, PT-07/PT-08 leftover
  - Follow-up backlog: D4 NU1902 bump, F-DBR-2 third guard, F-PERF-1
    token-history hardening, image-fixture consolidation, AC-7 cross-
    repo write, no-revocation-list residual

Next: Step 17 (Retrospective, cycle-end mode).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 03:42:10 +03:00

17 KiB

Deploy Report — Cycle 3 (AZ-491 / AZ-492 / AZ-493 / AZ-494 / AZ-495 / AZ-496)

Date: 2026-05-12 Cycle: 3 Scope: Test-infrastructure + dependency + security hardening from the cycle-2 retrospective backlog:

  • AZ-491 — consolidate JWT test-mint helpers into a single SatelliteProvider.TestSupport source of truth (replaces cycle-2 duplicate factories).
  • AZ-492 — wire perf harness PT-01..PT-06 JWT-attach + implement PT-07 (region cold/warm) and PT-08 (UAV batch upload) scenarios.
  • AZ-493 — integration test DB reset hook with two-guard safety (env + Host allowlist); replaces the AZ-488 wall-clock coordinate workaround.
  • AZ-494 — JWT iss / aud validation enabled (Option B per user decision); fail-fast at startup if env vars missing.
  • AZ-495 — formalise doc folder convention for new tasks.
  • AZ-496 — bump Microsoft.AspNetCore.OpenApi + Microsoft.AspNetCore.Authentication.JwtBearer 8.0.21 → 8.0.25 (closes cycle-1 D1 + cycle-2 D3 supply-chain findings).

What is shipping

Code changes (committed to dev, pushed)

Commit Subject
76076cb [AZ-491] [AZ-492] [AZ-493] [AZ-494] [AZ-495] [AZ-496] Cycle 3 Step 9: 6 task specs
9cfd80b [AZ-495] [AZ-496] Cycle 3 batch 1: doc convention + AspNetCore 8.0.25
c396740 [AZ-491] Cycle 3 batch 2: consolidate JWT test-mint helpers into TestSupport
745f484 [AZ-493] Cycle 3 batch 3: integration test DB-reset hook
080441d [AZ-492] Cycle 3 batch 4: perf harness PT-07 + PT-08 + JWT-attach
f979e18 [AZ-494] Enable JWT iss/aud validation with fail-fast startup
495605f [AZ-494] [AZ-492] Cycle 3 Step 16: full test suite green; close batches
e42bf62 [AZ-491] [AZ-492] [AZ-493] [AZ-494] [AZ-495] [AZ-496] Cycle 3 Steps 11-13: test-spec sync + ripple log
314d1de [AZ-491] [AZ-492] [AZ-493] [AZ-494] [AZ-496] Cycle 3 Step 14: security audit refresh

All 9 commits on dev, pushed to origin/dev as of this report.

Database migration

None this cycle. All cycle-3 work is at the application / test-infrastructure / config layer. The tiles table schema is unchanged.

Configuration changes (operator must verify before promoting)

Setting Was Now Source
JWT_ISSUER (env var) unset MUST be set to admin-team-confirmed value AZ-494 — required for API to start. App throws InvalidOperationException at startup if missing or whitespace.
JWT_AUDIENCE (env var) unset MUST be set to admin-team-confirmed value AZ-494 — same fail-fast contract as JWT_ISSUER / JWT_SECRET.
Jwt:Issuer (appsettings) n/a empty in appsettings.json; DEV-ONLY-iss-admin-azaion-local in appsettings.Development.json AZ-494. Env var overrides config.
Jwt:Audience (appsettings) n/a empty in appsettings.json; DEV-ONLY-aud-satellite-provider in appsettings.Development.json AZ-494. Env var overrides config.
appsettings.jsonJwt.Issuer / Jwt.Audience absent empty string AZ-494 — empty prod values guarantee the fail-fast contract triggers.
Microsoft.AspNetCore.OpenApi PackageReference 8.0.21 8.0.25 AZ-496 — closes cycle-1 D1 (CVE-2026-26130 SignalR DoS; not reachable in this app but the bump is hygiene).
Microsoft.AspNetCore.Authentication.JwtBearer PackageReference 8.0.21 8.0.25 AZ-496 — closes cycle-2 D3 (same 8.0.21 patch line).
docker-compose.ymlapi.environment (already had JWT_SECRET) adds JWT_ISSUER=${JWT_ISSUER} + JWT_AUDIENCE=${JWT_AUDIENCE} AZ-494
docker-compose.tests.ymlintegration-tests.environment (already had JWT_SECRET) adds same JWT_ISSUER + JWT_AUDIENCE pass-through + ASPNETCORE_ENVIRONMENT=Testing + INTEGRATION_KEEP_STATE=${…:-} AZ-493 + AZ-494
.env.example (no iss/aud lines) adds JWT_ISSUER + JWT_AUDIENCE placeholder lines + documents fail-fast contract AZ-494
scripts/run-tests.sh (loaded JWT_SECRET) also loads + fail-fasts on JWT_ISSUER + JWT_AUDIENCE; --keep-state flag added AZ-493 + AZ-494
scripts/run-performance-tests.sh (script-rot: no JWT attach, perf-test stubs) full rewrite: JWT attach across PT-01..PT-06, PT-07 cold/warm distribution, PT-08 batch distribution, AZ-494 iss/aud handling AZ-492 + AZ-494
SatelliteProvider.TestSupport project (new) n/a added by AZ-491; consumed by SatelliteProvider.Tests + SatelliteProvider.IntegrationTests. IsPackable=false; never ships in a production container image. AZ-491

Documentation, test-spec, audit, leftover artifacts (all committed in the commits above)

  • _docs/02_document/architecture.md, module-layout.md, modules/api_program.md, modules/tests_unit.md, modules/tests_integration.md — Step 13 (Update Docs).
  • _docs/02_document/tests/{traceability-matrix,security-tests,environment,performance-tests}.md — Step 12 (Test-Spec Sync) + AZ-492's PT-07/PT-08 entries.
  • _docs/02_document/ripple_log_cycle3.md — Step 13 ripple log (new).
  • _docs/05_security/{dependency_scan,static_analysis,owasp_review,infrastructure_review,security_report}.md — Step 14 (Security Audit), cycle-3 deltas appended. PASS_WITH_WARNINGS.
  • _docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md (new) — Step 15 deferral.
  • _docs/03_implementation/batch_0{1,2,3,4,5}_cycle3_report.md + matching reviews/batch_0{1,2,3,4,5}_cycle3_review.md — Step 10 per-batch + review reports.
  • _docs/03_implementation/cumulative_review_batches_0{1-3,4-5}_cycle3_report.md — Step 10 cumulative reviews (every 3 batches).
  • _docs/02_tasks/done/AZ-49{1,2,3,4,5,6}_*.md (moved from todo/).
  • _docs/02_tasks/_dependencies_table.md (statuses updated to In Testing).
  • _docs/_process_leftovers/2026-05-11_perf-pt07-harness.md — deleted (resolved by AZ-492).

Pre-deploy gate recap

Gate Outcome
Step 11 — Run Tests PASSscripts/run-tests.sh --full green against the post-cycle-3 build (log at /tmp/run-tests-cycle3-step16.log, 8025 lines, "All tests passed (mode=full)"). 4 new AZ-494 fail-fast unit tests + 2 new integration scenarios (JwtIntegrationTests.WrongIssuer_Returns401 line 650, WrongAudience_Returns401 line 653) PASS. Full integration suite (incl. all SEC-, BT-, RL- scenarios from cycle 1+2) green. Test-run skill execution skipped per its § Functional Mode (same runner, immediately preceding invocation as Implement skill's Step-16 internal gate).
Step 12 — Test-Spec Sync PASS — cycle-update mode appended traceability rows for AZ-491 AC-1..AC-6, AZ-493 AC-1..AC-6, AZ-494 AC-1..AC-7 (AC-7 ◐ deferred), AZ-495, AZ-496; SEC-12 (wrong iss → 401) + SEC-13 (wrong aud → 401) added to security-tests.md; environment.md env-var table extended with 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 PASS — most module / architecture / security docs were already updated inline during batches; tests_unit.md had a duplicate AuthenticationServiceCollectionExtensionsTests entry consolidated; ripple_log_cycle3.md generated.
Step 14 — Security Audit PASS_WITH_WARNINGS — 0 Critical, 0 High, 0 new Medium. F-AUTH-2 (cycle-2 Medium) RESOLVED by AZ-494; D1 (cycle-1) + D3 (cycle-2) supply-chain RESOLVED by AZ-496. New cycle-3 findings: D4 (Low, test-only NU1902 7.0.3 < 7.1.2), F-AUTH-3 (Informational, test-runner-only iss/aud log), F-AUTH-4 (Informational, by-design DEV-ONLY placeholders), F-DBR-1 (false positive, hard-coded TRUNCATE table list), F-DBR-2 (Low, operator-bypassable two-guard model), F-PERF-1 (Low, 4-hour GPS token on stdout).
Step 15 — Performance Test SKIPPED (user skipped the optional gate question). The cycle-3 perf-harness work (AZ-492 PT-07/PT-08) is runnable but was not executed. Tracked in _docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md.
dotnet format whitespace --verify-no-changes Implicitly passed via Step 11 (first line of the test-run log: "Step 0: dotnet format whitespace --verify-no-changes").

Cycle-3-specific operational risks (the deploy operator must act on these)

R1 — JWT_ISSUER + JWT_AUDIENCE must be set to admin-team-confirmed prod values

The API throws InvalidOperationException at startup if either is missing or whitespace (caught by 4 new unit tests AddSatelliteJwt_ThrowsOn{Missing,Empty}{Issuer,Audience} and verified by SEC-12 / SEC-13 integration scenarios). HOWEVER: an operator who copies appsettings.Development.json verbatim into prod, or who sets JWT_ISSUER / JWT_AUDIENCE to the literal DEV-ONLY-iss-admin-azaion-local / DEV-ONLY-aud-satellite-provider placeholders, would pass the non-empty check. The placeholders are deliberately published in this repo (Option B forcing function).

Operator action:

  1. Confirm with the admin team the exact iss value the admin API stamps into prod tokens.
  2. Confirm the aud value satellite-provider should require.
  3. Set both as environment variables in the deploy environment (NOT in committed config).
  4. Grep the rendered deploy environment for DEV-ONLY- and confirm zero hits before promoting beyond dev.

R2 — Cross-repo doc update (AZ-494 AC-7) still pending

The cycle-3 work did NOT touch suite/_docs/10_auth.md in the parent monorepo. AZ-494 AC-7 says the suite-level auth contract should either document the iss/aud values or note that satellite-provider validates them locally. This write is outside satellite-provider's autodev workspace boundary.

Operator action: ask the suite repo's owner to add a paragraph to suite/_docs/10_auth.md noting that satellite-provider validates iss + aud against JWT_ISSUER / JWT_AUDIENCE env vars (admin-team-supplied) with fail-fast at startup.

R3 — Cycle-2 R1 / R3 follow-ups remain operationally relevant

The cycle-2 R1 (gps-denied-onboard must attach Bearer tokens) and R3 (UAV consumers need GPS permission) are unchanged from cycle 2. Now additionally: those tokens must also have iss + aud matching the operator-set prod values, or they 401 (post-AZ-494). The admin team is the single source of truth for both the secret-distribution and the iss/aud values.

R4 — DEV-ONLY iss/aud values are NOT secrets but are still operator-visible in test logs

The integration-test runner's bootstrap line (Auth : JWT_SECRET resolved (… bytes); iss=...; aud=...) prints the resolved values at startup. In local / CI runs against the DEV-ONLY placeholders this is harmless; in any production-pointing test run (NOT recommended) it would echo prod values into the test log. The production API itself does NOT log iss/aud — verified by repo grep.

Operator action: do not point the integration-test runner at production env vars. If a smoke test against a live prod API is needed, mint a token manually with the prod values and run a curl probe — do not run the full test-runner image against prod.

Rollback plan

This deploy ships zero schema changes. Rollback is purely an image-version flip plus a config rollback:

  1. Re-deploy the pre-cycle-3 image (last cycle-2 deploy commit was b69cf56).
  2. Optionally remove the JWT_ISSUER=${JWT_ISSUER} + JWT_AUDIENCE=${JWT_AUDIENCE} lines from the deployed docker-compose.yml (the previous image does NOT validate them, so leaving them is harmless).
  3. No DB rollback needed — tiles table is identical before and after cycle 3.
  4. Inform consumers (if any AZ-494-specific debugging is needed) that iss/aud claim values are no longer enforced; they may continue to attach matching claims (harmless) or stop.
  5. The 8.0.25 → 8.0.21 ASP.NET Core downgrade reverts the AZ-496 hygiene bump; this is acceptable for an emergency rollback (the CVE-2026-26130 path is not reachable in this app).

Post-deploy verification

After the cycle-3 image is deployed and the API is bound:

  1. JWT iss/aud smoke (NEW, AZ-494):

    # Token with WRONG issuer must be rejected:
    curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TOKEN_WITH_WRONG_ISS" \
      "$API_URL/api/satellite/tiles/latlon?Latitude=47.461747&Longitude=37.647063&ZoomLevel=18"
    # Expected: 401
    
    # Token with WRONG audience must be rejected:
    curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TOKEN_WITH_WRONG_AUD" \
      "$API_URL/api/satellite/tiles/latlon?Latitude=47.461747&Longitude=37.647063&ZoomLevel=18"
    # Expected: 401
    
    # Token with MATCHING iss + aud must be accepted (cycle-2 JWT smoke still works):
    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. Fail-fast smoke: deliberately deploy ONCE with JWT_ISSUER unset to confirm the API refuses to start. Then set the env var and confirm the API binds.

  3. Cycle-2 verifications still apply: JWT smoke (anonymous → 401, valid token → 200), UAV upload smoke (GPS permission → 200; no permission → 403), Swagger Bearer button visible.

  4. No regression in tile reads: same as cycle-2 R4 verification — GET /api/satellite/tiles/latlon?... against a multi-source cell.

  5. Tail Serilog for the first hour after promote: alert on InvalidOperationException (would indicate fail-fast triggered after a successful first bind, which means env vars went away mid-run — unusual but worth knowing) and on any spike in 401s (would indicate the prod iss/aud value drifted from what admin-API is stamping).

CI/CD path

.woodpecker/02-build-push.yml (unchanged this cycle) builds and pushes on push to dev, stage, main. All 9 cycle-3 commits are on dev and pushed to origin/dev, so the dev-tier image is being built / has built automatically.

Promote to stage / main only after R1 (admin-team-confirmed iss/aud values) is satisfied AND the R3 cycle-2 consumer coordination is still in place.

Push policy: per git-workflow.mdc, this autodev pushed only to dev. Manual operator action required for stage / main promotion.

Resolved-this-cycle backlog items

  • F-AUTH-2 (cycle-2 Medium) — iss/aud not validated. RESOLVED in AZ-494. Operational gate replaces the code-level gate.
  • D1 (cycle-1 Medium) — Microsoft.AspNetCore.OpenApi on the 8.0.21 patch line. RESOLVED in AZ-496 — bumped to 8.0.25.
  • D3 (cycle-2 Low) — Microsoft.AspNetCore.Authentication.JwtBearer on the same 8.0.21 line. RESOLVED in AZ-496.
  • PT-07 / PT-08 leftover (cycle-2 leftover) — perf-harness work for Authorization: Bearer attach + PT-07 + PT-08 scenarios. RESOLVED in AZ-492 — harness now runnable; only execution itself is now-deferred (separate cycle-3 leftover).

Cycle-1 carry-overs (S1, S2, S4, D1 closed, D2, I1, I3, I5) — S1, S2, S4, I1, I3, I5 unchanged; still flagged in _docs/05_security/security_report.md as the pre-public-network hardening backlog.

Cycle-3 follow-ups (NOT cycle-blocking; tracked for future PBIs)

  • D4 (cycle-3 Low, test-only) — bump System.IdentityModel.Tokens.Jwt 7.0.3 + Microsoft.IdentityModel.Tokens 7.0.3 to ≥ 7.1.2 (or align to the 8.0.x family) to eliminate the NU1902 warning noise. Test-only, no production reachability.
  • F-DBR-2 (cycle-3 Low, test-only) — consider adding a third guard to IntegrationTestResetGuard requiring explicit INTEGRATION_TEST_DB_RESET_CONFIRM=I-UNDERSTAND-THIS-TRUNCATES env var when running against non-postgres hosts.
  • F-PERF-1 (cycle-3 Low, operator-CLI) — pipe --mint-only token via process substitution / xargs so it never lands in shell history.
  • AZ-492 image-fixture consolidationUavUploadTests.CreateValidJpeg + PerfBootstrap.GenerateUavFixture + UavTileImageFactory are 3 sites that all produce conceptually-similar JPEGs; consolidate into one factory in TestSupport.
  • AZ-492 server-side per-item timing — PT-08 currently reports per-item gate cost as a derived proxy (batch_p95 / batch_size); true per-call UavTileQualityGate.Validate timing requires server-side instrumentation.
  • AZ-493 DB-name alignment — optional rename satelliteprovidersatelliteprovider_test in docker-compose.tests.yml would let IntegrationTestResetGuard adopt the spec's original DB-name pattern as a second guard alongside Host allowlist.
  • AZ-494 AC-7 cross-repo doc — write paragraph in suite/_docs/10_auth.md (parent monorepo).
  • No token revocation list (residual A07 after AZ-494) — Low; re-evaluate if user-revocable tokens or session management is ever required.