Files
missions/_docs/02_document/05_drift_findings_2026-05-14.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

15 KiB
Raw Blame History

Drift Findings — Targeted Verification Re-run, 2026-05-14

Status: discovery complete; doc revisions and test-spec re-issues PENDING (next session). Scope: targeted re-verification of Auth/JwtExtensions.cs, Program.cs, Infrastructure/*.cs, Services/*.cs, Database/DatabaseMigrator.cs, Middleware/ErrorHandlingMiddleware.cs, Controllers/FlightsController.cs, Controllers/AircraftsController.cs against the corresponding _docs/ artifacts. Trigger: while preparing autodev Step 4 (Code Testability Revision), ran a code-level cross-check that contradicts the _docs/02_document/04_verification_log.md § 3 "All flow claims reconcile" verdict for F5 (JWT) + F6 (Startup) + AC-9 (Authz). Root cause (likely): the prior verification did doc-vs-doc consistency checks for these areas instead of opening the actual .cs files. The docs are internally consistent describing a HS256+shared-secret+permissive-CORS+dev-fallback world that no longer exists in the code. Decision (user, this turn): Option A — re-run /document Step 4 targeted at the drifted areas, then re-issue test-spec for the affected ACs.


Drift NOT covered by the B-ticket rename mapping

These are real findings. Each item is actual today-code state, NOT a "post-rename target". The _docs/ and the test specs in _docs/02_document/tests/ need updates to match.

D-JWT — Auth/JwtExtensions.cs (MAJOR)

# Aspect Doc claim (AC-5.*, modules/auth.md, components/05_identity, architecture.md § 7) Code today (Auth/JwtExtensions.cs)
J1 Algorithm HS256 (SymmetricSecurityKey(UTF-8(JWT_SECRET))) ECDSA-SHA256 (ValidAlgorithms = [SecurityAlgorithms.EcdsaSha256]) with JWKS keys
J2 Key material Shared HMAC secret in JWT_SECRET env var JWKS retrieved from admin via ConfigurationManager<JsonWebKeySet> + JwksRetriever + HttpDocumentRetriever { RequireHttps = true }
J3 Env var contract JWT_SECRET (single var) Three vars: JWT_ISSUER, JWT_AUDIENCE, JWT_JWKS_URL (NO JWT_SECRET)
J4 ValidateIssuer false true + ValidIssuer = <JWT_ISSUER>
J5 ValidateAudience false true + ValidAudience = <JWT_AUDIENCE>
J6 ClockSkew 1 minute 30 seconds
J7 Pinned algorithms not mentioned ValidAlgorithms = [EcdsaSha256] (forces algo to prevent HS256-confusion attack)
J8 RequireSignedTokens / RequireExpirationTime not explicitly mentioned both true
J9 Coupling to admin "Local validation; this service never calls back to admin" Calls admin for JWKS at startup + on ConfigurationManager refresh schedule
J10 Rotation model "Token signed with old JWT_SECRET → 401 across the entire device until coordinated re-deploy" JWKS rotation on admin + auto-refresh; no coordinated re-deploy needed
J11 Dev fallback JWT_SECRET=development-secret-key-min-32-chars!! if env unset (ADR-005 carry-forward) No fallback; ConfigurationResolver.ResolveRequiredOrThrow throws at startup if any of JWT_ISSUER/JWT_AUDIENCE/JWT_JWKS_URL is unset
J12 Authz policies Single "FL" policy; "GPS" is the post-B7 target (existing-code has both today) Today has both "FL" AND "GPS" — matches what the verification log already says, kept here for completeness

ACs / NFTs to revise: AC-5.1, AC-5.2, AC-5.3, AC-5.4, AC-5.5, AC-5.6, AC-5.7, AC-5.9; NFT-SEC-0109; NFT-RES-07; FT-N-08; results_report.md AC-5 entire group; environment.md JWT mock spec; test-data.md JWT mint section.

D-CONFIG — Program.cs + Infrastructure/ConfigurationResolver.cs (MAJOR)

# Aspect Doc claim Code today
C1 Required env vars Two: DATABASE_URL, JWT_SECRET (E1) Four: DATABASE_URL, JWT_ISSUER, JWT_AUDIENCE, JWT_JWKS_URL
C2 Configuration source order IConfigurationEnvironment.GetEnvironmentVariable → fallback Env var first (Environment.GetEnvironmentVariable), then IConfiguration key (e.g. Database:Url, Jwt:Issuer), then throw (no fallback)
C3 Dev fallbacks for JWT_SECRET / DATABASE_URL "ungated by IsDevelopment(); production deploy without env vars silently boots with the dev secret" (ADR-005 carry-forward) No fallbacks at all; production deploy without env vars now throws InvalidOperationException at startup. ADR-005 is OBSOLETE for this aspect
C4 Database:Url config-key alternative not mentioned in docs (env-only) Code reads Database:Url as fallback to DATABASE_URL env var
C5 Jwt:Issuer / Jwt:Audience / Jwt:JwksUrl config-key alternatives not mentioned Code reads each as fallback to its env var

ACs / NFTs to revise: AC-6.1, AC-6.2, E1, E3, E4 (no shared secret anymore); NFT-RES-05 (still tests DB-down crash, but the failure mode is more direct now); environment.md "Test Execution" env var list; test-data.md env vars; results_report.md AC-6 group + E3 lock test.

D-CORS — Infrastructure/CorsConfigurationValidator.cs (MAJOR)

# Aspect Doc claim (E9) Code today
O1 Permissive policy scope "AllowAnyOrigin / AllowAnyMethod / AllowAnyHeader in all environments (assumed safe behind suite reverse proxy)" Conditionally permissive: EnsureSafeForEnvironment THROWS in Production if CorsConfig:AllowedOrigins is empty AND CorsConfig:AllowAnyOrigin != true. Permissive only when explicit opt-in OR non-Production
O2 Config keys not mentioned New keys: CorsConfig:AllowedOrigins (string array) and CorsConfig:AllowAnyOrigin (bool)
O3 Warning behavior not mentioned Logs PermissiveDefaultWarning at startup when implicit-permissive applies (origins empty + AllowAnyOrigin=false + non-Production)

ACs / NFTs to revise: E9 restriction; results_report.md E9 lock test (currently doesn't exist; was deferred to follow-up); ADR-002 in architecture.md only if it discusses CORS.

D-DBSCHEMA — Database/DatabaseMigrator.cs (SMALL)

# Aspect Doc claim (data_parameters.md § 3) Code today
S1 created_date / first_seen_at / last_seen_at column type TIMESTAMPTZ TIMESTAMP (no timezone) — affects how DateTime kinds round-trip
S2 Foreign-key declarations "logical FK ... no DB-level FK constraint declared in migrator" (data_parameters.md § 3.2 + § 3.3 note) REFERENCES <parent>(id) declared on every FK in the migrator (flights.aircraft_id, waypoints.flight_id, orthophotos.flight_id, gps_corrections.flight_id + gps_corrections.waypoint_id, map_objects.flight_id)
S3 Default values not detailed Migrator sets DEFAULT 0 / DEFAULT FALSE / DEFAULT NOW() / DEFAULT '' on most non-nullable columns

ACs / NFTs to revise: data_parameters.md § 3 schema tables (TIMESTAMPTZ → TIMESTAMP, add REFERENCES notes, add DEFAULT notes); AC-2.8 (TOCTOU on FK) — actually now PARTLY mitigated by DB-level FK (insert would fail at DB layer with PG error 23503, not just app-layer); modules/database.md § Internal Logic.

D-FILTER — Services/AircraftService.cs + FlightService.cs (SMALL)

# Aspect Doc claim (AC-1.6) Code today
F1 Vehicle (today: Aircraft) name filter case sensitivity "case-sensitive contains on Name" (AC-1.6, data_parameters.md § 2.1) a.Name.ToLower().Contains(query.Name.ToLower())case-INSENSITIVE contains
F2 Mission (today: Flight) name filter case sensitivity not specified in AC-2.3 case-INSENSITIVE contains (same ToLower().Contains(ToLower()) pattern)
F3 Mission list ordering AC-2.3 doesn't specify OrderByDescending(f => f.CreatedDate) — newest first
F4 Vehicle list ordering AC-1.5 doesn't specify OrderBy(a => a.Name) — alphabetical ASC

ACs / NFTs to revise: AC-1.6 (case-INSENSITIVE); FT-N-01 (current test asserts "case mismatch returns 0 rows" which is WRONG against today's code — case is ignored, so a name=br query against BR-01 actually returns 1 row, not 0); add ordering specs to AC-1.5 and AC-2.3; FT-P-04 + FT-P-08 should assert ordering.

D-WP-NEST-CHECK — Services/WaypointService.cs (TINY)

# Aspect Doc claim (AC-4.2) Code today
W1 Parent-mission existence check "Parent mission missing → 404" Code's CreateWaypoint checks db.Flights.AnyAsync(f => f.Id == flightId) and throws KeyNotFoundException ✓; UpdateWaypoint and DeleteWaypoint use a composite WHERE w.FlightId == flightId && w.Id == waypointId and throw KeyNotFoundException if no match — meaning the test for "parent missing" returns 404 BUT the doc-implied "parent missing first, then waypoint missing" two-step check is collapsed into one.

ACs / NFTs to revise: minor — clarify in AC-4.2 that the check is "matching (flightId, waypointId) returns no row → 404", which collapses two error cases into one.

D-VERIFICATION-LOG — _docs/02_document/04_verification_log.md (META)

The verification log itself is wrong:

  • § 3 row F5 (JWT validation): says "JwtExtensions matches exactly" — wrong, see D-JWT above.
  • § 3 row F6 (Startup + migration): says "matches exactly" but the docs claim hardcoded fallbacks while code has ResolveRequiredOrThrowwrong, see D-CONFIG.
  • § 4.1 D6 (modules/middleware.md correction): correctly identifies the camelCase envelope, ✓.
  • § 4.2 F3 (carry-forward Swagger / CORS unconditional): "CORS unconditional" is wrong — code is gated. Swagger is still unconditional ✓.

Action: re-issue § 3 rows F5, F6 with the new evidence; demote § 4.2 F3 (CORS unconditional) into the corrected list.


Phase 1 — /document re-run in task mode, scope = drifted files

Inputs: this drift findings report. Skills: .cursor/skills/document/SKILL.md in Task mode. Files to update (estimate 1012 doc files):

File Sections to revise
_docs/02_document/architecture.md § 7 Cross-cutting (auth subsection: ECDSA+JWKS+iss/aud), § 7 (CORS subsection: gated), ADR-005 (mark obsolete or rewrite "no dev fallback" + "Swagger still ungated"), ADR-002 (no change — wire shape unaffected)
_docs/02_document/components/05_identity/description.md full rewrite of "Mechanism" + "Caveats" (ECDSA, JWKS, iss/aud, calls admin)
_docs/02_document/components/07_host/description.md Program.cs section (ConfigurationResolver, CorsConfigurationValidator); ADR-005 cross-ref
_docs/02_document/modules/auth.md full rewrite
_docs/02_document/modules/program.md rewrite startup section: env var contract, no fallback, CORS gating
_docs/02_document/modules/database.md TIMESTAMP (not TIMESTAMPTZ), REFERENCES declared, DEFAULT clauses
_docs/02_document/data_model.md § 11 schema table column types + FK note
_docs/02_document/04_verification_log.md re-issue § 3 F5+F6 rows; correct § 4.2 F3
_docs/02_document/state.json append decomposition_revised entry recording the verification re-run; update last_updated
_docs/00_problem/problem.md review for any auth-shape claims
_docs/00_problem/acceptance_criteria.md AC-1.6 (case), AC-1.5 + AC-2.3 (ordering), AC-5.15.7, AC-5.9, AC-9.1 (today both FL+GPS), AC-6.1, AC-6.2
_docs/00_problem/restrictions.md E1 (4 env vars), E3 (no fallback today), E4 (no shared secret), E9 (gated CORS), S6 (Swagger still ungated ✓ — no change)
_docs/00_problem/security_approach.md JWT validation, CORS gating, no dev secret
_docs/00_problem/input_data/data_parameters.md § 1 env vars (4 vars now), § 3 schema (TIMESTAMP, REFERENCES, DEFAULT)
_docs/01_solution/solution.md per-component table for 05 + 07

Phase 2 — /test-spec re-issue in scoped mode

Inputs: revised docs from Phase 1. Skill: .cursor/skills/test-spec/SKILL.md in cycle-update mode (NOT full re-run; scoped to AC-5, AC-6.1/6.2, AC-9.1, AC-1.5, AC-1.6, AC-2.3, E1, E3, E4, E9 + the 4 NFT families that those ACs feed). Files to revise:

File Scope
_docs/00_problem/input_data/expected_results/results_report.md re-issue AC-1 row 1.6, AC-5 entire group, AC-6 rows 6.16.2, AC-9 row 9.1, AC-1 ordering rows, AC-2 ordering rows; add E3+E9 lock rows
_docs/02_document/tests/environment.md replace "in-process JWT mint with HS256 shared secret" with "in-process ECDSA keypair + ephemeral JWKS HTTP service mock" (e.g. WireMock.NET serves /.well-known/jwks.json); add JWT_ISSUER + JWT_AUDIENCE + JWT_JWKS_URL env vars; remove JWT_SECRET
_docs/02_document/tests/test-data.md rewrite "External Dependency Mocks" — admin JWKS mock; rewrite Data Validation Rules JWT rows
_docs/02_document/tests/blackbox-tests.md revise FT-N-01 (case-insensitive); add ordering assertions to FT-P-04 + FT-P-08
_docs/02_document/tests/security-tests.md full revision of NFT-SEC-01 through NFT-SEC-09 (ECDSA, iss/aud, JWKS rotation, missing JWT_ISSUER startup throw)
_docs/02_document/tests/resilience-tests.md revise NFT-RES-05 (ResolveRequiredOrThrow failure modes — add scenarios for missing each of the 4 env vars); revise NFT-RES-07 (JWKS rotation, not shared-secret rotation)
_docs/02_document/tests/traceability-matrix.md re-trace AC-5, AC-6, AC-9, AC-1.5, AC-1.6, AC-2.3, E-rows
docker-compose.test.yml replace JWT_SECRET with JWT_ISSUER + JWT_AUDIENCE + JWT_JWKS_URL; add a jwks-mock service (e.g. WireMock or a small Kestrel test server)

Phase 3 — Resume autodev Step 4 (Code Testability Revision)

After Phase 1+2: re-enter existing-code Step 4 with the revised docs + test specs. The original Step 4 analysis result ("code is largely testable as-is") still holds — the JWT/CORS/Config drift didn't introduce hardcoded paths or singletons, it just made the code MORE testable than the docs described.

Expected outcome: Step 4 → "all scenarios testable as-is" → Step 5 (Decompose Tests, session boundary).


Cross-cutting acknowledgements

  • The B-ticket plan (B5B12) is unaffected. None of the drift overlaps with the rename/GPS-Denied work — the JWT/CORS/Config evolution happened independently.
  • The _docs/_process_leftovers/2026-05-14_rename-flights-to-missions.md leftover stays as-is.
  • The suite docs (../suite/_docs/00_roles_permissions.md, ../suite/_docs/05_identity*, etc.) likely have correlated drift on the JWT model. Out of scope for this repo's /autodev; flag at suite-level next time /autodev runs in the suite workspace.