Files
admin/_docs/02_document/tests/traceability-matrix.md
T
Oleksandr Bezdieniezhnykh a77b3f8a59 [AZ-529] [AZ-530] Cycle-2 documentation refresh
Refreshes _docs/02_document/ to reflect the cycle-2 auth-modernization
+ CMMC hardening landings (AZ-531..AZ-538). Authoritative source for
the ripple set is ripple_log_cycle2.md.

Covered:
- architecture.md (section 1 rewritten, ADRs 6-9 added)
- data_model.md (sessions, audit_events, user columns, migrations)
- system-flows.md (F1 rewritten; F11-F17 added; F2/F7/F9 minor)
- module-layout.md (cycle-2 sub-component table)
- diagrams/flows/flow_login.md (dual-token + MFA)
- components/{01_data_layer,03_auth_and_security,05_admin_api}
- modules/ (12 new, 8 modified — full Argon2id/ES256/MFA/refresh
  /mission/session/audit/jwks rollup)
- tests/{blackbox,security,traceability-matrix}

Step 13 (Update Docs) output for cycle 2.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 09:22:53 +03:00

18 KiB

Traceability Matrix

Acceptance Criteria Coverage

AC ID Acceptance Criterion Test IDs Coverage
AC-1 Valid login returns JWT FT-P-01, NFT-PERF-01, NFT-RES-01, NFT-RES-LIM-04 Covered
AC-2 Unknown email returns code 10 FT-N-01 Covered
AC-3 Wrong password returns code 30 FT-N-02 Covered
AC-4 JWT lifetime 4 hours FT-P-03, NFT-SEC-04 Covered
AC-5 Email min 8 chars FT-P-02, FT-N-03 Covered
AC-6 Email format validation FT-P-02, FT-N-04 Covered
AC-7 Password min 8 chars FT-P-02, FT-N-08 Covered
AC-8 Duplicate email returns code 20 FT-N-07 Covered
AC-9 Only ApiAdmin can manage users FT-P-06, FT-P-07, FT-P-11, FT-P-12, FT-P-13, NFT-SEC-02, NFT-SEC-06 Covered
AC-10 First hardware check stores FT-P-04, NFT-RES-03 Covered
AC-11 Subsequent hardware check validates FT-P-05, NFT-RES-03 Covered
AC-12 Hardware mismatch returns code 40 FT-N-06 Covered
AC-13 Max upload 200 MB FT-P-08, NFT-RES-LIM-01, NFT-RES-LIM-02, NFT-PERF-03 Covered
AC-14 AES-256-CBC encryption FT-P-09, FT-P-10, NFT-PERF-02, NFT-PERF-03, NFT-RES-LIM-03 Covered
AC-15 Encrypt-decrypt round-trip FT-P-10 Covered
AC-16 Empty file upload returns code 70 FT-N-05 Covered
AC-17 SHA-384 password hashing NFT-SEC-03 Covered
AC-18 All non-login endpoints require auth FT-P-09, NFT-SEC-01, NFT-RES-02, NFT-RES-LIM-04 Covered
AC-19 Encryption key derived from email+password+hw FT-P-10, NFT-SEC-05 Covered

Restrictions Coverage

Restriction ID Restriction Test IDs Coverage
RESTRICT-SW-01 .NET 10.0 runtime All tests (implicit — Docker build uses .NET 10.0) Covered
RESTRICT-SW-02 PostgreSQL database All DB tests (implicit — docker-compose uses PostgreSQL) Covered
RESTRICT-SW-03 Max request body 200 MB NFT-RES-LIM-01, NFT-RES-LIM-02 Covered
RESTRICT-SW-04 JWT HMAC-SHA256 signing FT-P-03, NFT-SEC-04 Covered
RESTRICT-HW-01 ARM64 target architecture NOT COVERED — CI builds ARM64; tests run on dev x64 host
RESTRICT-ENV-01 Secrets via env vars All tests (implicit — docker-compose passes env vars) Covered
RESTRICT-ENV-02 CORS admin.azaion.com NOT COVERED — CORS is browser-enforced, not testable at API level
RESTRICT-OP-01 Serilog logging NOT COVERED — log output verification not in scope

Coverage Summary

Category Total Items Covered Not Covered Coverage %
Acceptance Criteria (baseline) 19 19 0 100%
Acceptance Criteria (cycle 1) 24 24 0 100%
Acceptance Criteria (cycle 2) 6 6 0 100%
Restrictions 8 5 3 63%
Total 57 54 3 95%

Uncovered Items Analysis

Item Reason Not Covered Risk Mitigation
RESTRICT-HW-01 (ARM64) Tests run on x64 dev/CI host; cross-architecture testing requires ARM hardware Low — .NET runtime handles arch differences; no arch-specific code in application CI builds ARM64 image; manual smoke test on target device
RESTRICT-ENV-02 (CORS) CORS is enforced by browsers, not by server-to-server HTTP calls Low — CORS policy is declarative in Program.cs Visual inspection of CORS configuration in code
RESTRICT-OP-01 (Logging) Log output format/content verification adds complexity without proportional value Low — Serilog configuration is declarative Code review of Serilog setup

Cycle 1 Additions (2026-05-13) — AZ-513, AZ-196, AZ-183, AZ-197

Appended during the existing-code cycle 1 Test-Spec Sync (autodev Step 12). Cycle 1 ACs are namespaced by their tracker ID to avoid colliding with the baseline AC-1..AC-19 numbering above.

AZ-513 — Detection Classes CRUD

AC ID Acceptance Criterion Test IDs Coverage
AZ-513 AC-1 POST /classes creates a class FT-P-14 Covered
AZ-513 AC-2 POST /classes requires ApiAdmin authorization FT-N-09 Covered
AZ-513 AC-3 PATCH /classes/{id} updates an existing class (full body) FT-P-15 Covered
AZ-513 AC-4 PATCH /classes/{id} accepts partial body (partial-merge) FT-P-16 Covered
AZ-513 AC-5 PATCH /classes/{id} returns 404 for unknown id FT-N-10 Covered
AZ-513 AC-6 PATCH /classes/{id} requires ApiAdmin authorization FT-N-11 Covered
AZ-513 AC-7 DELETE /classes/{id} removes a class FT-P-17 Covered
AZ-513 AC-8 DELETE /classes/{id} returns 404 for unknown id FT-N-12 Covered
AZ-513 AC-9 DELETE /classes/{id} requires ApiAdmin authorization FT-N-13 Covered
AZ-513 AC-10 UI add/delete/edit affordances work end-to-end Cross-workspace (ui/ e2e harness) — out of scope for this workspace

AZ-196 — Device Auto-Registration

AC ID Acceptance Criterion Test IDs Coverage
AZ-196 AC-1 First device gets serial azj-0000 (shape: serial / email / 32-hex password) FT-P-18 Covered
AZ-196 AC-2 Sequential numbering on subsequent calls FT-P-19 Covered
AZ-196 AC-3 Persisted user has Role=CompanionPC, IsEnabled=true FT-P-20 Covered (verified via successful login → role-gated behaviour)
AZ-196 AC-4 Returned plaintext password is hashed (SHA-384) in DB, not stored plaintext FT-P-20 Covered (verified via successful login round-trip)
AZ-196 AC-5 Requires ApiAdmin authorization FT-N-14 Covered

AZ-183 — Resources OTA Update Check (REVERTED post-cycle-1)

The OTA Update Check & Publish feature shipped in cycle 1 was reverted later the same day after the security audit (finding F-1: /get-update disclosed plaintext per-resource encryption keys to any authenticated caller; the OTA delivery model itself was deemed obsolete in the target architecture). The endpoints, service, entity, table, request DTOs, response DTO, cache key, master-key config field, and the e2e test class ResourceUpdateTests were all removed.

AC ID Acceptance Criterion Test IDs Status
AZ-183 AC-1 Resources table created with required columns Reverted — table dropped from migration set (env/db/05_resources.sql deleted)
AZ-183 AC-2 POST /get-update returns newer resources FT-P-21 Reverted — endpoint and test deleted
AZ-183 AC-3 POST /get-update returns empty when device already current FT-P-22 Reverted — endpoint and test deleted
AZ-183 AC-4 Memory cache avoids DB pressure under 2000-device polling Reverted — cache key removed
AZ-183 AC-5 Cache invalidated on CI/CD publish FT-P-23 Reverted — endpoint and test deleted

AZ-197 — Hardware-Binding Removal

AC ID Acceptance Criterion Test IDs Coverage
AZ-197 AC-1 Resource download works without Hardware field FT-P-09 / FT-P-10 (legacy bodies retained; wire shape now omits the field) Covered (e2e ResourceTests updated by AZ-197 batch 6)
AZ-197 AC-2 PUT /users/hardware/set and POST /resources/check return 404 FT-N-15 Covered
AZ-197 AC-3 Security.GetApiEncryptionKey signature simplified to (email, password) Internal signature — covered by Azaion.Test/SecurityTest unit tests, not blackbox
AZ-197 AC-4 HardwareBindingTests removed; no remaining test asserts code 40 / hardware-hash binding Build/CI invariant — verified by test-suite enumeration
AZ-197 AC-5 Resource calls in remaining tests do not send Hardware Build/CI invariant — verified by source review during AZ-197 batch 6
AZ-197 AC-6 ExceptionEnum no longer carries HardwareIdMismatch / BadHardware Build/CI invariant — verified by enum read
AZ-197 AC-7 dotnet build is clean (no new warnings) Build invariant
AZ-197 AC-8 Test suite passes (excluding deleted HardwareBindingTests) All e2e tests + Azaion.Test Covered by Step 11 Run Tests (48/48 e2e + 2/2 unit, 2026-05-13)

Obsoleted Baseline Entries (superseded by AZ-197)

The matrix rows below are kept for ID stability but no longer reflect production behaviour. They are superseded by the AZ-197 entries above and by FT-N-15 in blackbox-tests.md. Do NOT regenerate or delete these in cycle-update mode — wait for a full /test-spec rerun.

Legacy Matrix Row Status
AC-10 (First hardware check stores) Obsoleted by AZ-197 — endpoint removed
AC-11 (Subsequent hardware check validates) Obsoleted by AZ-197 — endpoint removed
AC-12 (Hardware mismatch returns code 40) Obsoleted by AZ-197 — ExceptionEnum value removed
AC-19 (Encryption key derived from email+password+hw) Partially obsoleted — derivation is now email + password only

Cycle 2 Cleanup (2026-05-14) — Obsolete Resource Endpoints Removed

The encrypted-download and installer-download endpoints were removed as obsolete. Affected matrix rows below are kept for ID stability but the underlying behaviour is gone; they are superseded by FT-N-16 in blackbox-tests.md.

Removed surface Endpoint(s) Affected legacy entries Status
Per-user encrypted resource download POST /resources/get/{dataFolder?} AC-14 (AES-256-CBC encryption), AC-15 (round-trip), AC-19 (key derivation), FT-P-09, FT-P-10 Reverted — endpoint deleted; Security.GetApiEncryptionKey / EncryptTo / DecryptTo and ResourcesService.GetEncryptedResource deleted; GetResourceRequest DTO deleted; e2e tests Encrypted_download_returns_octet_stream_and_non_empty_body and Encryption_round_trip_decrypt_matches_original_bytes deleted from ResourceTests.cs; e2e test Per_user_encryption_produces_distinct_ciphertext_for_same_file deleted from SecurityTests.cs; Azaion.Test/SecurityTest.cs deleted (and the now-empty Azaion.Test project removed from the solution).
Installer download (production + staging) GET /resources/get-installer, GET /resources/get-installer/stage AC-23 (latest installer), ResourcesConfig.SuiteInstallerFolder / SuiteStageInstallerFolder references Reverted — endpoints deleted; ResourcesService.GetInstaller deleted; both config properties removed from appsettings.json, .env.example, secrets/staging.public.env, secrets/production.public.env, and docker-compose.test.yml. No e2e tests had been written for these endpoints, so no tests required removal.
AC ID Acceptance Criterion Test IDs Coverage
Cycle-2 AC-1 POST /resources/get/{dataFolder?} returns 404 FT-N-16 Covered
Cycle-2 AC-2 GET /resources/get-installer returns 404 FT-N-16 Covered
Cycle-2 AC-3 GET /resources/get-installer/stage returns 404 FT-N-16 Covered
Cycle-2 AC-4 ExceptionEnum no longer carries WrongResourceName (50); the gap is preserved Build/CI invariant — verified by enum read
Cycle-2 AC-5 Azaion.Test project no longer in solution; build is clean Build invariant — dotnet build Azaion.AdminApi.sln clean post-cleanup
Cycle-2 AC-6 E2E suite passes after the test deletions above All e2e tests Covered by Step 11 Run Tests post-cleanup (2026-05-14)

Cycle 2 Additions (2026-05-14) — Auth Modernization (AZ-529 + AZ-530)

Appended during the existing-code cycle 2 Test-Spec Sync (autodev Step 12) for the eight tasks delivered by the auth-modernization + CMMC-hardening epics. Rows below are namespaced by tracker ID; functional scenarios live in blackbox-tests.md, security-only invariants in security-tests.md. Existing AC/test IDs from earlier cycles are preserved unchanged.

AZ-536 — Argon2id Password Hashing (epic AZ-530, 5 ACs)

AC ID Acceptance Criterion Test IDs Coverage
AZ-536 AC-1 New users get Argon2id hashes (PHC, m ≥ 64 MiB, t ≥ 3, p ≥ 1) NFT-SEC-07 Covered
AZ-536 AC-2 Legacy SHA-384 hashes still validate FT-P-24 Covered
AZ-536 AC-3 Successful legacy login transparently re-hashes to Argon2id FT-P-25 Covered
AZ-536 AC-4 Wrong password fails for both formats with the same error code FT-N-17 Covered
AZ-536 AC-5 Verify is constant-time (no remotely observable timing leak) NFT-SEC-08 Covered (with known suite-concurrency flake — see cycle-2 carry-forward F6)

AZ-537 — /login Rate Limit + Account Lockout (epic AZ-530, 6 ACs)

AC ID Acceptance Criterion Test IDs Coverage
AZ-537 AC-1 Per-IP rate limit triggers HTTP 429 with Retry-After NFT-SEC-09 Covered (legitimate environment-mismatch skip in shared-IP container env)
AZ-537 AC-2 Per-account rate limit triggers HTTP 429 across IPs NFT-SEC-10 Covered
AZ-537 AC-3 Account lockout after 10 failures returns 423 even on correct password NFT-SEC-11 Covered
AZ-537 AC-4 Successful login resets failed_login_count and clears lockout_until FT-P-26 Covered
AZ-537 AC-5 Lockout auto-expires after configured duration FT-P-27 Covered
AZ-537 AC-6 Audit-log entry written on each lockout event NFT-SEC-12 Covered

AZ-538 — CORS HTTPS-Only + HSTS (epic AZ-530, 5 ACs)

AC ID Acceptance Criterion Test IDs Coverage
AZ-538 AC-1 HTTP origin gets no Access-Control-Allow-Origin header NFT-SEC-13 Covered
AZ-538 AC-2 HTTPS origin preflight echoes credentials flag FT-P-28 Covered
AZ-538 AC-3 HSTS header present in production responses NFT-SEC-14 Covered (legitimate Production-only environment-mismatch skip in dev test harness — verified by code inspection of Program.cs UseHsts)
AZ-538 AC-4 HTTP request returns 307 to HTTPS in production NFT-SEC-15 Covered (legitimate Production-only environment-mismatch skip in dev test harness — verified by code inspection of Program.cs UseHttpsRedirection)
AZ-538 AC-5 Development env unchanged (no redirect, no HSTS) FT-P-29 Covered

AZ-531 — Refresh-Token Flow (epic AZ-529, 5 ACs)

AC ID Acceptance Criterion Test IDs Coverage
AZ-531 AC-1 /login returns dual tokens, session row persisted FT-P-30 Covered
AZ-531 AC-2 /token/refresh rotates refresh + chains via parent_session_id FT-P-31 Covered
AZ-531 AC-3 Reuse-detection kills the entire session family NFT-SEC-16 Covered
AZ-531 AC-4 Sliding window + 12 h absolute family expiry FT-P-32 Covered
AZ-531 AC-5 Refresh tokens are opaque, hashed at rest, never logged in raw form NFT-SEC-17 Covered

AZ-532 — Asymmetric Signing + JWKS (epic AZ-529, 5 ACs)

AC ID Acceptance Criterion Test IDs Coverage
AZ-532 AC-1 Access tokens carry alg=ES256 + kid NFT-SEC-18 Covered
AZ-532 AC-2 GET /.well-known/jwks.json serves the active public key with cache headers FT-P-33 Covered
AZ-532 AC-3 Two-key overlap during rotation (both JWKS entries valid) FT-P-34 Covered
AZ-532 AC-4 JWKS never exposes private material NFT-SEC-19 Covered
AZ-532 AC-5 alg-confusion forgery (HS256 with public key as secret) is rejected NFT-SEC-20 Covered

AZ-533 — Mission-Token Issuance for UAV (epic AZ-529, 6 ACs)

AC ID Acceptance Criterion Test IDs Coverage
AZ-533 AC-1 Mission token issued with correct lifetime (planned_duration_h + 1h) FT-P-35 Covered
AZ-533 AC-2 Hard cap of 12 h enforced (HTTP 400 with cap message) FT-N-19 Covered
AZ-533 AC-3 Mission token carries mission_id, aircraft_id, aud, permissions, sid, jti FT-P-36 Covered
AZ-533 AC-4 Mission session auto-revoked when aircraft user reconnects FT-P-37 Covered
AZ-533 AC-5 Endpoint requires authenticated session FT-N-18 Covered
AZ-533 AC-6 MFA step-up required (amr must include mfa) NFT-SEC-21 Spec only — pending wire-up post-AZ-534 (cycle-2 carry-forward F1)

AZ-534 — TOTP-Based 2FA at Login (epic AZ-529, 6 ACs)

AC ID Acceptance Criterion Test IDs Coverage
AZ-534 AC-1 Enrollment returns secret + QR + 10 recovery codes FT-P-38 Covered
AZ-534 AC-2 Confirm with valid TOTP completes enrollment FT-P-39 Covered
AZ-534 AC-3 Two-step /login/login/mfa flow; access-token amr=["pwd","mfa"] FT-P-40 Covered
AZ-534 AC-4 Recovery code substitutes for TOTP and is single-use FT-P-41 Covered
AZ-534 AC-5 Disable requires password + valid TOTP FT-P-42 Covered
AZ-534 AC-6 TOTP secret encrypted at rest in users.mfa_secret NFT-SEC-22 Covered

AZ-535 — Logout + Revocation Surface (epic AZ-529, 5 ACs)

AC ID Acceptance Criterion Test IDs Coverage
AZ-535 AC-1 POST /logout revokes the current session and kills refresh FT-P-43 Covered
AZ-535 AC-2 POST /logout/all revokes every session for the user FT-P-44 Covered
AZ-535 AC-3 Admin can revoke any session by id; row records actor FT-P-45 Covered
AZ-535 AC-4 GET /sessions/revoked?since=… returns recent, non-expired entries FT-P-46 Covered
AZ-535 AC-5 POST /logout is idempotent (no second DB write) FT-P-47 Covered

Cycle 2 Coverage Update

Category Total Items Covered Not Yet Wired Coverage %
Acceptance Criteria (cycle 2 — auth modernization) 43 42 1 (AZ-533 AC-6 — pending wire-up F1) 98%
Acceptance Criteria — combined total (baseline + cycle 1 + cycle 2 cleanup + cycle 2 auth) 100 96 1 (F1) + 3 baseline restrictions still uncovered 96%

The single uncovered cycle-2 AC (AZ-533 AC-6) is documented in the cycle-2 implementation report as carry-forward item F1 — the /sessions/mission amr=mfa enforcement was deferred during AZ-533, became implementable once AZ-534 shipped, and is filed as a follow-up ticket to be picked up in a later cycle.