Files
missions/_docs/02_document/components/07_host/description.md
T
Oleksandr Bezdieniezhnykh a26d7b163b [AZ-549] B10a: clean up forward-looking notes; mark image rename done
The .woodpecker/build-arm.yml already pushes ${REGISTRY_HOST}/azaion/missions
(landed earlier as part of the B5 csproj/namespace rename). What this commit
fixes is the missions-internal documentation that still described the legacy
azaion/flights image as the *current* state.

Edits:

- _docs/02_document/deployment/environment_strategy.md: drop "today's edge
  compose still references azaion/flights" — B10 is done. Container/service
  name 'flights' still noted as B6/B11 work.
- _docs/02_document/deployment/containerization.md: drop "today's Dockerfile
  ENTRYPOINT is dotnet Azaion.Flights.dll, image tag base is azaion/flights"
  — both AZ-544 (B5) and AZ-549 (B10) done.
- _docs/02_document/deployment/ci_cd_pipeline.md: same fix.
- _docs/02_document/components/07_host/description.md: same fix.
- _docs/02_document/04_verification_log.md row for AZ-549: explicitly
  marked "done"; Code symbol column converged to post-rename value.
- _docs/00_problem/restrictions.md E6: parenthetical reworded so the row
  reads as a present-state assertion (B10 done) instead of a forward-
  looking note.
- _docs/02_document/glossary.md "Synonym pairs" heading flipped from
  "today's code ↔ post-rename target" to "pre-rename ↔ post-rename"
  (adjacent hygiene — B5-B9+B10 are done across the missions rename
  Epic; the table's "today" framing no longer matches reality).

Spec _docs/tasks/todo/AZ-549a_missions_rename_b10_pipeline.md moved to
_docs/tasks/done/.

rg -F 'azaion/flights' missions/ | grep -v done/ now returns only
intentional pre-rename historical references in glossary.md /
architecture.md / restrictions.md / verification_log.md — the "current
state" wording is gone.

Suite-side slice (AZ-549b — _infra/deploy/*/docker-compose.yml image
ref + ci/README.md example) shipped separately in the suite repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-16 11:57:09 +03:00

7.9 KiB

07 — Host (Composition Root)

Spec source: ../../../suite/_docs/00_top_level_architecture.md § Edge compose excerpt — confirms the port (5002:8080) and DB target (postgres-local). The env-var contract in suite docs still references the legacy JWT_SECRET and predates this service's transition to JWKS-based JWT validation; the four-variable env contract documented below is the verified current state in code, and the suite docs are flagged for sync in _docs/02_document/05_drift_findings_2026-05-14.md.

Implementation status: implemented.

Note

: namespace (Azaion.Missions), entrypoint (dotnet Azaion.Missions.dll), and container image (azaion/missions:*-arm) reflect the post-rename state — renames landed under AZ-544 (B5) + AZ-549 (B10).

Files: Program.cs, GlobalUsings.cs, Infrastructure/ConfigurationResolver.cs, Infrastructure/CorsConfigurationValidator.cs

1. High-Level Overview

Purpose: Build the ASP.NET Core web host: read environment, register all DI services, configure the request pipeline, run the schema migrator at startup, and serve the API on port 8080 (mapped to host 5002 in edge compose).

Architectural pattern: Composition root + ASP.NET Core minimal-host bootstrap (top-level statements).

Upstream dependencies: Every other component in this service.

Downstream consumers: The container runtime (ENTRYPOINT ["dotnet", "Azaion.Missions.dll"] in Dockerfile after B10) and any local dotnet run.

2. Internal Interface

None. The host has no exported types -- its surface is the running HTTP server.

3. External API

Endpoint Method Auth Description
/health GET Public Returns { "status": "healthy" }
/swagger/* GET Public Swagger UI + JSON spec, served unconditionally in all environments
(mapped controllers from feature components) various Per-controller [Authorize] See components 01 (vehicles) and 02 (missions).

4. Data Access Patterns

  • Opens a single scope at startup to call DatabaseMigrator.Migrate(db) -- populates the 4 owned tables in the shared local PostgreSQL.
  • Registers AppDataConnection as scoped so each HTTP request gets a fresh DataConnection (one Npgsql connection per request from the pool).

5. Implementation Details

State Management: Stateless (request pipeline). The only run-once side effect is the migrator call.

Key Dependencies:

Library Version Purpose
Microsoft.AspNetCore (in Microsoft.NET.Sdk.Web) net10.0 Web host + middleware pipeline
linq2db 6.2.0 DB access via AppDataConnection registration
Npgsql 10.0.2 PostgreSQL driver (used through linq2db)
Swashbuckle.AspNetCore 10.1.5 Swagger UI + JSON spec generation

Error Handling: Delegated to 06_http_conventions' middleware, placed FIRST in the pipeline so it wraps everything else.

Configuration: All required values flow through Infrastructure/ConfigurationResolver.csResolveRequiredOrThrow. Resolution order per value: Environment.GetEnvironmentVariable(envVar)IConfiguration[configKey] → throws InvalidOperationException at startup with a message naming both the env var and the config key. There are no hardcoded dev fallbacks; a misconfigured production deploy cannot silently boot.

Env var Config key Required? Purpose
DATABASE_URL Database:Url Yes Either Npgsql key=value form OR a postgresql:// URI (converted via ConvertPostgresUrl)
JWT_ISSUER Jwt:Issuer Yes Expected iss claim value (see 05_identity)
JWT_AUDIENCE Jwt:Audience Yes Expected aud claim value (see 05_identity)
JWT_JWKS_URL Jwt:JwksUrl Yes HTTPS URL of admin's JWKS endpoint (see 05_identity)
CorsConfig:AllowedOrigins (same) No (defaults to []) String array of allowed origins for the CORS policy
CorsConfig:AllowAnyOrigin (same) No (defaults to false) When true, applies AllowAnyOrigin/Method/Header regardless of origins

The legacy JWT_SECRET env var is no longer consulted; 05_identity documents the JWKS-based replacement.

CORS gating (Infrastructure/CorsConfigurationValidator.cs):

  • EnsureSafeForEnvironment(origins, allowAnyOrigin, environmentName) THROWS InvalidOperationException in Production (case-insensitive match on the ASPNETCORE_ENVIRONMENT value) when origins are empty AND allowAnyOrigin is false. The host refuses to start with an implicit permissive policy in Production.
  • ShouldUsePermissivePolicy(origins, allowAnyOrigin) returns true when allowAnyOrigin == true OR origins is empty — used by the CORS policy builder. In non-Production environments with empty origins this falls back to permissive.
  • ShouldWarnAboutPermissiveDefault(origins, allowAnyOrigin) is true when origins are empty AND allowAnyOrigin is false (implicit permissive). When true, the host logs PermissiveDefaultWarning at startup with the current environment name.

ConvertPostgresUrl helper: ad-hoc parser converting postgresql://user[:pass]@host[:port]/db to Npgsql key=value form. Does not URL-decode user/password — caveat for credentials with @, :, /, %.

6. Extensions and Helpers

  • GlobalUsings.cs -- three project-wide global using directives for LinqToDB.

7. Caveats & Edge Cases

  • Swagger is unconditional: Swagger UI + JSON spec are mounted regardless of environment (no IsDevelopment() guard). This is the only remaining aspect of ADR-005 that still applies — the legacy "dev-fallback secret" aspect of ADR-005 is now obsolete (ConfigurationResolver.ResolveRequiredOrThrow throws on any missing value at startup).
  • CORS hard-fail is Production-only. In Staging or any custom environment name that is not literal Production (case-insensitive), an empty allow-list with AllowAnyOrigin=false falls back to permissive (with a startup warning) instead of throwing. Operators deploying to a "Staging" tier that should be locked down need to set CorsConfig:AllowedOrigins explicitly — the validator will not enforce it for them.
  • JWKS startup dependency: the first protected request after process start triggers a synchronous HTTPS fetch to JWT_JWKS_URL. If admin is unreachable at that moment, the request fails 500 from 05_identity's IssuerSigningKeyResolver. Once cached, request-path validation does not call admin.
  • Migrator failure crashes the process at startup. Container orchestrator (Watchtower-restarted Docker) is expected to bring it back; flight-gate (per ../../../suite/_docs/00_top_level_architecture.md) ensures this doesn't happen mid-mission.
  • No HTTPS redirection middleware; assumes a TLS-terminating reverse proxy upstream (Caddy fronting Gitea is documented but in-deployment TLS termination is environment-specific). Note: JWT_JWKS_URL is independently constrained to HTTPS by HttpDocumentRetriever { RequireHttps = true } inside 05_identity.
  • Port 8080 matches the Dockerfile EXPOSE 8080 and edge compose 5002:8080 mapping per ../../../suite/_docs/00_top_level_architecture.md excerpt.
  • No GPS-Denied service registration here. Earlier drafts of this doc reserved a slot for a GPS-Denied feature component; per Jira AZ-EPIC child B7, GPS-Denied lives in a separate (out-of-this-repo) service, so this host registers only VehicleService, MissionService, WaypointService.

8. Dependency Graph

Must be implemented after: every other component (01-06).

Blocks: nothing internal (it is the runtime root).

9. Logging Strategy

ASP.NET Core defaults (Console / Debug providers, no Serilog/structured logging configured). The only structured log emitted by app code is 06_http_conventions' middleware LogError(ex, "Unhandled exception"). No correlation ID, no request tracing.