# Traceability Matrix

**Generated by `scripts/project-tracker/render.py`. The JSON is authoritative.**

| Requirement | Title | Status | Tasks | Tests | Evidence / blocker |
|---|---|---|---|---|---|
| `REQ-001` | Wi-Fi SoftAP with internal DHCP | **PARTIALLY_VERIFIED** | `TASK-101`, `TASK-102`, `TASK-103` | `TEST-101` | SoftAP is verified up and stable on the bench with the SSID derived from the efuse MAC, and the admin page is implemented. NOT verified: no real client has yet associated and taken a lease, so the headline acceptance criterion is still open (TASK-101, TEST-101). The captive portal and the full-size pool are descoped with the reasons above, not deferred by accident. |
| `REQ-002` | Wi-Fi STA scan, credential entry and auto-connect | **NOT_STARTED** | `TASK-102`, `TASK-105` | `TEST-102` | Not yet attempted. |
| `REQ-003` | Cellular connection management over A7670E | **PARTIALLY_VERIFIED** | `TASK-106`, `TASK-107` | `TEST-103` | VERIFIED. AT+CSQ, AT+CEREG?, AT+COPS? all respond. Registered on Airtel MCC 40490 / MNC 90, +CEREG: 0,1, CSQ 26,99 with patch antenna attached. Data bearer active: +CGPADDR: 1,100.90.93.199. |
| `REQ-004` | Dual-WAN automatic failover with NAT routing | **PARTIALLY_VERIFIED** | `TASK-020`, `TASK-108`, `TASK-109`, `TASK-110` | `TEST-104`, `TEST-105` | Precondition now proven, feature not yet implemented. TASK-020 (2026-09-29) verified on hardware that PPP negotiates end to end on the Airtel SIM: lwIP reached phase RUNNING with local=100.76.140.112 and gateway=10.64.64.64, held stable, and reproduced on a cold boot at local=100.77.131.255. See TEST-020 and BUG-006. That establishes the IP interface NAT forwarding requires.

Still unverified, and what actually gates this requirement: IP forwarding configuration, the NAT/SoftAP data path itself, dual-WAN failover behaviour, and the 3-second recovery target. Also note the negotiated address is carrier-grade NAT (100.x.x.x), so the LTE leg is outbound-only and not reachable inbound. |
| `REQ-005` | Camera capture and MJPEG stream over HTTP | **PARTIALLY_VERIFIED** | `TASK-201`, `TASK-202` | `TEST-201` | VERIFIED at sensor level. OV2640 confirmed MID 0x7fa2 PID 0x0026 VER 0x0042, SCCB addr 0x30. 320x240 RGB565 in PSRAM, luma stddev 15.4 (non-zero variance proves a live sensor). MJPEG over HTTP is NOT yet verified. |
| `REQ-006` | Camera control web page | **NOT_STARTED** | `TASK-203` | `TEST-202` | Not yet attempted. |
| `REQ-007` | GNSS acquisition, NMEA parsing and position fix | **VERIFIED** | `TASK-301`, `TASK-302` | `TEST-301` | VERIFIED. Cold-start fix 18.480816, 73.898640 (LOCK t+130s, 4 sats, hdop 7.3, 2026-09-30 17:46 local). Prior 2026-09-29: 18.480776, 73.897998 in 69 s, 17 GPS + 10 GLONASS, C/N0 up to 73. Requires PWRKEY GPIO42 power-on pulse and CGNSSPORTSWITCH=0,1 (1,1 errors on rev A7670M7_V1.11.1). REGRESSION NOTE 2026-09-30 18:37 local: the unmodified gnss_hold control then reported satsUsed=0 / fix=no for the full 500 s across four consecutive runs while NMEA kept streaming and the engine stayed powered, i.e. an RF/sky-view signature rather than a firmware fault; suspect the patch antenna was dislodged when the unit was moved. RE-VERIFY 2026-10-01 after a physical antenna reseat (see BUG-010). See ADR-002, ADR-006 and LESSONS_LEARNED.md. |
| `REQ-008` | Cellular LBS position fallback | **VERIFIED** | `TASK-303` | `TEST-302` | VERIFIED 2026-09-29. AT+CLBS=1,1 returned +CLBS: 0,18.481703,73.897415,550. Independently corroborated by the GPS fix at 18.480776, 73.897998 (separation well within the reported 550 m accuracy). Requires the 4G patch antenna attached (CSQ 16 -> 26) and AT+SIMEI populated from AT+CGSN. REGRESSION NOTE 2026-09-30: the identical lbsmatrix control flow, including after a true cold modem power-cycle, returned +CLBS: 10 in every mode while the modem data plane stayed healthy (NETOPEN OK, CIPOPEN to mqtt.easyiothings.com:80 and lbs-simcom.com:3002 OK). ret_code 10 is an LBS-application close on the carrier/SIMCom LBS service side, not a firmware or data-plane fault. RE-VERIFY 2026-10-01 by rerunning the unmodified lbsmatrix control; a return to +CLBS: 0 confirms a server-side service outage. NOTE: this requirement is not in the PRD; it was added after hardware testing showed a real, working fallback exists. |
| `REQ-009` | Location web page with live map | **NOT_STARTED** | `TASK-304` | `TEST-303` | Not yet attempted. |
| `REQ-010` | Cloud broker credential configuration UI | **NOT_STARTED** | `TASK-401`, `TASK-402` | `TEST-401` | Not yet attempted. Credentials must be stored in NVS and never committed to the repository. |
| `REQ-011` | Telemetry publish controls and interval | **NOT_STARTED** | `TASK-403`, `TASK-404` | `TEST-402` | Not yet attempted. |
| `REQ-012` | Direct MQTT telemetry publish from device over LTE | **PARTIALLY_VERIFIED** | `TASK-405`, `TASK-406` | `TEST-403` | NOT VERIFIED END TO END. The transport is proven: AT+CIPOPEN returns +CIPOPEN: 0,10 to broker.hivemq.com:1883, which is the command the TinyGSM A7672x driver uses. AT+CIPSTART returns ERROR on this firmware and must not be used. A full MQTT CONNECT plus CONNACK plus broker-received payload from the device has NOT yet been observed. An independent macOS subscriber has confirmed the broker side round trip on the user's HiveMQ Cloud cluster over WSS 8884, but that is not the device path. |
| `REQ-013` | MQTT subscriber command engine | **NOT_STARTED** | `TASK-407`, `TASK-408` | `TEST-404` | Not yet attempted. Depends on TASK-405 completing first. |
| `REQ-014` | SMS receiver engine with whitelist | **PARTIALLY_VERIFIED** | `TASK-501`, `TASK-502` | `TEST-501` | PARTIALLY VERIFIED. Outbound SMS confirmed working: +CMGS: 2 and +CMGS: 3, both received by the user. AT+CNMI=2,1,0,1,0 is required to get delivery reports. AT+CMGL="SENT" is NOT supported (CMS ERROR: Operation not allowed). Inbound +CMTI interception and whitelist enforcement not yet implemented. |
| `REQ-015` | SMS command parser and auto-reply | **NOT_STARTED** | `TASK-503` | `TEST-502` | Not yet attempted. Depends on TASK-501. |
| `REQ-016` | Incoming call management with auto-answer and whitelist | **PARTIALLY_VERIFIED** | `TASK-601`, `TASK-602` | `TEST-601` | PARTIALLY VERIFIED. Outgoing call confirmed: +VOICE CALL: BEGIN, active call state +CLCC: 1,0,2, user heard audio. Poll AT+CLCC? for state 2=active, 4=held; hang up with AT+ATH. Incoming call detection and whitelist not yet implemented. |
| `REQ-017` | Outgoing call execution and dialpad UI | **NOT_STARTED** | `TASK-603`, `TASK-604` | `TEST-602` | ATD dialing is proven. The web dialpad and the full-duplex I2S audio path are NOT verified. See RISK-003: the board's audio codec/mic wiring is unconfirmed and nomad-sentinel TASKS.md 1.11 records the audio path as UNSUPPORTED/unknown. |
| `REQ-018` | Offline speech recognition engine (ESP-SR) | **NOT_STARTED** | `TASK-701`, `TASK-702` | `TEST-701` | Not attempted. PRD NFR requires recognition latency strictly under 800 ms; that target is unvalidated. See RISK-004. |
| `REQ-019` | Voice contacts directory with wake-word dialing | **NOT_STARTED** | `TASK-703` | `TEST-702` | Not attempted. Depends on REQ-018 and on the audio hardware being confirmed present. |
| `REQ-020` | Asynchronous responsive web portal | **PARTIALLY_VERIFIED** | `TASK-801`, `TASK-802`, `TASK-803` | `TEST-801` | The async server, the seven-section navigation and the dashboard are implemented and inside the commit gate (make product), 2026-09-30. Verified only by a browser harness against a MOCK /api/status, which is a pre-flight on the HTML and is explicitly not hardware evidence. The device has never served a page, so TEST-801 remains NOT_RUN. Live camera, location, telemetry, SMS and telephony sections are NOT INTEGRATED and say so with their owning TASK id. No Basic Auth yet (deferred, TASK-404) -- this is a test surface, not a shipped admin portal. |
| `REQ-021` | NVS persistence of configuration and secrets | **NOT_STARTED** | `TASK-404`, `TASK-402` | `TEST-802` | Not started as a product feature, but the specific Rule 27 violation found during bootstrap is now closed: the hardcoded broker credential was removed from the tracker sketch, which now reads a gitignored secrets.h. All 7 baseline sketches compile and the secrets scanner passes. Remaining work is TASK-402: move ALL configuration (Wi-Fi, APN, contacts, SMS whitelist) into NVS so no secret or setting is hardcoded. The exposed credential must still be rotated at the broker; removal is not rotation. |
| `REQ-022` | Security hardening | **NOT_STARTED** | `TASK-804`, `TASK-405` | `TEST-803` | Not attempted. TLS on the cellular path is untested: AT+CSSLCFG and SNI are unverified on firmware A011B04A7670M7_F. See RISK-005. ADDITION 2026-09-30: the admin console now exists and currently has NO authentication at all, by explicit decision (ADR-007) rather than by omission. It is reachable only over the device's own SoftAP and is labelled a test surface in its own UI. TASK-404 (Basic Auth) and TASK-402 (NVS secret storage) must both close before this is treated as a production admin surface. |
| `REQ-023` | SD card storage for captures and logs | **PARTIALLY_VERIFIED** | `TASK-805` | `TEST-804` | VERIFIED on the Arduino sketch path: 3,984,588,800-byte FAT32 card, write/read/delete round trip passed. Pins SD_MMC.setPins(5, 4, 6) with 20000 kHz. CONFLICT: nomad-sentinel/TASKS.md 1.7 records SD as BLOCKED under ESP-IDF with err=0x107 on all card commands. The Arduino path works and the IDF path does not; this discrepancy is tracked as RISK-006 and BUG-001. |

## Coverage

- Requirements with at least one linked task: **23 / 23**
- Requirements with a test: **23 / 23**
- Requirements fully VERIFIED: **2 / 23**
- Requirements with a hardware measurement recorded: **7 / 23**

A requirement may legitimately have no test while it is NOT_STARTED. It may not be
VERIFIED without evidence — `validate.py` enforces that.
