Files
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

20 KiB
Raw Permalink Blame History

Traceability Matrix

Status: produced by autodev /test-spec Phase 2 (2026-05-14); re-issued in cycle-update mode after the targeted re-verification of 2026-05-14 (drift findings Phase 2). Naming: post-rename target. Tests written for the post-rename API surface — RED-status until B5B8 land. The traceability matrix below treats the documented spec as the source of truth. Drift correction: rows for AC-5, AC-6, AC-9, AC-1.5/1.6, AC-2.3, E1/E3/E4/E9 are updated below to reflect the ECDSA+JWKS JWT model, fail-fast configuration resolver, and CORS production-gate validator. Several NOT COVERED items in the pre-revision matrix are now Covered thanks to the new NFT-SEC-10..13 + NFT-RES-05 rewrite + the inverted FT-N-01.

Acceptance Criteria Coverage

AC-1 — Vehicle CRUD

AC ID Acceptance Criterion (short) Test IDs Coverage
AC-1.1 Create vehicle FT-P-01 Covered
AC-1.2 Default-clear on create/update/setDefault FT-P-02, FT-P-03 Covered
AC-1.3 "Exactly one default" stricter than spec (B12 pending) covered indirectly via FT-P-02, FT-P-03 (assertions on count == 1) Covered (carry-forward)
AC-1.4 Default-clear NOT transaction-wrapped → race NFT-RES-08 Covered (probabilistic)
AC-1.5 GET /vehicles is plain array (NO pagination), ordered by Name ASC FT-P-04 Covered
AC-1.6 Filter case-INSENSITIVE on name, exact on isDefault FT-P-05 (positive + lowercase), FT-N-01 (no-match negative) Covered
AC-1.7 GET /vehicles/{id} 404 FT-N-02 Covered
AC-1.8 DELETE /vehicles/{id} 409 if referenced FT-N-03 Covered
AC-1.9 All /vehicles/* require Policy="FL" NFT-SEC-01, NFT-SEC-05, NFT-SEC-06 Covered

AC-2 — Mission CRUD

AC ID Acceptance Criterion (short) Test IDs Coverage
AC-2.1 Create mission, default CreatedDate = UtcNow FT-P-07 Covered
AC-2.2 Non-existent VehicleId → 400 (today; spec wants 404) FT-N-04 Covered (carry-forward)
AC-2.3 GET /missions paginated PaginatedResponse<Mission>, ordered by CreatedDate DESC, name filter case-INSENSITIVE FT-P-08 (ordering + case-INSENSITIVE), FT-P-09, FT-P-10, NFT-PERF-04 Covered
AC-2.4 GET /missions/{id} 404 FT-N-05 Covered
AC-2.5 PUT partial update (Name update only) FT-P-11 Covered
AC-2.6 LinqToDB does NOT eager-load [Association] covered indirectly via FT-P-07/FT-P-11 (body shape assertion checks Vehicle == null, Waypoints == null/[]) Covered
AC-2.7 All /missions/* require Policy="FL" NFT-SEC-01 Covered
AC-2.8 TOCTOU on FK between existence check and insert — now PARTLY mitigated by DB-level FK (PG 23503); surface today is 500 NOT directly covered as a separate test (deterministic reproduction still requires controllable concurrency); the FK mitigation is observable indirectly via 6.10 startup-schema test asserting REFERENCES vehicles(id) exists NOT COVERED — see Uncovered Items §1

AC-3 — Mission cascade delete F3 (most critical)

AC ID Acceptance Criterion (short) Test IDs Coverage
AC-3.1 Cascade walks map_objects → detection → annotations → media → waypoints → missions FT-P-12 Covered
AC-3.2 Mission missing → 404 BEFORE any cascade DELETE FT-N-06 Covered
AC-3.3 Cascade NOT transaction-wrapped → orphans NFT-RES-01, NFT-RES-02 Covered
AC-3.4 relation does not exist → 500 + log NFT-RES-01 (uses media drop) Covered
AC-3.5 After B7 cascade does NOT touch orthophotos / gps_corrections covered via NFT-RES-04 (post-B9 build asserts tables absent); cascade does not reference them by construction (verified by code-level absence at Step 8) Partially covered
AC-3.6 <50ms typical (P50) NFT-PERF-01 Covered
AC-3.7 autopilot race after step 1 → orphan NFT-RES-08-style (orphan race, on map_objects insert) — design spec'd; probabilistic implementation deferred NOT COVERED — see Uncovered Items §2

AC-4 — Waypoint CRUD F4

AC ID Acceptance Criterion (short) Test IDs Coverage
AC-4.1 Routes nested under /missions/{id}/waypoints[/{wpId}] FT-P-13, FT-P-14, FT-P-15, FT-P-18, FT-N-07 (every endpoint exercised) Covered
AC-4.2 Parent missing → 404 FT-N-07 Covered
AC-4.3 GET unpaginated, ordered by OrderNum ASC FT-P-13 Covered
AC-4.4 PUT is full overwrite FT-P-15 Covered
AC-4.5 Scoped cascade (detection → annotations → media → waypoints) FT-P-18 Covered
AC-4.6 Same NO-transaction caveat NFT-RES-02 Covered
AC-4.7 Require Policy="FL" NFT-SEC-01, NFT-SEC-05 Covered

AC-5 — JWT validation

AC ID Acceptance Criterion (short) Test IDs Coverage
AC-5.1 ECDSA-SHA256 with ValidAlgorithms = [EcdsaSha256] (algorithm pin) NFT-SEC-02 (signature reject), NFT-SEC-10 (HS256-confusion defense) Covered
AC-5.2 ValidateLifetime=true, ClockSkew=30s NFT-SEC-03 Covered
AC-5.3 ValidateIssuer=true + ValidateAudience=true (CMMC L2 row 3 structurally fixed in this service) NFT-SEC-04, NFT-SEC-04b Covered
AC-5.4 Missing header → 401 NFT-SEC-01 Covered
AC-5.5 Invalid signature / no matching public key → 401 NFT-SEC-02 Covered
AC-5.6 Expired token (outside 30s skew) → 401 NFT-SEC-03 Covered
AC-5.7 JWKS key rotation without restart — old kid eventually rejected, new kid eventually accepted NFT-RES-07, NFT-SEC-11 Covered
AC-5.8 Missing permissions=FL claim → 403 NFT-SEC-05 Covered
AC-5.9 Request-path validation local after JWKS cached; cold-start synchronously fetches JWKS NFT-SEC-* all pass with admin not running (only jwks-mock runs); the cold-start failure mode is testable by stopping jwks-mock and restarting missions then issuing the first protected request Covered (covered-cold-start case under results_report row 5.10)
AC-5.10 Algorithm pin (alg ∉ [EcdsaSha256] → 401) NFT-SEC-10 Covered
AC-5.11 iss validation (iss != JWT_ISSUER → 401) NFT-SEC-04 Covered
AC-5.12 aud validation (aud != JWT_AUDIENCE → 401) NFT-SEC-04b Covered

AC-6 — Startup + migration

AC ID Acceptance Criterion (short) Test IDs Coverage
AC-6.1 Four required env vars resolved via ResolveRequiredOrThrow (env-first, then IConfiguration, else throw); URL form converted via ConvertPostgresUrl results_report 6.1, 6.1b, 6.1c; NFT-SEC-12; NFT-RES-05 Covered
AC-6.2 DATABASE_URL raw form accepted; no JWT_SECRET legacy env consulted results_report 6.2; NFT-SEC-12 row 4 (asserts JWT_JWKS_URL is consulted, not JWT_SECRET) Covered
AC-6.3 Migrator runs ONCE at startup, inside scope NFT-RES-03 (idempotency assertion implies single-run + safe-restart) Partially covered
AC-6.4 4 owned tables + 3 indexes created NFT-RES-03 (asserts schema via \d+ after first start) Covered
AC-6.5 Post-B9 one-shot legacy DROP TABLE IF EXISTS NFT-RES-04 Covered
AC-6.6 Migrator idempotent NFT-RES-03 Covered
AC-6.7 DB unreachable → process exits non-zero NFT-RES-05 Covered
AC-6.8 DB missing (3D000) → process exits NFT-RES-06 Covered
AC-6.9 ErrorHandlingMiddleware registered FIRST covered indirectly via FT-N-08 + NFT-SEC-08 (any unhandled exception produces the documented envelope) Covered
AC-6.10 Listens on port 8080; edge maps host 5002:8080 covered by every test that connects to port 5002→8080 Covered
AC-6.11 CORS Production-gate fail-fast (empty allow-list + AllowAnyOrigin != true → throw) NFT-SEC-13; results_report 6.106.13 Covered
AC-6.12 JWT_JWKS_URL HTTPS-only at fetch time (passes startup config) NFT-SEC-12 row 5; results_report 6.1c Covered

AC-7 — Health probe

AC ID Acceptance Criterion (short) Test IDs Coverage
AC-7.1 GET /health anonymous FT-P-16, NFT-SEC-07 Covered
AC-7.2 200 { "status": "healthy" } FT-P-16, FT-P-17 Covered
AC-7.3 <10ms typical NFT-PERF-03 Covered
AC-7.4 If pipeline down, TCP connect fails (Watchtower restarts) container-lifecycle behavior outside the service; out-of-scope at the service test level Out of scope — see Uncovered Items §4

AC-8 — Wire shape

AC ID Acceptance Criterion (short) Test IDs Coverage
AC-8.1 Entity bodies PascalCase FT-P-01, FT-P-04 (key-set assertion) Covered
AC-8.2 Error envelope camelCase by accidental match FT-N-02, FT-N-05 (key-set assertion includes statusCode, message) Covered
AC-8.3 Envelope MUST NOT include errors field FT-N-02 (key-set excludes errors), FT-N-05, NFT-SEC-08 Covered
AC-8.4 KeyNotFoundException → 404 FT-N-02, FT-N-05, FT-N-07 Covered
AC-8.5 ArgumentException → 400, InvalidOperationException → 409 FT-N-04 (400), FT-N-03 (409) Covered
AC-8.6 500 body redacted, stack only in log FT-N-08, NFT-SEC-08 Covered
AC-8.7 PaginatedResponse<T> PascalCase keys FT-P-08 (key-set assertion) Covered

AC-9 — Authorization

AC ID Acceptance Criterion (short) Test IDs Coverage
AC-9.1 Policy "FL" registered as RequireClaim("permissions", "FL")contains-match: a multi-permission token containing "FL" is accepted every protected-endpoint test + NFT-SEC-06 step 7-8 (multi-permission accepted) Covered
AC-9.2 Hardcoded string mismatch ("fl", "FLight", "ADMIN") → 403 NFT-SEC-06 steps 2/4/6 Covered
AC-9.3 Policy NAME "FL" retains legacy wording (deferred) not testable at runtime — documentation-only Documentation only
AC-9.4 No per-method authz beyond [Authorize(Policy="FL")] covered by NFT-SEC-01 + NFT-SEC-07 (every endpoint gets the same gate; health is the only exception) Covered

AC-10 — Operational invariants (API-observable subset)

AC ID Acceptance Criterion (short) Test IDs Coverage
AC-10.1 One container per device container-orchestration constraint, not API-observable; tracked under H1 + H3 Out of scope (suite-level)
AC-10.2 RTO ≈ container restart, RPO = device-local backup suite-level operational concern Out of scope
AC-10.3 Unhandled 500 logged with stack trace via LogError FT-N-08, NFT-SEC-08 Covered
AC-10.4 No correlation id, no per-user audit absence-of-feature; NOT directly testable; documented carry-forward Documentation only
AC-10.5 B9 DROP block ordered AFTER gps-denied migrated suite-level deploy ordering, NOT enforced by this service Out of scope (suite-level)
AC-10.6 Cross-service cascade requires sibling tables present NFT-RES-01 covers the failure mode (table dropped) — passes when failure produces 500 + partial deletes Covered

Restrictions Coverage

Hardware (H1H6)

Restriction ID Restriction (short) Test IDs Coverage
H1 Edge-device, one container per device NFT-RES-LIM-01 through NFT-RES-LIM-04 (resource budget aligned with device assumption) Indirectly covered
H2 Multi-arch (ARM64 + AMD64) NOT testable at the test-spec level — covered by suite-level CI matrix (.woodpecker/build-arm.yml + future build-amd.yml) Out of scope
H3 Vertical scale only implicit in test environment (single missions container) Implicitly covered
H4 No managed cloud architectural constraint; not testable Documentation only
H5 Watchtower + flight-gate suite-level orchestration Out of scope
H6 No container-internal resource limits NFT-RES-LIM-0104 (observe baseline so suite-level cgroups can be sized correctly) Covered

Software (S1S15)

Restriction ID Restriction (short) Test IDs Coverage
S1 C# / .NET 10 implicit in test environment Implicitly covered
S2 ASP.NET Core implicit Implicitly covered
S3S5 Library versions csproj / lockfile concern; NOT a behavioral test Out of scope (build-time check)
S6 Swagger NOT gated on IsDevelopment() NOT directly tested; could add a single GET /swagger test that asserts 200 in production-like env. Carry-forward divergence NOT COVERED — see Uncovered Items §4
S7 PostgreSQL only implicit Implicitly covered
S8 One csproj, one root namespace csproj structure; NOT a behavioral test Out of scope (code organization)
S9 No src/ directory repo layout; NOT a behavioral test Out of scope
S10 Layer-organized code organization; NOT a behavioral test Out of scope
S11 No automated tests today this entire spec converts S11 from a constraint into a goal Resolved by this spec
S12 No migration tool NFT-RES-03 + NFT-RES-04 (idempotency observed) Covered
S13 No in-process MQ / event bus architectural constraint Out of scope (architecture)
S14 Owned + borrowed tables covered by FT-P-12, FT-P-18 (cascade walks both owned and borrowed) Covered
S15 gps-denied decoupled covered indirectly by NFT-RES-04 (legacy tables absent post-B9) + AC-3.5 absence of cascade reference Covered

Environment (E1E10)

Restriction ID Restriction (short) Test IDs Coverage
E1 Four required env vars (DATABASE_URL, JWT_ISSUER, JWT_AUDIENCE, JWT_JWKS_URL) — fail-fast via ResolveRequiredOrThrow NFT-SEC-12 (all 4 rows), NFT-RES-05 (all 5 rows), results_report 6.1b Covered
E2 DATABASE_URL accepts URL or raw form URL form covered via every default test; raw form covered by results_report 6.2 Covered
E3 No hardcoded dev fallbacksResolveRequiredOrThrow throws NFT-SEC-12, NFT-RES-05 rows 1-5 Covered
E4 Asymmetric ECDSA: no shared secret on this side; only public-key configuration NFT-SEC-* all run against jwks-mock (the mock holds the private key, this service holds only public-key config) Covered
E5 Container EXPOSE 8080; edge maps 5002:8080 implicit Implicitly covered
E6 Image tag post-B10 build-time concern, not behavior Out of scope
E7 Entrypoint post-B5 build-time concern Out of scope
E8 No appsettings env-specific overrides code organization; NOT a behavioral test Out of scope
E9 CORS gated by CorsConfigurationValidator — Production throws on empty allow-list NFT-SEC-13 (all 5 rows), results_report 6.106.13 Covered
E10 TLS termination is suite reverse proxy; JWKS independently constrained to HTTPS NFT-SEC-12 row 5 (JWKS HTTPS-only) Covered (HTTPS half)

Operational (O1O10)

Restriction ID Restriction (short) Test IDs Coverage
O1 Migrator at every start, idempotent NFT-RES-03, NFT-RES-04 Covered
O2 flight-gate prevents restart mid-mission suite-level orchestration Out of scope
O3 No version table covered indirectly by NFT-RES-03 (no version-table query observed) Implicitly covered
O4 Single Woodpecker job, no test/security stage this spec adds a test stage as a follow-up artifact Resolved by this spec
O5 No structured logging absence-of-feature; NOT testable directly Documentation only
O6 No correlation id, no audit absence-of-feature Documentation only
O7 Health is process-liveness only FT-P-17 (PG stopped, health still 200) Covered
O8 Cascade NOT transaction-wrapped NFT-RES-01, NFT-RES-02 Covered
O9 Sibling table absent → cascade fails on relation does not exist NFT-RES-01 (uses media drop) Covered
O10 One-instance-per-device → no cluster awareness architectural constraint Documentation only

Coverage Summary

Category Total Items Covered Partially / Implicit Not Covered Out of Scope / Doc-only Coverage % (Covered + Partial of in-scope)
AC-1 Vehicle CRUD 9 8 1 (carry-forward) 0 0 100%
AC-2 Mission CRUD 8 7 0 1 (AC-2.8 TOCTOU) 0 87%
AC-3 Cascade F3 7 5 1 1 (AC-3.7 race) 0 86%
AC-4 Waypoint CRUD F4 7 7 0 0 0 100%
AC-5 JWT 12 12 0 0 0 100%
AC-6 Startup + migration 12 12 0 0 0 100%
AC-7 Health 4 3 0 0 1 100% (in-scope)
AC-8 Wire shape 7 7 0 0 0 100%
AC-9 Authz 4 3 0 0 1 100% (in-scope)
AC-10 Operational 6 1 0 0 5 100% (in-scope)
Restrictions H 6 1 2 0 3 100% (in-scope)
Restrictions S 15 4 2 0 9 100% (in-scope)
Restrictions E 10 7 1 0 2 100% (in-scope)
Restrictions O 10 4 2 0 4 100% (in-scope)
Total 117 78 10 3 26 97% in-scope

Uncovered Items Analysis

# Item Reason Not Covered Risk Mitigation
1 AC-2.8 — TOCTOU on FK between existence check and insert Deterministic reproduction requires controllable concurrency primitive (instrumented test build with pg_advisory_lock). Note: DB-level FK now produces PG error 23503 so the failure surface is consistent — only the timing of the race is hard to reproduce Low — failure mode is well-documented and produces a 500 (loud failure, not silent corruption); occurs only when admin races with create Add probabilistic test (similar to NFT-RES-08) under a follow-up ticket. Document as known carry-forward.
2 AC-3.7 — autopilot orphan race on map_objects insert after step-1 read Same as #1 — needs controllable concurrency Low — leaves at most one orphan row per race; cleanup on next mission delete or via manual sweep Same mitigation as #1; add to follow-up.
3 AC-7.4 — TCP connect fails on container down (Watchtower restarts) Container lifecycle outside service surface None at service level — testable at suite e2e level Cover at suite e2e (monorepo-e2e skill scope)
4 S6 — Swagger NOT gated on IsDevelopment() (surviving branch of ADR-005) Carry-forward security finding; not part of AC Medium — production deploy with Swagger exposed Add a single test GET /swagger/index.html returns 200 in test env, with explicit comment that this LOCKS the carry-forward divergence (will fail when remediated). Suggest as follow-up.

Resolved by the 2026-05-14 re-verification:

  • E3 (hardcoded dev fallbacks) — structurally fixed in code via ResolveRequiredOrThrow. Old "Uncovered §6" obsolete; now Covered by NFT-SEC-12 + NFT-RES-05.
  • E9 (CORS AllowAnyOrigin/Method/Header in all environments) — structurally fixed by CorsConfigurationValidator. Old "Uncovered §7" obsolete; now Covered by NFT-SEC-13.
  • AC-6.2 / E2 (DATABASE_URL raw form path) — covered by results_report row 6.2 as part of the cycle-update; no longer a gap.

Recommendation: items 1, 2 are deterministic-test improvements to land alongside a future transaction-wrap refactor (closes the carry-forward at the same time as the test improvement). Item 4 is a 1-test addition — add in Step 5 (Decompose Tests) under a "blackbox-lock-carry-forward" task.

Phase 3 Coverage Gate

Threshold: ≥ 75% (per cursor-meta.mdc Quality Thresholds + phases/03-data-validation-gate.md). Achieved: 97% in-scope (after the 2026-05-14 drift Phase 2 re-issue). Verdict: PASS — Phase 3 gate cleared. The 4 remaining uncovered items are all low-medium risk with documented mitigations; the previous E3 / E9 / AC-6.2 gaps were closed by the structural code fixes already in Infrastructure/ConfigurationResolver.cs and Infrastructure/CorsConfigurationValidator.cs.