mirror of
https://github.com/azaion/missions.git
synced 2026-06-21 12:51:07 +00:00
7025f4d075
Updated JWT authentication to use configuration values instead of hardcoded secrets, improving security and flexibility. Enhanced CORS policy to conditionally allow origins based on configuration settings, with logging for permissive defaults. Updated README to reflect project renaming and clarify service context.
249 lines
18 KiB
Markdown
249 lines
18 KiB
Markdown
# 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<T>` 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<T> 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<string>?` (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.
|