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) |