# Flow F6 — Service startup + schema migration > One-shot per process start. Idempotent migrator (`CREATE ... IF NOT EXISTS`). Post-B9 the migrator additionally `DROP TABLE IF EXISTS orthophotos / gps_corrections` for fielded edge devices that previously ran the legacy schema. ## Description `Program.cs` builds the DI graph from environment, runs `DatabaseMigrator.Migrate(db)` once inside a startup scope, then starts serving HTTP. The migrator only owns the 4 tables this service is responsible for (`vehicles`, `missions`, `waypoints`, `map_objects`); the borrowed tables (`media`, `annotations`, `detection`) are migrated by their owner services in their own startups. ## Preconditions - `DATABASE_URL` resolves (env or hardcoded dev fallback). - `postgres-local` is reachable. - The `azaion` database itself exists in PostgreSQL (created at provisioning time, NOT by this migrator). ## Sequence Diagram ```mermaid sequenceDiagram autonumber participant Docker as Docker / Watchtower participant Host as 07_host (Program.cs) participant Cfg as IConfiguration participant Identity as 05_identity participant DI as DI container participant Migrator as DatabaseMigrator participant DB as postgres-local Docker->>Host: ENTRYPOINT dotnet Azaion.Missions.dll Host->>Cfg: read DATABASE_URL → ConvertPostgresUrl → Npgsql connection string Host->>Cfg: read JWT_SECRET (env or hardcoded fallback) Host->>Identity: AddJwtAuth(jwtSecret) — DI registration only, no network Host->>DI: register controllers + middleware + scoped AppDataConnection + scoped services Host->>DI: build Host Host->>Migrator: scope.Resolve(); Migrate(db) Migrator->>DB: CREATE TABLE IF NOT EXISTS vehicles (...) Migrator->>DB: CREATE TABLE IF NOT EXISTS missions (...) Migrator->>DB: CREATE TABLE IF NOT EXISTS waypoints (...) Migrator->>DB: CREATE TABLE IF NOT EXISTS map_objects (...) Migrator->>DB: CREATE INDEX IF NOT EXISTS ix_missions_vehicle_id, ix_waypoints_mission_id, ix_map_objects_mission_id Migrator->>DB: DROP TABLE IF EXISTS orthophotos Migrator->>DB: DROP TABLE IF EXISTS gps_corrections note right of Migrator: B9 one-shot. Idempotent on devices that already cleaned up. Migrator-->>Host: void Host->>Host: register ErrorHandlingMiddleware FIRST in pipeline Host->>Host: UseAuthentication / UseAuthorization Host->>Host: MapControllers + MapGet("/health") + UseSwagger Host->>Docker: app.Run() — listening on 0.0.0.0:8080 ``` ## Flowchart ```mermaid flowchart TD Start([Container start]) --> ReadCfg[Read DATABASE_URL + JWT_SECRET] ReadCfg --> RegDI[DI registrations: controllers, middleware, scoped DB + services] RegDI --> Build[Build Host] Build --> OpenScope[Open startup scope] OpenScope --> Migrate[Run DatabaseMigrator.Migrate] Migrate --> Create["CREATE TABLE IF NOT EXISTS x4 + indexes"] Create --> Drop["DROP TABLE IF EXISTS orthophotos, gps_corrections (B9)"] Drop --> Pipeline[Wire pipeline: error MW first, auth, controllers, /health, Swagger] Pipeline --> Run([app.Run on :8080]) Migrate -. on failure .-> Crash([Process exits non-zero — Watchtower restarts]) ``` ## Data Flow | Step | From | To | Data | Format | |------|------|----|------|--------| | 1 | Environment / appsettings | `Program.cs` | `DATABASE_URL`, `JWT_SECRET` | string | | 2 | `Program.cs` | DI container | service registrations | C# code | | 3 | `DatabaseMigrator` | `postgres-local` | DDL statements (CREATE / INDEX / DROP) | SQL | | 4 | `Program.cs` | OS / Docker | bind to `0.0.0.0:8080` | TCP listener | ## Error Scenarios | Error | Where | Detection | Recovery | |-------|-------|-----------|----------| | `postgres-local` unreachable | Migrate step | Npgsql `IOException` / `SocketException` | Process exits non-zero. Watchtower restarts; `flight-gate` prevents restart mid-mission. **Fix**: ensure `postgres-local` healthcheck passes before `missions` starts (compose `depends_on` with `condition: service_healthy`) | | `azaion` database missing | Migrate step | Npgsql `3D000` (`invalid_catalog_name`) | Process exits. Operator must create the database — provisioning concern, not this service. Documented in `../../suite/_docs/00_top_level_architecture.md` | | `DROP TABLE IF EXISTS orthophotos` fails because table is locked by `gps-denied` | B9 one-shot | Lock timeout or `55006` | Process exits, Watchtower restarts in a few seconds. **Out-of-band ordering**: deploy `gps-denied` FIRST so it has its own copy of the schema before `missions` drops the legacy tables. Documented in B9 ticket | | One `CREATE TABLE` succeeds, the next fails | Mid-Migrate | Npgsql exception on later statement | Process exits. Each statement is individually idempotent (`IF NOT EXISTS`) so the next startup retries safely from the start. No partial-migration cleanup needed | | Wrong PostgreSQL version (e.g., < 13) | Migrate step | Specific syntax errors in newer features | Process exits. Suite-supported version is PG 16+; older devices need a Postgres upgrade | | `DATABASE_URL` malformed (e.g. user password contains `@`) | `ConvertPostgresUrl` | parse failure / silent mis-parse | `ConvertPostgresUrl` does NOT URL-decode user/password — caveat for credentials with `@`, `:`, `/`, `%`. `07_host` Caveats. Mitigation: avoid those chars in passwords, OR pass a raw Npgsql key=value string instead of a URL | ## Performance Expectations | Metric | Target | Notes | |--------|--------|-------| | Cold start total time | <2 seconds typical on Jetson Orin | Migrator runs ~10 DDL statements; all are no-ops on a steady-state device | | Cold start with legacy GPS-Denied tables present | +1 second | First-time-on-device B9 `DROP` adds two DDL statements | | Crash recovery (Watchtower restart) | ~10 seconds | Container restart latency dominates |