# Deploy Report — Cycle 1 (AZ-484)

**Date**: 2026-05-11
**Cycle**: 1
**Scope**: Multi-source tile storage schema (AZ-484) and supporting documentation/security artifacts.

## What is shipping

### Code changes (already committed)

| Commit | Subject |
|--------|---------|
| `5ba58b6` | `[AZ-484] [AZ-483] Add task spec + tile-storage v1.0.0 contract draft` |
| `687d6bd` | `[AZ-484] Multi-source tile storage: source + captured_at` |
| `e9d6db0` | `[AZ-484] Fix multi-source tile reads: drop Dapper enum handler` |

### Database migration

- **`013_AddTileSourceAndCapturedAt.sql`** — runs on first startup of the new image. Transactional (`BEGIN; … COMMIT;`), idempotent against partial replays. Adds `source` (VARCHAR(32) NOT NULL DEFAULT `'google_maps'`) + `captured_at` (TIMESTAMP NOT NULL); backfills both for every pre-existing row; replaces 4-col `idx_tiles_unique_location` with 5-col `idx_tiles_unique_location_source`.

### Documentation, test-spec, audit artifacts (uncommitted in working tree, scoped to this deploy commit below)

- `_docs/02_document/data_model.md`, `module-layout.md`, 6 module / component docs — Step 13 (Update Docs).
- `_docs/02_document/ripple_log_cycle1.md` — Step 13 ripple analysis.
- `_docs/02_document/tests/performance-tests.md`, `traceability-matrix.md` — Step 12 (Test-Spec Sync) — added AZ-484 ACs and PT-07.
- `_docs/05_security/` (5 files) — Step 14 (Security Audit) — verdict PASS_WITH_WARNINGS.
- `_docs/06_metrics/perf_2026-05-11_cycle1_az484.md` — Step 15 (Performance Test) — all scenarios Unverified, non-blocking.
- `_docs/_process_leftovers/2026-05-11_perf-pt07-harness.md` — deferred PT-07 harness work.
- `_docs/_autodev_state.md` — autodev pointer.

## Pre-deploy checks (recap)

| Gate | Outcome |
|------|---------|
| Step 11 — Run Tests | All unit + integration tests pass against the post-AZ-484 build (213 unit + 5 new AZ-484 integration). |
| Step 14 — Security Audit | **PASS_WITH_WARNINGS** — 0 Critical, 0 High, 5 Medium, 5 Low. AZ-484 itself added zero new findings. Top hardening items recorded in `_docs/05_security/security_report.md`. |
| Step 15 — Performance Test | All scenarios `Unverified` (PT-07 harness deferred to next cycle). Non-blocking per test-run perf-mode rules. |
| `dotnet format whitespace --verify-no-changes` | Passed (part of `scripts/run-tests.sh`). |

## Migration safety

- **Idempotent**: re-running migration 013 against an already-migrated database is a no-op (`ADD COLUMN IF NOT EXISTS`, `DROP INDEX IF EXISTS`, `CREATE UNIQUE INDEX IF NOT EXISTS` patterns).
- **Backwards compatible**: pre-AZ-484 application code that reads `tiles` still works — `source` defaults to `'google_maps'`, `captured_at` is backfilled to `created_at`. Vestigial columns `version` / `maps_version` are preserved nullable.
- **No data loss**: the only DDL change is dropping `idx_tiles_unique_location` (replaced by `idx_tiles_unique_location_source`); rows are not touched except by the backfill UPDATE.
- **Lock duration**: ALTER TABLE on a tiles table with O(100K-1M) rows takes a few seconds for ADD COLUMN (metadata-only in PG 11+) and a few seconds for the UPDATE backfill on existing data. Acceptable for a maintenance window-free deploy assuming current row counts (`_docs/02_document/data_model.md` storage estimate: 100K-1M tiles).

## Rollback plan

The migration is forward-only by DbUp design (no automatic down migrations). In the unlikely event AZ-484 needs to be reverted in production:

1. Re-deploy the previous image (`registry.../azaion/satellite-provider:<previous-tag>`). The pre-AZ-484 code reads `*` from `tiles` and ignores the new columns — it will function correctly against the migrated schema.
2. The 5-col `idx_tiles_unique_location_source` is a strict superset of the pre-AZ-484 4-col index for the only producer pre-AZ-484 sees (`source = 'google_maps'`); pre-AZ-484 UPSERT on `(lat, lon, tile_zoom, tile_size_meters, version)` would fail conflict detection against the new index — so do **not** roll the schema back, only roll the application back. The schema is forward-compatible with the old code.
3. If a true schema rollback is ever needed, write a follow-up migration `014_RevertAz484.sql` rather than editing `013` in place.

## Post-deploy verification

After the new image is deployed and migrations have run:

1. Hit `GET /api/satellite/region/{any-existing-region-id}` — confirm 200 + status JSON (smoke test that the `tiles` reads still parse with the new entity shape).
2. Hit `GET /api/satellite/tiles/latlon?Latitude=...&Longitude=...&ZoomLevel=18` for a known cell — confirm 200 + tile bytes.
3. Run a single `psql` query: `SELECT count(*) FROM tiles WHERE source IS NULL OR captured_at IS NULL;` — expected `0`.
4. Tail Serilog logs for `Slow GetTilesByRegionAsync` warnings (>500ms) for the first hour. A spike here would be the earliest signal of a perf regression vs. the (unmeasured) pre-AZ-484 baseline.

## CI/CD path

The project's `.woodpecker/02-build-push.yml` builds and pushes the image on push to `dev`, `stage`, `main`. The `dev` branch is the standard target per `.cursor/rules/git-workflow.mdc`. Pushing the new commit to `origin/dev` triggers the pipeline; no manual deploy steps are needed beyond the `git push`.

**Push policy**: per `git-workflow.mdc`, the autodev does NOT push without explicit user request. The next user prompt will offer the push as an explicit option.

## Security caveats carried into this deploy

The Security Audit (Step 14) flagged 5 Medium findings that should be tracked as separate hardening tasks before public-network exposure but do NOT block this internal deploy:

- **S1+S2+I5** — Postgres default credentials in `appsettings.json` + `docker-compose.yml`; `.env` not in `.dockerignore`.
- **S4** — Google Maps API key needs rotation + `.env.example`.
- **D1** — Bump `Microsoft.AspNetCore.OpenApi 8.0.21 → 8.0.25` (CVE-2026-26130 SignalR DoS — not reachable in this app).
- **I3** — Wire `Microsoft.AspNetCore.RateLimiting`.

None of these are AZ-484 regressions; they are pre-existing items that today's audit surfaced. Tracking them as separate Jira tickets is recommended but not enforced by this gate.
