Files
missions/_docs/02_document/00_discovery.md
T
Oleksandr Bezdieniezhnykh 7025f4d075 refactor: enhance JWT authentication and CORS configuration
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.
2026-05-14 19:48:25 +03:00

18 KiB
Raw Blame History

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 B5B12 (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 1015 in ../../suite/_docs/02_missions.md)
    • [Authorize FL] missions/*MissionsController (matches spec items 19, 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 IConfigurationEnvironment.GetEnvironmentVariable → fallback Host=localhost;Database=azaion;Username=postgres;Password=changeme
JWT_SECRET IConfigurationEnvironment.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.WaypointsWaypoint.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.