# Codebase Discovery — Azaion.Missions > **NOTE (forward-looking)**: this discovery doc reflects the **post-rename, post-GPS-Denied-removal target** for this repo. Today the source still uses `Azaion.Flights` namespace, `Aircraft*`/`Flight*`/`Orthophoto*`/`GpsCorrection*` filenames, `[Route("aircrafts"|"flights")]`, and migrates 6 tables. The renames + drops are tracked under Jira AZ-EPIC + child tickets B5–B12 (see plan and `_autodev_state.md`). The doc IS the spec for that work. ## Suite Context (read first) This workspace is **one submodule** of the `azaion-suite` meta-repo (an aggregate of 11 component submodules orchestrated by the parent at `../../`). The canonical, human-confirmed documentation for what this service is supposed to do lives at: | Suite doc | Relevance to `missions` | |-----------|-------------------------| | `../../suite/_docs/02_missions.md` | **Primary spec** — Missions / Waypoints / Vehicles CRUD, all 15 endpoints (post-rename) | | `../../suite/_docs/00_top_level_architecture.md` | Service topology, deployment tiers, error/pagination wire conventions | | `../../suite/_docs/00_database_schema.md` | Authoritative ER diagram for the shared edge PostgreSQL | | `../../suite/_docs/00_roles_permissions.md` | `FL` permission code consumed by this service | | `../../suite/_docs/04_system_design_clarifications.md` | SSE conventions (relevant for downstream consumers) | | `../../suite/_docs/glossary.md` | Suite-wide terminology | | `../../suite/_docs/11_gps_denied.md` | **NOT this service** — GPS-Denied is a separate (out-of-this-repo) service | | `../../suite/_docs/05_security/cmmc_l2_scorecard.md` | CMMC L2 row 3 finding on JWT iss/aud validation, tracked at suite level (AZ-487/AZ-494) | `../../suite/_docs/_repo-config.yaml` post-rename: `name: missions, stack: .NET, deployment_tier: edge, primary_doc: _docs/02_missions.md`. ### What `missions` IS, in suite terms `missions` is the **edge-tier .NET service that owns the "mission" domain** of an Azaion deployment. It runs on each Jetson / OrangePI / operator-PC alongside `annotations`, `detections`, `autopilot`, `gps-denied`, and the React `ui`. **All edge services share one local PostgreSQL** on the device; each migrates only its own tables. JWTs are minted by the remote `admin` service and validated locally with the shared HMAC secret. Per the spec, this service has TWO feature slots, both gated by ONE permission: | Spec feature slot | Permission | Status in code today | |-------------------|------------|----------------------| | Vehicle Catalog (Plane / Copter / UGV / GuidedMissile inventory + default selection) | `FL` | Implemented (today as "aircraft catalog" with Plane/Copter only; `VehicleType` expansion is B6) | | Mission Planning (Missions + Waypoints CRUD + cross-service cascade-delete) | `FL` | Implemented (today as "flight planning"; rename + cascade shrink is B6 + B7) | GPS-Denied is **not** a feature of this service. The orthophoto / live-GPS / GPS-correction endpoints listed in `../../suite/_docs/11_gps_denied.md` live in the new (separate) `gps-denied` service. ### Why "missions" not "flights" The previous service name `flights` was too narrow. The fleet covered by `VehicleType` includes: - `Plane = 0` — fixed-wing UAV - `Copter = 1` — multirotor UAV - `UGV = 2` — Unmanned Ground Vehicle (per `../../hardware/_standalone/target_acquisition/target_acquisition.md`) - `GuidedMissile = 3` — single-use loitering munition A "flight" only describes air vehicles. A "mission" is the right abstraction for any of the above. ### Cross-service contracts owned here 1. **JWT validation** — trusts admin-issued tokens via shared HMAC secret (`JWT_SECRET`). This service never talks to `admin`; it only validates. 2. **Mission cascade-delete** — when a mission or waypoint is deleted, this service tears down rows in `media`, `annotations`, `detection` (owned schema-wise by `annotations` + the detection pipeline) AND `map_objects` (written by `autopilot` from H3-indexed detections per `../../suite/_docs/06_autopilot_design.md`). It is the only place in the system that knows the full mission ownership graph. 3. **Suite-standard wire shapes** — error envelope and `PaginatedResponse` are shared with `annotations`, `admin`, `satellite-provider` (defined in `../../suite/_docs/00_top_level_architecture.md`). --- ## Repository Layout ``` flights/ ← post-rename: missions/ ├── Auth/ (1 file) — JWT bearer auth setup + permission policy ├── Controllers/ (2 files) — REST API surface ├── DTOs/ (15 files) — Request/response/query/shared payloads ├── Database/ │ ├── AppDataConnection.cs — LinqToDB DataConnection with ITable properties │ ├── DatabaseMigrator.cs — Bootstraps the 4 tables this service OWNS the schema for (post-B7+B9) │ └── Entities/ (7 files) — LinqToDB-mapped row types (4 owned + 3 borrowed read-only stubs) (post-B7) ├── Enums/ (5 files) — Domain enumerations stored as INTEGER columns ├── Middleware/ (1 file) — Global exception → JSON error mapper ├── Entities/ (empty dir) — likely scaffolding leftover; delete in B5 ├── Infrastructure/ (empty dir) — likely scaffolding leftover; delete in B5 ├── Program.cs — Web host composition root + URL→connection-string adapter ├── GlobalUsings.cs — `global using LinqToDB[.Async|.Data]` ├── Azaion.Missions.csproj — `Microsoft.NET.Sdk.Web`, TargetFramework `net10.0` (post-B5) ├── Dockerfile — Multi-arch SDK build → `dotnet/aspnet:10.0` runtime, EXPOSE 8080 (entrypoint Azaion.Missions.dll post-B10) ├── README.md — One-liner; today says ".NET 8" — STALE (csproj targets net10.0) ├── .woodpecker/build-arm.yml — Single CI job: docker build + push on `[dev, stage, main]` └── .gitignore ``` Total source files **post-B7**: **~33 `.cs` files** across 7 logical directories (down from 37 today; loses `Aircraft.cs` renamed → `Vehicle.cs`, `Flight.cs` renamed → `Mission.cs`, `Orthophoto.cs` deleted, `GpsCorrection.cs` deleted, `OrthophotoRequest.cs` does not exist anyway). ## Tech Stack | Concern | Technology | Source of evidence | |---------|-----------|--------------------| | Language / runtime | C# on .NET 10 (`net10.0`) | csproj | | Web framework | ASP.NET Core (`Microsoft.NET.Sdk.Web`) | csproj, `Program.cs` | | Data access | linq2db `6.2.0` | csproj, `AppDataConnection.cs` | | Database driver | Npgsql `10.0.2` (PostgreSQL — shared with all other edge services) | csproj, `Program.cs` (`UsePostgreSQL`) | | Migrations | None (raw `CREATE TABLE IF NOT EXISTS` for the 4 owned tables, plus a one-shot `DROP TABLE IF EXISTS` block in B9 for legacy GPS-Denied tables) | `Database/DatabaseMigrator.cs` | | Auth | JWT bearer, HS256 shared-secret with admin; single claim-based permission `FL` (post-B7) | `Auth/JwtExtensions.cs` | | API docs | Swashbuckle `10.1.5` (Swagger UI mounted unconditionally) | csproj, `Program.cs` | | CORS | Open default policy (`AllowAnyOrigin/Method/Header`) | `Program.cs` | | Error handling | Custom middleware mapping `KeyNotFoundException`/`ArgumentException`/`InvalidOperationException` → 404/400/409, fallthrough → 500 | `Middleware/ErrorHandlingMiddleware.cs` | | Health endpoint | `GET /health` returns `{ status: "healthy" }` | `Program.cs` | | Container build | Dockerfile multi-arch (`--platform=$BUILDPLATFORM`, `dotnet publish --os linux --arch $arch`) | `Dockerfile` | | CI | Woodpecker single ARM-tagged build-and-push job; tag pattern `${REGISTRY_HOST}/azaion/missions:${BRANCH}-arm` (post-B10) | `.woodpecker/build-arm.yml` | | Tests | **None present** — tracked in `../../suite/_docs/_process_leftovers/2026-04-22_ci-unit-test-lane-missing-projects.md` | full file scan | ## Entry Points - `Program.cs` — Web host (top-level statements). Builds DI graph, runs `DatabaseMigrator.Migrate` once at startup, then `app.Run()`. - HTTP routes (registered via `MapControllers` + one minimal `MapGet`) (post-B6 + B8): - `[Authorize FL]` `vehicles/*` → `VehiclesController` (matches spec items 10–15 in `../../suite/_docs/02_missions.md`) - `[Authorize FL]` `missions/*` → `MissionsController` (matches spec items 1–9, includes nested `missions/{id}/waypoints/*`) - Anonymous `GET /health` - **No GPS-Denied endpoints** — those live in the separate `gps-denied` service. ## Configuration / Secrets Resolved at startup in `Program.cs`: | Key | Source order | Default (development fallback) | |-----|-------------|------------------------------| | `DATABASE_URL` | `IConfiguration` → `Environment.GetEnvironmentVariable` → fallback | `Host=localhost;Database=azaion;Username=postgres;Password=changeme` | | `JWT_SECRET` | `IConfiguration` → `Environment.GetEnvironmentVariable` → fallback | `development-secret-key-min-32-chars!!` | `DATABASE_URL` accepts either a `postgresql://user:pass@host:port/db` URL (converted via local helper `ConvertPostgresUrl`) or a raw Npgsql connection string. Edge compose passes `DATABASE_URL: postgresql://postgres:${PG_LOCAL_PASSWORD}@postgres-local/azaion` per `../../suite/_docs/00_top_level_architecture.md`. ## Test Layout No tests detected. Tracked in `../../suite/_docs/_process_leftovers/2026-04-22_ci-unit-test-lane-missing-projects.md`. The autodev BUILD pipeline will fill this gap (Steps 3 → 6 of existing-code flow). Sibling project would be `Azaion.Missions.Tests` (post-B5). ## Existing Documentation - `README.md` — one line, says ".NET 8" but `csproj` targets `net10.0`. **STALE** — fixed by Phase A1 of the rename plan. - No inline XML doc comments on any public type observed. - No `docs/` directory in this submodule. The canonical docs live at `../../suite/_docs/` (suite level). - This `_docs/02_document/` tree is the local autodev artifact set. ## Dependency Graph (file-level, internal-only) — post-rename Modules grouped by topological layer (each module imports only from layers above it). Internal-only edges shown; external NuGet edges (LinqToDB, ASP.NET Core, Npgsql) omitted. ``` Layer 0 — Leaves (no internal deps) Enums.VehicleType, Enums.FuelType, Enums.WaypointSource, Enums.WaypointObjective, Enums.ObjectStatus DTOs.GeoPoint, DTOs.SetDefaultRequest, DTOs.UpdateMissionRequest, DTOs.CreateMissionRequest, DTOs.GetMissionsQuery, DTOs.PaginatedResponse, DTOs.ErrorResponse, DTOs.GetVehiclesQuery Database.Entities.Media, Database.Entities.Annotation, Database.Entities.Detection Middleware.ErrorHandlingMiddleware Auth.JwtExtensions GlobalUsings Layer 1 — Depend on Enums (or GeoPoint) DTOs.CreateVehicleRequest → Enums DTOs.UpdateVehicleRequest → Enums DTOs.CreateWaypointRequest → Enums, DTOs.GeoPoint DTOs.UpdateWaypointRequest → Enums, DTOs.GeoPoint Database.Entities.Vehicle → Enums Database.Entities.MapObject → Enums Database.Entities.Waypoint → Enums (Association → Mission) Database.Entities.Mission → Enums (Associations → Vehicle, Waypoint) Layer 2 — Database wiring Database.AppDataConnection → all Database.Entities (7) Database.DatabaseMigrator → Database.AppDataConnection Layer 3 — Services Services.VehicleService → AppDataConnection, Entities, DTOs Services.WaypointService → AppDataConnection, Entities, DTOs, Enums Services.MissionService → AppDataConnection, Entities, DTOs (cascade also touches MapObjects, Media, Annotations, Detections) Layer 4 — HTTP surface Controllers.VehiclesController → Services.VehicleService, DTOs Controllers.MissionsController → Services.MissionService, Services.WaypointService, DTOs Layer 5 — Composition root Program.cs → Auth.JwtExtensions, Database (AppDataConnection, DatabaseMigrator), Middleware.ErrorHandlingMiddleware, Services.{Vehicle,Waypoint,Mission}Service ``` ### Topological Processing Order (used for module-level documentation) 1. **Enums** (5) 2. **Simple DTOs** (8) 3. **Simple Entities** (3 cross-service stubs) 4. **Cross-cutting leaves** (3): `JwtExtensions`, `ErrorHandlingMiddleware`, `GlobalUsings` 5. **DTOs depending on Enums** (4) 6. **Entities depending on Enums** (4) 7. **DB wiring** (2) 8. **Services** (3) 9. **Controllers** (2) 10. **Composition root** (1) No import cycles detected. --- ## Cross-Repo Schema Ownership (the key insight) The shared edge PostgreSQL hosts tables owned by multiple services. This service's `AppDataConnection` exposes ALL of them, but only migrates the 4 it owns: | Table | Schema owner | Written by | Read by `missions` | `missions` writes? | |-------|--------------|-----------|-------------------|--------------------| | `vehicles` | missions | missions (`VehicleService`) | yes | yes (full CRUD) | | `missions` | missions | missions (`MissionService`) | yes | yes (full CRUD) | | `waypoints` | missions | missions (`WaypointService`) | yes | yes (full CRUD) | | `map_objects` | missions | `autopilot` (per `../../suite/_docs/06_autopilot_design.md`) | no | cascade-delete only | | `media` | `annotations` (per `../../suite/_docs/01_annotations.md`) | `annotations` | yes (id + waypoint_id resolution) | cascade-delete only | | `annotations` | `annotations` | `annotations` | yes (id + media_id resolution) | cascade-delete only | | `detection` | detection pipeline | detections / ai-training | yes (id resolution) | cascade-delete only | | `orthophotos` | **gps-denied** (separate service, post-B7) | gps-denied | no | no (was: cascade-delete only) | | `gps_corrections` | **gps-denied** (separate service, post-B7) | gps-denied | no | no (was: cascade-delete only) | This pattern explains the shape of `AppDataConnection` and `DatabaseMigrator` — what looks like "incompleteness" in the migrator is the deliberate per-service ownership pattern. --- ## Cycles / Anomalies (post-rename) - **Entity ↔ Entity navigation** (`Mission.Waypoints` ↔ `Waypoint.Mission`, `Mission.Vehicle`) — LinqToDB `[Association]` pair, not an import cycle. - **Empty directories**: `Entities/` and `Infrastructure/` at the root are empty. **Delete in B5** as part of the rename pass. - **`detection` table singularity**: `Detection.cs` maps to `[Table("detection")]` (singular) while every other table is plural. Owned by another service — naming is THEIR call to make consistent. - **README ".NET 8" claim**: contradicts `net10.0` in `csproj`. Fix in Phase A1. ## Spec ↔ Code Divergences (carried into the verification log) | # | Concern | Spec source | Code reality | Resolution | |---|---------|------------|--------------|------------| | 1 | Service / domain naming | `../../suite/_docs/02_missions.md` (post-rename) | `Azaion.Flights.*`, `[Route("flights")]`, `[Route("aircrafts")]` today | B5 + B6 + B8 | | 2 | GPS-Denied feature slot | `../../suite/_docs/11_gps_denied.md` (separate service) | Schema (`Orthophoto`, `GpsCorrection`) + cascade branches still in this repo | B7 + B9 (drop entirely) | | 3 | `VehicleType` membership | `../../suite/_docs/02_missions.md` (Plane / Copter / UGV / GuidedMissile) | `AircraftType { Plane, Copter }` only | B6 | | 4 | `Geopoint` representation | `../../suite/_docs/02_missions.md` § "GPS (Geopoint)" + `../../suite/_docs/00_database_schema.md` (`Waypoints.GPS: string`) | 3 separate columns (`lat NUMERIC`, `lon NUMERIC`, `mgrs TEXT`); no auto-conversion | Out of this Epic — carry forward | | 5 | Error wire shape | `../../suite/_docs/00_top_level_architecture.md` § Error Response Format (camelCase, `errors: object?` keyed by field) | PascalCase `{ "StatusCode", "Message" }`; no `errors`; `ErrorResponse` DTO has `List?` (wrong shape) and is unused | Out of this Epic — carry forward | | 6 | Pagination wire shape | `../../suite/_docs/00_top_level_architecture.md` § Pagination (camelCase) | PascalCase via System.Text.Json defaults | Out of this Epic — carry forward | | 7 | Vehicle `IsDefault` exclusivity | `../../suite/_docs/02_missions.md` § 11 + 15 (just toggles) | Code clears the flag on every other row (stricter than spec) + race-prone | **B12** (decision-only ticket) | | 8 | Vehicle listing pagination | Spec endpoint 13 says "unpaginated" | Matches spec ✓ | — | | 9 | Waypoint listing pagination | Spec endpoint 6 says "unpaginated" | Matches spec ✓ | — | | 10 | Cascade-delete chain | Spec § 9 covers mission delete cascade; § 5 covers waypoint delete | Code matches the spec's cascade order; missing transaction wrapping | B7 shrinks the chain; transaction wrap is opportunistic improvement carried forward | | 11 | Swagger gating | Not specified | Unconditional, no `IsDevelopment` guard | Out of this Epic | | 12 | CORS policy | Not specified | Open in all environments | Out of this Epic | | 13 | JWT issuer/audience validation | CMMC L2 finding (suite-tracked, AZ-487/AZ-494) | Disabled, consistent with shared-secret model; suite-level remediation pending | Suite-level (not this Epic) | | 14 | `FL` permission code name | `../../suite/_docs/00_roles_permissions.md` | Code carries `"FL"` (legacy "Flight" name) even after rename to `missions` | TODO in `00_roles_permissions.md`; not this Epic (fleet-wide auth change) | | 15 | `FuelType` for `GuidedMissile` | Not specified | Existing `{ Electric, Gasoline, Diesel }` may not fit single-use missiles | Phase C decision — may spawn follow-up ticket | ## Cross-cutting Observations 1. No automated tests in the repository. 2. CORS allows any origin/method/header in all environments — production exposure if not overridden upstream. 3. JWT secret default (`development-secret-key-min-32-chars!!`) is hardcoded; production deploys MUST set `JWT_SECRET`. 4. Swagger UI is enabled unconditionally (no `IsDevelopment` guard). 5. Database password default (`changeme`) is hardcoded. 6. CI pipeline (`.woodpecker/build-arm.yml`) has only a build/push step — no test, no security scan, no migration check. 7. Schema bootstrap runs every startup (`DatabaseMigrator.Migrate`); idempotent (`IF NOT EXISTS`) but no schema versioning. The B9 `DROP TABLE IF EXISTS` is the one explicit destructive step in the migrator's history.