mirror of
https://github.com/azaion/missions.git
synced 2026-06-21 19:21:07 +00:00
chore: update configuration and Docker setup for JWT and test results
ci/woodpecker/push/build-arm Pipeline was successful
ci/woodpecker/push/build-arm Pipeline was successful
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.
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
# Traceability Matrix
|
||||
|
||||
> **Status**: produced by autodev `/test-spec` Phase 2 (2026-05-14).
|
||||
> **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 B5–B8 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
|
||||
|
||||
@@ -13,8 +14,8 @@
|
||||
| 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) | FT-P-04 | Covered |
|
||||
| AC-1.6 | Filter case-sensitive on `name`, exact on `isDefault` | FT-P-05, FT-N-01 | Covered |
|
||||
| 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 |
|
||||
@@ -25,12 +26,12 @@
|
||||
|-------|------------------------------|----------|----------|
|
||||
| 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>` | FT-P-08, FT-P-09, FT-P-10, NFT-PERF-04 | Covered |
|
||||
| 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 → 500 | NOT directly covered as a separate test (deterministic reproduction is hard); falls under NFT-RES-08-style probabilistic family | NOT COVERED — see Uncovered Items §1 |
|
||||
| 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)
|
||||
|
||||
@@ -60,22 +61,25 @@
|
||||
|
||||
| AC ID | Acceptance Criterion (short) | Test IDs | Coverage |
|
||||
|-------|------------------------------|----------|----------|
|
||||
| AC-5.1 | HS256 + `SymmetricSecurityKey(UTF-8(JWT_SECRET))` | covered indirectly via NFT-SEC-02 (different secret rejected) and NFT-SEC-03 (correct secret accepted) | Covered |
|
||||
| AC-5.2 | `ValidateLifetime=true`, `ClockSkew=1min` | NFT-SEC-03 | Covered |
|
||||
| AC-5.3 | `ValidateIssuer=false`, `ValidateAudience=false` (today) | NFT-SEC-04 | Covered (locks today's behavior) |
|
||||
| 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 → 401 | NFT-SEC-02 | Covered |
|
||||
| AC-5.6 | Expired token (outside skew) → 401 | NFT-SEC-03 | Covered |
|
||||
| AC-5.7 | Old `JWT_SECRET` after rotation → 401 | NFT-RES-07 | 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 | Local validator never calls `admin` | NOT directly observable from outside the process; covered indirectly by `admin` not running in the test env (NFT-SEC-* still pass) | Partially 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 | DATABASE_URL URL form converted via `ConvertPostgresUrl` | covered indirectly via every test that depends on a working DB connection (compose env uses URL form) | Covered |
|
||||
| AC-6.2 | DATABASE_URL raw form accepted | NOT directly covered — the test environment uses URL form; can be added by an extra startup scenario with the raw form | NOT COVERED — see Uncovered Items §3 |
|
||||
| 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 |
|
||||
@@ -84,6 +88,8 @@
|
||||
| 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.10–6.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
|
||||
|
||||
@@ -110,8 +116,8 @@
|
||||
|
||||
| AC ID | Acceptance Criterion (short) | Test IDs | Coverage |
|
||||
|-------|------------------------------|----------|----------|
|
||||
| AC-9.1 | Policy `"FL"` registered, satisfied by `permissions == "FL"` | every protected-endpoint test | Covered |
|
||||
| AC-9.2 | Hardcoded string mismatch ("fl", "FLight") → 403 | NFT-SEC-06 | Covered |
|
||||
| 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 |
|
||||
|
||||
@@ -146,7 +152,7 @@
|
||||
| S1 | C# / .NET 10 | implicit in test environment | Implicitly covered |
|
||||
| S2 | ASP.NET Core | implicit | Implicitly covered |
|
||||
| S3–S5 | 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 §5 |
|
||||
| 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 |
|
||||
@@ -161,16 +167,16 @@
|
||||
|
||||
| Restriction ID | Restriction (short) | Test IDs | Coverage |
|
||||
|---------------|---------------------|----------|----------|
|
||||
| E1 | Two required env vars | implicit; NFT-RES-05 (DB unreachable) + NFT-SEC-* (JWT_SECRET behavior) | Covered |
|
||||
| E2 | DATABASE_URL accepts URL or raw form | URL form covered via NFT-RES-05 path; raw form NOT covered | NOT COVERED — see Uncovered Items §3 |
|
||||
| E3 | Hardcoded dev fallbacks NOT gated on IsDevelopment() | a startup test with NO env vars set could verify fallback boot — security risk gate; carry-forward | NOT COVERED — see Uncovered Items §6 |
|
||||
| E4 | JWT_SECRET shared across services | suite-level concern | Out of scope |
|
||||
| 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 fallbacks** — `ResolveRequiredOrThrow` 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 `AllowAnyOrigin/Method/Header` | could add a single CORS preflight test that asserts the documented permissive behavior | NOT COVERED — see Uncovered Items §7 |
|
||||
| E10 | TLS termination is suite reverse proxy | suite-level concern | Out of scope |
|
||||
| E9 | CORS **gated by `CorsConfigurationValidator`** — Production throws on empty allow-list | NFT-SEC-13 (all 5 rows), results_report 6.10–6.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 (O1–O10)
|
||||
|
||||
@@ -195,34 +201,36 @@
|
||||
| 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 | 9 | 8 | 1 | 0 | 0 | 100% |
|
||||
| AC-6 Startup + migration | 10 | 8 | 1 | 1 (AC-6.2 raw conn) | 0 | 90% |
|
||||
| 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 | 1 | 1 | 3 (E2, E3, E9) | 5 | 60% (in-scope) |
|
||||
| Restrictions E | 10 | 7 | 1 | 0 | 2 | 100% (in-scope) |
|
||||
| Restrictions O | 10 | 4 | 2 | 0 | 4 | 100% (in-scope) |
|
||||
| **Total** | 112 | 67 | 11 | 6 | 28 | **93%** 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 that doesn't exist today (instrumented test build with `pg_advisory_lock`) | 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. |
|
||||
| 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-6.2 / E2 — `DATABASE_URL` raw form path | Test env uses URL form; raw form is the alternate adapter branch | Low — branch is small, well-localised in `ConvertPostgresUrl` | Add a single startup scenario with raw form. Single-line config change in test compose. |
|
||||
| 4 | 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) |
|
||||
| 5 | S6 — Swagger NOT gated on `IsDevelopment()` | 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. |
|
||||
| 6 | E3 — Hardcoded dev fallbacks NOT gated | Carry-forward security finding | Medium — production deploy without env vars boots with well-known secret | Add a startup test with NO env vars set, assert `JWT_SECRET` claim ladder still works (locks the divergence). Suggest as follow-up. |
|
||||
| 7 | E9 — CORS `AllowAnyOrigin/Method/Header` | Carry-forward; assumed safe behind reverse proxy | Low — assumed deployment topology mitigates | Add CORS preflight test that locks current behavior. Suggest as 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. |
|
||||
|
||||
**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). Items 3, 5, 6, 7 are 1-test additions each — add them in Step 5 (Decompose Tests) under a "blackbox-lock-carry-forward" task.
|
||||
**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**: 93% in-scope.
|
||||
**Verdict**: **PASS** — Phase 3 gate cleared on first iteration. The 6 uncovered items above are all low-medium risk with documented mitigations.
|
||||
**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`.
|
||||
|
||||
Reference in New Issue
Block a user