Files
missions/_docs/01_solution/solution.md
T
Oleksandr Bezdieniezhnykh 78dea8ebab
ci/woodpecker/push/build-arm Pipeline was successful
chore: update configuration and Docker setup for JWT and test results
Enhanced the .gitignore to exclude test results and updated the Dockerfile to include a new entrypoint script for improved container initialization. Refactored JWT configuration to support additional parameters for automatic refresh intervals, ensuring better control over token management. Updated the ConfigurationResolver to enforce required environment variables without hardcoded fallbacks, enhancing security and flexibility.
2026-05-15 03:23:23 +03:00

23 KiB
Raw Blame History

Solution — Azaion.Missions

Status: derived-from-code (autodev /document Step 5, 2026-05-14). Mode: retrospective synthesis from the verified _docs/02_document/ set. Forward-looking caveat: this document describes the post-rename, post-GPS-Denied-removal target the documentation already reflects. Today's source still uses Azaion.Flights.*, Aircraft*/Flight*/Orthophoto*/GpsCorrection* filenames, and [Route("aircrafts"|"flights")]. The implementation gap is tracked under Jira AZ-EPIC (AZ-539) children B4B12; the doc-vs-code reconciliation table lives in _docs/02_document/04_verification_log.md § 0. References to "the implemented solution" in this document mean the code as it exists today plus the deltas closed by B4B12.


1. Product Solution Description

missions is the edge-tier .NET 10 REST service that owns the mission domain of an Azaion deployment — vehicle inventory (Plane / Copter / UGV / GuidedMissile), mission plans, ordered waypoints, and the cross-service cascade-delete that keeps the rest of the edge stack consistent when missions or waypoints are removed.

Runtime topology: exactly one container per device (Jetson Orin / OrangePI / operator-PC), co-located with annotations, the detection pipeline, autopilot, gps-denied, and the React ui. All edge services share one local PostgreSQL on the device; each migrates and writes only the tables it owns. JWTs are minted by the central admin service (ECDSA-signed) and validated locally by missions against admin's JWKS endpoint — request-path validation is local after the JWKS is cached, but the first protected request after a cold start triggers a synchronous JWKS HTTPS GET against admin. Key rotation publishes a new kid in admin's JWKS and propagates to validators on the cache-refresh tick (no coordinated redeploy).

Component interaction (high-level)

flowchart LR
  ui[[Operator UI]]
  admin[[admin service<br/>JWT issuer]]
  autopilot[[autopilot]]
  annotations[[annotations]]
  detection[[detection pipeline]]
  gps[[gps-denied service]]

  subgraph missions["missions (this service, one .NET process)"]
    direction TB
    h07[07_host<br/>Program.cs / DI / startup]
    c01[01_vehicle_catalog]
    c02[02_mission_planning]
    p04[04_persistence<br/>AppDataConnection + Migrator]
    i05[05_identity<br/>JWT bearer + FL policy]
    h06[06_http_conventions<br/>error envelope + pagination]
  end

  pg[(postgres-local<br/>shared per device)]

  ui -- "REST + JWT" --> c01
  ui -- "REST + JWT" --> c02
  admin -- "JWKS over HTTPS (lazy fetch + refresh)" --> i05
  c01 --> p04
  c02 --> p04
  c02 -. "cross-service cascade delete" .-> annotations
  c02 -. "cross-service cascade delete" .-> detection
  autopilot <-- "DB read missions/waypoints<br/>DB write map_objects" --> pg
  p04 --> pg
  annotations <--> pg
  detection <--> pg
  gps -. "no runtime coupling<br/>(GUID refs only)" .-> pg

  h07 --> c01
  h07 --> c02
  h07 --> p04
  h07 --> i05
  h07 --> h06

The HTTP surface is summarised in _docs/02_document/system-flows.md (7 flows, F1F7); the per-component HTTP routes are listed in _docs/02_document/components/0[1,2]_*/description.md.


2. Architecture (as implemented)

The dominant pattern is thin ASP.NET Core controller → service class → linq2db active-record over a per-request scoped DataConnection, with no repository abstraction and no in-process message queue / event bus. ADR rationale (ADR-001 .. ADR-008) is in _docs/02_document/architecture.md § 8.

2.1 Per-component solution table

# Component Solution (what it does) Tools / libs Advantages Limitations Requirements satisfied Security Cost Fit
01 01_vehicle_catalog Vehicle CRUD + is_default exclusivity. Controller [Authorize(Policy="FL")]VehicleServiceITable<Vehicle> ASP.NET Core, linq2db ITable<Vehicle>, [Authorize] Single owner of the inventory abstraction; same exact pattern as 02_mission_planning so engineers context-switch cheaply "Exactly one default" is enforced by clear-then-set without a transaction → race window (B12 decision pending); no input validation on Name/BatteryCapacity (carry-forward) Spec § 6.1 (Vehicle Catalog), suite roles FL [Authorize(Policy="FL")] on every action; no per-method authz One service file + one controller (~190 LoC together) Good — matches operator-paced load, vertical scale only
02 02_mission_planning Mission + Waypoint CRUD + the cross-service cascade-delete walk. Existence-checks vehicle_id on create/update; paginates GET /missions (the only paginated endpoint). ASP.NET Core, linq2db, PaginatedResponse<T> (06_http_conventions) One canonical place that knows the full mission ownership graph; cascade walks map_objects → media → annotations → detection → waypoints → missions in FK order Cascade is NOT transaction-wrapped (ADR-006) → partial failure leaves orphans; UpdateWaypoint is a full overwrite even though DTO looks partial; vehicle_id missing returns 400 (spec wants 404); LinqToDB does not eager-load [Association] so Vehicle and Waypoints serialize null/empty Spec § 6.2 (Mission Planning + Waypoints), spec § cascade contract [Authorize(Policy="FL")] on every action; no audit log, no correlation id Two service files + one controller (~370 LoC together); sequential I/O (47 round-trips per cascade) — single-digit ms typical against local Postgres Acceptable today; will need transaction wrap (one-line) before SLO commitments
04 04_persistence AppDataConnection : DataConnection exposes ITable<T> for every persisted entity (4 owned post-B7+B9 + 3 borrowed read-only stubs). DatabaseMigrator runs CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS at startup; B9 adds a one-shot DROP TABLE IF EXISTS orthophotos / gps_corrections for fielded devices linq2db 6.2.0, Npgsql 10.0.2, raw Execute for DDL Lightweight; no migration tool dependency; idempotent every restart; ITable<T> lets cross-component reads/cascades stay typed No schema versioning; column drops / type changes need manual SQL or a future migration tool; no connection-pool tuning beyond Npgsql defaults Spec § database schema, suite ER diagram (post-B7) DB credentials are env-driven (DATABASE_URL); no column-level encryption; relies on PG-level access control One file for the connection (~70 LoC) + one for the migrator (~120 LoC post-B9) Good for current schema scale (4 owned tables); will become limiting when schema starts evolving frequently
05 05_identity JwtExtensions.AddJwtAuth(issuer, audience, jwksUrl) registers JwtBearer with ECDSA-SHA256 (algorithm pin), iss + aud validation, ClockSkew = 30s, and the named policy "FL". Signing keys are pulled from admin's JWKS via ConfigurationManager<JsonWebKeySet> with HttpDocumentRetriever { RequireHttps = true } Microsoft.AspNetCore.Authentication.JwtBearer 10.0.5, Microsoft.IdentityModel.Protocols, JsonWebKeySet admin outage AFTER the JWKS is cached does NOT take this service down; key rotation publishes a new kid and propagates on the refresh tick — no coordinated redeploy; iss + aud + alg-pin closes the CMMC L2 row 3 finding in this service's code First protected request after a cold start triggers a synchronous JWKS fetch → if admin is unreachable at that exact moment the request 500s (new failure mode vs the legacy local-only model); the policy code "FL" retains the legacy "Flight" wording (fleet-wide auth change deferred); user-id claim is parsed but not consumed anywhere (no per-user audit) Spec § auth, ../../suite/_docs/00_roles_permissions.md Asymmetric: admin holds the private key; this service holds only public-key configuration + the JWT_ISSUER / JWT_AUDIENCE / JWT_JWKS_URL env vars (no shared secret on this side anymore) One file (~80 LoC) Good; the cold-start dependency on admin reachability is the cost of the rotation-without-redeploy operational win
06 06_http_conventions ErrorHandlingMiddleware (global exception → JSON envelope) + PaginatedResponse<T> + the dead ErrorResponse DTO ASP.NET Core middleware, System.Text.Json (defaults) Single chokepoint for HTTP wire shape — error mapping is uniform across components Two divergences from the suite spec carry forward (ADR-002): entity/DTO bodies are PascalCase (no JsonNamingPolicy.CamelCase); error envelope misses spec's errors field. The error envelope IS already camelCase by accidental match (anonymous-object literal). The ErrorResponse DTO is dead on the wire and has the wrong shape (List<string>? instead of spec's object? keyed by field name) Spec § Error Response Format, § Pagination LogError(ex, ...) only — no PII redaction (none in payload today); fallback 500 body shows the generic message, NOT the stack trace (logged only) One middleware file + two DTO files (~80 LoC together) Acceptable until the suite-wide camelCase migration; cutover is all-or-nothing because UI + autopilot consume PascalCase today
07 07_host Program.cs composition root: resolve four required config values via Infrastructure/ConfigurationResolver.ResolveRequiredOrThrow (DATABASE_URL, JWT_ISSUER, JWT_AUDIENCE, JWT_JWKS_URL); env → Npgsql connection string adapter (ConvertPostgresUrl); JWT registration; scoped DI for AppDataConnection + service classes; run migrator at startup; CorsConfigurationValidator.EnsureSafeForEnvironment gating CORS; mount middleware in correct order; MapGet("/health"); mount Swagger ASP.NET Core minimal host APIs, Infrastructure/ConfigurationResolver.cs, Infrastructure/CorsConfigurationValidator.cs One file you can read top-to-bottom in one sitting; fail-fast on missing required config — no silent boot with insecure defaults; CORS gated by environment — Production refuses an empty allow-list unless AllowAnyOrigin=true Swagger UI is still NOT gated on IsDevelopment() (surviving branch of ADR-005); a misconfigured JWT_JWKS_URL = http://... passes config resolution but fails at first JWKS fetch (detected at runtime, not startup) Spec § service composition; container EXPOSE 8080; Watchtower restart contract Required config is loud-fail (InvalidOperationException) on absence; no hardcoded dev fallbacks anywhere. Swagger surviving branch remains a tracked carry-forward One file (~180 LoC) plus the two Infrastructure/*.cs helpers (~70 LoC together) Good — the security posture is materially improved over the pre-2026-05 state

2.2 Cross-cutting design choices

Choice Rationale Status
One PostgreSQL per device, shared by all edge services (ADR-001) 6× operational overhead saved per device; cross-service cascade is physically possible in one DB connection Implemented
Manual cascade-delete in code, NOT ON DELETE CASCADE (ADR-003) Schema-level cascade would couple annotations / detection schemas to this service's lifecycle Implemented (transaction-wrap missing — ADR-006 carry-forward)
CREATE TABLE IF NOT EXISTS schema bootstrap (ADR-004), no migration tool 4-table schema; no column drops or type changes; restart-driven deploy via Watchtower Implemented (B9 adds the one explicit DROP TABLE IF EXISTS block for fielded devices)
JWT validation against admin JWKS, request-path local after cache (ADR-005, F5) Asymmetric trust + rotation-without-redeploy; closes the CMMC L2 iss/aud finding in this service's code while keeping admin off the per-request hot path Implemented (ECDSA-SHA256 with algorithm pinning, iss + aud validation, HTTPS-only JWKS retrieval, cold-start synchronous fetch trade-off documented)
One csproj, one root namespace (ADR-008); layering by convention not by compiler Service is small enough that 6 csprojs add more navigation cost than safety value Implemented (post-B5); enforcement via module-layout.md § Allowed Dependencies + /code-review Phase 7
GPS-Denied moved to a sibling service (ADR-007, B7+B9) Different scaling + deployment cadence; GPS-Denied owns its tables and lifecycle Doc-only today; B7 (code) + B9 (DB migration) close the gap

2.3 Implementation order (relative to other components)

The 6 components have no circular dependencies. Implementation/refactor order (lowest layer first), per _docs/02_document/module-layout.md:

  1. 04_persistence (Layer 1) — depends only on linq2db + Npgsql.
  2. 05_identity, 06_http_conventions (Layer 2) — depend on ASP.NET Core only.
  3. 01_vehicle_catalog (Layer 3) — depends on 04_persistence, 05_identity.
  4. 02_mission_planning (Layer 4) — depends on 01_vehicle_catalog (vehicle existence check), 04_persistence, 05_identity, 06_http_conventions (paginated envelope).
  5. 07_host (Layer 5) — depends on every other component (composition root).

Cross-component reads happen via the shared AppDataConnection (e.g. 02_mission_planning reads vehicles to existence-check vehicle_id); the codebase does not wrap that lookup behind an interface in 01_vehicle_catalog. This is intentional (one csproj, one DB) — see ADR-008.


3. Testing Strategy (as observed)

Status (today): NO automated tests are present in this codebase. The Tech Stack table in _docs/02_document/architecture.md § 2 says "Tests: None present"; _docs/02_document/00_discovery.md § Repository Layout confirms there is no tests/ directory and the csproj has no test project sibling. The autodev existing-code flow's Phase A Steps 3 → 7 (Test Spec → Decompose Tests → Implement Tests → Run Tests) is the planned path to close this gap.

3.1 What exists today

Layer Coverage Where it is
Unit None
Integration / functional None
Non-functional (perf, load, security) None
Health probe One endpoint (GET /health returns { status: "healthy" }) Program.cs MapGet("/health")
Schema sanity Indirect — DatabaseMigrator runs at every startup; if a column/table is missing the process crashes Database/DatabaseMigrator.cs
Wire-shape verification Manual diff against ../../suite/_docs/00_top_level_architecture.md § Error Response Format + § Pagination Code review

3.2 What the autodev existing-code flow will produce

  • Step 3 (Test Spec)_docs/02_document/tests/traceability-matrix.md + per-flow scenario files for F1F7. The 8 ADRs and 7 carry-forward concerns from architecture.md are the seed set for test scenarios.
  • Step 4 (Code Testability Revision) → minimal, surgical fixes if the codebase blocks tests from running. The 2026-05-14 re-verification confirmed that the JWT/CORS/Config evolution actually made the code MORE testable than the docs described (env-first ResolveRequiredOrThrow, JWKS retrievable via an in-process ECDSA keypair + ephemeral JWKS HTTP service mock, explicit CORS config), so this step is expected to land "all scenarios testable as-is". Scope: smallest set of changes; deeper refactors deferred to Step 8.
  • Step 5 (Decompose Tests) → per-test task files in _docs/02_tasks/todo/, plus _test_infrastructure.md.
  • Step 6 (Implement Tests)tests/Azaion.Missions.Tests/ sibling project (xUnit is the suite-standard choice; per coderule.mdc "follow the established directory structure", no src/ layer).
  • Step 7 (Run Tests) → green test suite forms the safety net for Step 8 (Refactor) and every Phase B feature cycle thereafter.

3.3 Scenarios likely to land first (anticipated, not yet specified)

These are obvious test seams given the F1F7 flows and the 7 carry-forward concerns; the actual scenario set is produced by Step 3.

Priority Scenario family Why it lands first
1 MissionService.DeleteMission — full cascade in dependency order Critical-flow F3, NOT transaction-wrapped today; tests would catch any future regressions in the cascade chain immediately
1 WaypointService.DeleteWaypoint — scoped cascade variant Same reason as F3; same NO-transaction caveat
2 MissionService.CreateMission / UpdateMissionvehicle_id existence check + spec-vs-code 400 vs 404 divergence Locks in the current behaviour so the spec-conformance fix is intentional, not accidental
2 VehicleService.SetDefault / Create / Update — "exactly one default" race B12 decision (spec-vs-code stricter behaviour) — tests pin whichever resolution the user picks
2 ErrorHandlingMiddleware mapping (KeyNotFoundException → 404, ArgumentException → 400, InvalidOperationException → 409, fallthrough → 500) Wire-shape contract used by every flow
3 JWT validation — accept valid ECDSA-SHA256 / reject alg ∉ [EcdsaSha256] (HS256-confusion) / reject invalid signature / reject mismatched kid / reject expired (with 30s skew) / reject iss != JWT_ISSUER / reject aud != JWT_AUDIENCE / reject missing-FL claim / JWKS rotation picks up new kid on refresh tick F5 cross-cutting; pins the asymmetric-validation contract
3 DatabaseMigrator.Migrate — idempotent on a fresh DB, idempotent on already-migrated DB, B9 DROP on a fielded-legacy DB F6; tests guard the only explicit destructive step

4. Non-functional behaviour (observed)

Concern Observed behaviour Where it is set Notes
Latency Single-digit ms typical; cascade delete = 47 sequential round-trips against local Postgres Database/AppDataConnection.cs (per-request scope), MissionService.DeleteMission No SLO in spec; observed under operator-paced load
Throughput Operator-paced (~1 op/s peak); not load-tested Edge deployment shape; not a hot path
Availability Best-effort per device; Watchtower restarts on crash; flight-gate prevents restart mid-mission Dockerfile + ../../suite/_infra/_compose/, suite arch doc No multi-instance HA per device by design
Recovery RTO ≈ container restart time (~10s); RPO = device-local backup cadence (suite concern) Watchtower + suite-level backup
Cascade atomicity Currently violated (ADR-006); one-line fix queued Services/MissionService.cs, Services/WaypointService.cs Recommended to land with B6
Wire-shape conformance Currently divergent on entity/DTO case + error envelope's missing errors field (ADR-002) Program.cs (no JsonNamingPolicy.CamelCase); Middleware/ErrorHandlingMiddleware.cs (anonymous object envelope) Cutover is suite-wide; out of this Epic
Health endpoint < 10 ms typical (no DB ping); used by Watchtower + reverse proxy Program.cs MapGet("/health") Future improvement: gate on DB ping
Resource limits None in code; container-level limits set by edge compose Dockerfile (no --memory / cpu limits inside) Suite-level concern

5. References

5.1 Source artefacts (this repo)

Concern File
Web host composition Program.cs
Vehicle catalog Controllers/AircraftsController.cs (post-B6/B8: Controllers/VehiclesController.cs), Services/AircraftService.cs (post-B6: VehicleService.cs)
Mission planning Controllers/FlightsController.cs (post-B6/B8: Controllers/MissionsController.cs), Services/FlightService.cs (post-B6: MissionService.cs), Services/WaypointService.cs
Persistence Database/AppDataConnection.cs, Database/DatabaseMigrator.cs, Database/Entities/*.cs
Identity Auth/JwtExtensions.cs
Configuration / CORS gates Infrastructure/ConfigurationResolver.cs, Infrastructure/CorsConfigurationValidator.cs
HTTP conventions Middleware/ErrorHandlingMiddleware.cs, DTOs/PaginatedResponse.cs, DTOs/ErrorResponse.cs
Container Dockerfile
CI .woodpecker/build-arm.yml
Project Azaion.Flights.csproj (post-B5: Azaion.Missions.csproj)

5.2 Generated documentation (this repo)

Doc Path
Discovery _docs/02_document/00_discovery.md
Per-module docs (12 modules) _docs/02_document/modules/*.md
Per-component descriptions (6 components) _docs/02_document/components/*/description.md
Module layout (file ownership + layering) _docs/02_document/module-layout.md
Architecture (this solution's source-of-truth) _docs/02_document/architecture.md
System flows (F1F7) _docs/02_document/system-flows.md + _docs/02_document/diagrams/flows/*.md
Data model _docs/02_document/data_model.md
Glossary (confirmed by user) _docs/02_document/glossary.md
Verification log (drift mapping) _docs/02_document/04_verification_log.md
Drift findings (2026-05-14 re-verification) _docs/02_document/05_drift_findings_2026-05-14.md
Deployment notes _docs/02_document/deployment/{containerization,ci_cd_pipeline,environment_strategy,observability}.md

5.3 Suite-level cross-references

Concern File
Primary spec for this service ../../suite/_docs/02_missions.md
Top-level architecture (error envelope, pagination, topology) ../../suite/_docs/00_top_level_architecture.md
Authoritative ER diagram (shared edge Postgres) ../../suite/_docs/00_database_schema.md
Roles & permissions (FL permission origin) ../../suite/_docs/00_roles_permissions.md
GPS-Denied (separate service after B7) ../../suite/_docs/11_gps_denied.md
CMMC L2 scorecard (JWT iss/aud finding) ../../suite/_docs/05_security/cmmc_l2_scorecard.md
Repo-config (post-rename) ../../suite/_docs/_repo-config.yaml

5.4 Tracker (Jira AZ project)

Plan ID Jira Type SP Status
Epic AZ-539 Epic To Do
B1 (local docs) AZ-540 Task 3 Done
B2 (suite docs) AZ-541 Task 3 Done
B3 (state bookkeeping) AZ-542 Task 3 Done
B4 (repo rename) AZ-543 Task 3 To Do
B5 (csproj + namespace) AZ-544 Story 3 To Do
B6 (domain rename) AZ-545 Story 5 To Do
B7 (drop GPS-Denied) AZ-546 Story 3 To Do
B8 (HTTP routes) AZ-547 Story 3 To Do
B9 (DB migration) AZ-548 Story 5 To Do
B10 (Dockerfile + image tag) AZ-549 Task 2 To Do
B11 (consumer cutover) AZ-550 Story 5 To Do
B12 (default vehicle rule decision) AZ-551 Task 2 To Do

Leftover index: _docs/_process_leftovers/2026-05-14_rename-flights-to-missions.md.