diff --git a/.fusa-reqs-pending.json b/.fusa-reqs-pending.json index 9f4ff17..e80dda6 100644 --- a/.fusa-reqs-pending.json +++ b/.fusa-reqs-pending.json @@ -189,16 +189,6 @@ "tc18": "§11.2, TC18.txt L1182; §11.2.1, TC18.txt L1204", "gap": "Confirmed absent (2026-08-22): shares REQ-TIMED-012's exact root cause and evidence — rcp/acf.hpp's encode_acf_abb() and rcp/l2.hpp's/udp.hpp's TSCF framing already provide the wire-encoding half of this pair (a caller can build a TSCF-headed ACF_ABB frame today), but the decode/admission half (a dispatch entry point that decodes a real TSCF-wrapped ACF_ABB frame and threads its avtp_timestamp into timed_admit()/timed_due() the way REQ-TIMED-012 describes) does not exist anywhere in cpp-RCP. See REQ-TIMED-012's gap text for the full detail; both ids share one missing piece (a TSCF-aware dispatch/admission entry point) and would likely be closed together." }, - { - "id": "REQ-WDG-010", - "title": "The per-stream watchdog is kicked by every request received on that stream", - "text": "The RC Server's request-reception path shall call rcp::watchdog::Manager::on_request_received() (StreamWatchdog::kick_from_request()) for a request stream's stream_key on every request successfully received on that stream, so the per-stream watchdog measures the gap between consecutive requests from that RC Client.", - "standard": "iso26262", - "level": "ASIL-B", - "asil": "ASIL-B", - "tc18": "§12.7.7, TC18.txt L2936-L2938", - "gap": "NOT a behavioral gap -- a traceability-tag gap (2026-08-22, cpp-RCP Phase 6 batch 5, watchdog module): the behavior itself is genuinely, robustly implemented and tested. rcp/mock.hpp's dispatch_e2e_core() (the reference RC Server's actual request-reception path) calls rx_watchdog_kick(stream_id, ...) unconditionally, before any admission/validation check, at both of its call sites (mock.hpp:1147 inside dispatch_e2e_core, and mock.hpp:1343 inside dispatch_frame_e2e's per-member loop) -- rx_watchdog_kick() itself (mock.hpp:2097) forwards straight to rcp::watchdog::Manager::on_request_received(). tests/test_mock.cpp has a dedicated test, \"dispatch_e2e kicks the per-stream RxWatchdog (REQ-WDG-010) on every call...\" (test_mock.cpp:1739-1742+), that exercises exactly this behavior end-to-end (kick before an overflow, elapsed-time overflow after). rcp/sim.hpp's Simulator::dispatch/register_stream (sim.hpp:86-153) independently wires the same rcp::watchdog::Manager hook for the simulation harness. The blocker is purely mechanical: cpp-FuSa's trace tool (src/trace/trace.cpp) only counts a requirement as traced/tested when a literal `// fusa:req REQ-WDG-010` / `// fusa:test REQ-WDG-010` comment line exists somewhere in the tree (regex `//\\s*fusa:req\\s+`/`//\\s*fusa:test\\s+` followed by a REQ-\\S+ token) -- the existing `REQ-WDG-010:` prose mentions in mock.hpp's plain comments and the `[REQ-WDG-010]` Catch2 tag string in test_mock.cpp do NOT match that regex, so this id currently has ZERO formal annotations anywhere in the repo. CI hard-gates on this: .github/workflows/ci.yml's cpfusa-trace job runs `cpfusa trace --req-coverage 100` (fails outright, no `|| true`) and a second step that requires testedRequirements == totalRequirements (also a hard `exit 1` gate) -- adding this id to .fusa-reqs.json today, with no real tag anywhere, would fail CI. Fixing this requires adding the two formal tags to include/rcp/mock.hpp and tests/test_mock.cpp, both out of this batch's file-scope (batch 5 is restricted to .fusa-reqs.json/.fusa-reqs-pending.json/include/rcp/watchdog.hpp/tests/test_watchdog.cpp only) -- filed here so a future mock.hpp-scoped batch can add those two tags and migrate this entry into .fusa-reqs.json in the same change." - }, { "id": "REQ-E2E-004", "title": "length_with_crc() applies the length-accounting pre-adjustment, saturating on overflow", @@ -257,46 +247,6 @@ "tc18": "§13.6 (\"The CRC calculation for request and response follows the identical scheme.\"), TC18.txt L3828", "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 6, e2e module). The behavior itself is genuinely implemented and tested in include/rcp/e2e.hpp / tests/test_e2e.cpp. The blocker is that cpp-RCP's pre-existing REQ-E2E-001..014 catalog range predates this rewrite's later convention (established from REQ-E2E-021 onward) of reusing c-RCP's exact ids: those 14 entries were numbered independently, by the order features appeared in e2e.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope, which is restricted to e2e.hpp/test_e2e.cpp/lifecycle.hpp/test_lifecycle.cpp) can resolve the REQ-E2E-001..014 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-E2E-009 (\"Watchdog overflow purges normal requests and retains safety-tagged ones\", corresponding to c-RCP's REQ-E2E-014). Genuinely implemented+tested: tests/test_e2e.cpp's \"wrap/unwrap round trips an unpadded ACF_ABB payload\" / \"...a padded ACF_ABB payload...\" / \"...an ACF_GBB payload...\" TEST_CASEs each assert byte-identical round-tripping end-to-end." }, - { - "id": "REQ-E2E-033", - "title": "Each ACF message in a multi-ACF AVTPDU carries and is verified against its own CRC32", - "text": "When an AVTPDU carries more than one ACF message, a separate CRC32 shall be computed, appended to, and verified for each E2E-protected ACF message individually -- never one CRC across the whole AVTPDU payload.", - "standard": "iso26262", - "level": "QM", - "asil": "QM", - "tc18": "§13.6, TC18.txt L3789-3791", - "gap": "NOT a behavioral gap -- an out-of-scope-file traceability gap (2026-08-22, cpp-RCP Phase 6 batch 6, e2e module), the same pattern as this file's own pre-existing REQ-WDG-010 entry. The behavior is genuinely implemented and tested, but in rcp/mock.hpp / tests/test_mock.cpp -- both out of this batch's file-scope (restricted to e2e.hpp/test_e2e.cpp/lifecycle.hpp/test_lifecycle.cpp). Server::dispatch_frame_e2e() (mock.hpp, per-member loop citing \"REQ-E2E-033\" in its own doc comment) routes each ACF member independently through dispatch_e2e(), so a corrupted trailer on one member neither blocks nor is masked by another member's valid one; tests/test_mock.cpp carries a Catch2 [REQ-E2E-033] tag on at least one TEST_CASE exercising this. No formal `// fusa:req`/`// fusa:test` comment tag exists anywhere for this id -- filed here so a future mock.hpp-scoped batch can add the two formal tags and migrate this entry in." - }, - { - "id": "REQ-E2E-039", - "title": "The ms bit decides whether a message carries a CRC32 trailer in its last quadlet", - "text": "A message whose ACF header ms bit is 0 shall carry the CRC32 in its last quadlet, and a message whose ms bit is 1 shall carry no CRC32 at all, the CRC appearing only on the final (ms=0) fragment of a multi-segment message.", - "standard": "iso26262", - "level": "ASIL-B", - "asil": "ASIL-B", - "tc18": "§13.6, TC18.txt L3821-3823", - "gap": "NOT a behavioral gap -- an out-of-scope-file traceability gap (2026-08-22, cpp-RCP Phase 6 batch 6, e2e module), the same pattern as this file's own pre-existing REQ-WDG-010 entry. The behavior is genuinely implemented and tested, but in rcp/mock.hpp / tests/test_mock.cpp -- both out of this batch's file-scope. dispatch_e2e_fragment_core() (mock.hpp) reads the ms bit itself (via the decoded ACF header, not e2e::fragment_carries_crc()'s own abstract is_last_fragment boolean) to decide whether a fragment carries a trailer, citing \"REQ-E2E-039\" in its own doc comment; tests/test_mock.cpp carries a Catch2 [REQ-E2E-039] tag on at least one TEST_CASE exercising this. No formal `// fusa:req`/`// fusa:test` comment tag exists anywhere for this id -- filed here so a future mock.hpp-scoped batch can add the two formal tags and migrate this entry in. rcp::e2e::fragment_carries_crc() itself (e2e.hpp) is the abstract, is_last_fragment-parameterized primitive this dispatch behavior is built on, already correctly catalogued at REQ-E2E-038's own new entry (this batch)." - }, - { - "id": "REQ-E2E-041", - "title": "A CRC mismatch on a protected stream generates an error response as well as skipping execution", - "text": "When a request arrives on an E2E-protected stream and its CRC32 does not match, the RC Server shall not execute the request AND shall generate an error response carrying the CRC-mismatch wire error code (POCI_FAILURE, 12).", - "standard": "iso26262", - "level": "QM", - "asil": "QM", - "tc18": "§13.6, TC18.txt L3827-3828", - "gap": "NOT a behavioral gap -- an out-of-scope-file traceability gap (2026-08-22, cpp-RCP Phase 6 batch 6, e2e module), the same pattern as this file's own pre-existing REQ-WDG-010 entry. The behavior is genuinely implemented in rcp/mock.hpp -- both Server::dispatch_e2e_core() and dispatch_e2e_fragment_core() build a real acf::WireErrorCode::PociFailure ErrorResponse via acf::encode_error_payload() on a e2e::E2eErrc::crc_error verdict from unwrap()/compute_fragmented_crc(), never calling further dispatch, exactly matching this requirement's text (mock.hpp, dispatch_e2e_core and dispatch_e2e_fragment_core bodies) -- but neither rcp/mock.hpp nor tests/test_mock.cpp carries any tag (formal or Catch2-bracket) for this specific id at all, and both files are out of this batch's file-scope. Filed here so a future mock.hpp-scoped batch can add the formal tags (and, if none exists yet, a dedicated test asserting the wire error code specifically) and migrate this entry in." - }, - { - "id": "REQ-E2E-047", - "title": "A reassembled fragmented request too large to re-encode as a single ACF frame is rejected with a genuine error response, not silently dropped", - "text": "When a fragmented request's reassembled payload cannot be re-encoded as a single ACF_ABB/ACF_GBB frame because it exceeds that message type's own maximum payload, the RC Server shall answer with a genuine RequestRejected error response addressed to the request's own byte_bus_id/transaction_num, not silently drop the request.", - "standard": "iso26262", - "level": "ASIL-B", - "asil": "ASIL-B", - "tc18": "§13.7.11.3 (\"...which requires segmentation, which is supported by the fields 'ms' and 'segment_num'.\"), TC18.txt L5859-5860", - "gap": "NOT a behavioral gap -- an out-of-scope-file traceability gap (2026-08-22, cpp-RCP Phase 6 batch 6, e2e module), the same pattern as this file's own pre-existing REQ-WDG-010 entry. The behavior is genuinely implemented in rcp/mock.hpp: dispatch_e2e_fragment_core() checks the reassembled payload's size against acf::kAcfAbbMaxPayload/kAcfGbbMaxPayload BEFORE attempting to re-encode it, and on overflow builds a real regmap::RegMapErrc::request_rejected error response via set_error_response() rather than returning silently (mock.hpp, citing \"REQ-E2E-038/c-RCP issue #614/#616\" in its own doc comment for the check) -- but neither rcp/mock.hpp nor tests/test_mock.cpp carries any tag (formal or Catch2-bracket) for this specific id, and both files are out of this batch's file-scope. Filed here so a future mock.hpp-scoped batch can add the tags (and confirm/extend the test coverage specifically for this oversized-reassembly path) and migrate this entry in." - }, { "id": "REQ-LIFECYCLE-023", "title": "HW_config's own lock rule (HW_GENERIC) also governs EP_GEN_CFG and QUEUE_CFG writes, per Figure 17", @@ -1595,6 +1545,464 @@ "level": "QM", "asil": "QM", "gap": "Deliberate architecture divergence, not an oversight (confirmed 2026-08-22, cpp-RCP Phase 6 batch 12): Adapt() returns std::unique_ptr (rcp/adapt.hpp), not a manually-refcounted rcp_relay_caller_t* -- C++ RAII ownership (move-only unique_ptr, or a caller wrapping it in shared_ptr if shared ownership is genuinely needed) supersedes c-RCP's own hand-rolled retain/release refcount protocol entirely; there is no retain() function and no refcount field to test. tests/test_adapt.cpp's own header comment (lines 69-75) explicitly documents this as a test case c-RCP's test_adapt.c has that this file deliberately does not port: 'std::unique_ptr already gives Adapt()'s return value RAII lifetime with no manual retain/release protocol to test.'" + }, + { + "id": "REQ-MOCK-001", + "title": "rcp_mock_strerror() never returns NULL", + "text": "rcp_mock_strerror() shall return a non-NULL, human-readable string for every rcp_mock_errc_t value, including one outside the defined enum.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-001 there is 'Server starts HW_UNCONFIGURED with a representative nine-endpoint register map' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-001 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: GENUINE GAP (implemented, untested): Server's own dispatch_category() error category's Cat::message() (include/rcp/mock.hpp:355-385) covers every DispatchErrc value plus a `default: return \"rcp/mock: unknown dispatch outcome\";` branch -- structurally equivalent to this requirement's contract (never empty, handles out-of-range) via std::error_category::message()'s own can-never-be-null-by-construction idiom -- but zero TEST_CASE anywhere in tests/test_mock.cpp calls dispatch_category() or .message() directly or indirectly (grep confirms)." + }, + { + "id": "REQ-MOCK-002", + "title": "rcp_mock_server_new() starts HW_UNCONFIGURED with an initialized, empty register map", + "text": "rcp_mock_server_new() shall return a server whose rcp_mock_server_state() is RCP_LIFECYCLE_HW_UNCONFIGURED and whose rcp_mock_server_regmap() has been initialized via rcp_regmap_general_init() (svr_ep_count 0, svr_root_client_index RCP_REGMAP_NO_ROOT_CLIENT), with no endpoints registered.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-002 there is 'advance_to_rcp_configured drives the lifecycle straight to RCP_CONFIGURED' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-002 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: GENUINE GAP (opposite behavior): Server() (mock.hpp:607-627) does start HwUnconfigured (satisfying that sub-claim), but calls make_initial_register_map() which sets regs.general.svr_ep_count = 10 and populates all 10 ep_id_mapping rows immediately -- the OPPOSITE of this requirement's 'svr_ep_count 0, no endpoints registered' claim. Test tests/test_mock.cpp:58-99 (cpp's own REQ-MOCK-001) asserts svr_ep_count == 10, not 0. Architectural: cpp's Server always starts fully populated with its fixed 10-endpoint set (no dynamic registration exists at all -- see REQ-MOCK-007/008/009's own gaps below)." + }, + { + "id": "REQ-MOCK-003", + "title": "rcp_mock_server_destroy() frees every endpoint's queued requests and is NULL-safe", + "text": "rcp_mock_server_destroy() shall free every registered endpoint's queued (undelivered) requests and srv itself, and shall be a no-op when srv is NULL.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-003 there is 'EP0 read answers the register map's magic number for any client' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-003 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: NOT APPLICABLE -- C++ RAII: mock::Server has no destroy()/factory-pointer API at all; it is a non-copyable, stack/member-owned value type (`Server(const Server&) = delete;`, mock.hpp:626). Its endpoints' pending-request storage is a fixed-size std::array (server.hpp, kMaxPending=32), so the compiler-generated destructor recursively frees everything with no dynamic allocation to leak. There is no nullable 'srv' pointer a caller could pass to a destroy function, so 'NULL-safe' is moot -- the language eliminates the concept this requirement guards against, not a behavioral gap." + }, + { + "id": "REQ-MOCK-004", + "title": "rcp_mock_server_state() reports the server's current lifecycle state", + "text": "rcp_mock_server_state() shall return srv's own current rcp_lifecycle_state_t, reflecting the most recent successful rcp_mock_server_transition().", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-004 there is 'EP0 write via dispatch is always rejected' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-004 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested, under a different id: mock::Server::lifecycle() (mock.hpp:634) exposes the real rcp::lifecycle::ServerLifecycle&; ServerLifecycle::state() (lifecycle.hpp:370) returns the current state, reflecting the most recent successful advance(). Tested pervasively, e.g. tests/test_mock.cpp:61 and :105. This exact claim is effectively covered by cpp's own REQ-LIFECYCLE-* catalog entries for ServerLifecycle::state(), not a REQ-MOCK entry (cpp never duplicates lifecycle state onto Server itself, an even thinner passthrough than c-RCP's own wrapped rcp_mock_server_state())." + }, + { + "id": "REQ-MOCK-005", + "title": "rcp_mock_server_transition() is a thin passthrough to rcp_lifecycle_transition()", + "text": "rcp_mock_server_transition() shall apply lifecycle.h's own rcp_lifecycle_transition() rules to srv's own state, leaving srv's state unchanged and returning the same error code rcp_lifecycle_transition() would for a rejected transition.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-005 there is 'write_whole_map requires the root client' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-005 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested, under a different id: rcp::lifecycle::ServerLifecycle::advance() (lifecycle.hpp:377-395) IS the transition-rule implementation itself; mock::Server::lifecycle() (mock.hpp:634) exposes it directly, with no separate wrapper duplicating or re-checking state. Rejected-transition behavior (state unchanged, LifecycleErrc::invalid_transition returned) is tested at tests/test_lifecycle.cpp:78-84 (REQ-LIFECYCLE-002), exercised by mock::Server via advance_to_rcp_configured() (mock.hpp:962-965) in essentially every mock test. Filed under REQ-LIFECYCLE-002 in cpp's catalog, not REQ-MOCK, since cpp IS the lifecycle object rather than wrapping a copy of its state." + }, + { + "id": "REQ-MOCK-006", + "title": "rcp_mock_server_regmap() exposes srv's own register map for direct mutation", + "text": "rcp_mock_server_regmap() shall return a non-NULL pointer to srv's own rcp_regmap_general_t, valid for srv's lifetime, through which a caller may freely read and write every field.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-006 there is 'Operational GPIO/SPI requests are rejected before RCP_CONFIGURED' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-006 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested: `regmap::RegisterMap& registers() noexcept { return regs_; }` (mock.hpp:637) -- a reference, never null by construction, satisfying 'non-NULL pointer... through which a caller may freely read and write every field' directly. Mutation tested via set_request_stream_cfg()/set_ep_id_map() (mock.hpp:659-673, tested tests/test_mock.cpp:1311-1367); reads exercised throughout the whole test file." + }, + { + "id": "REQ-MOCK-007", + "title": "rcp_mock_server_add_endpoint() registers a new endpoint slot and updates svr_ep_count", + "text": "rcp_mock_server_add_endpoint() shall, on success, add a slot addressed at byte_bus_id with generic config ep_type/ep_used=true and a queue starting at the given ep_enable, and shall update rcp_mock_server_regmap()'s own svr_ep_count to the new total registered-endpoint count.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-007 there is 'GPIO write applies evt[2:0] semantics and read reflects the new state' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-007 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: GENUINE GAP: no add_endpoint() (or equivalent) exists anywhere in include/rcp/mock.hpp or include/rcp/server.hpp (grep confirms zero matching function definitions). Architectural: cpp's 10 operational endpoints (GPIO/SPI/I2C/ADC/PWM_IN/LIN/CAN/UART/ISELED/MDIO) are fixed Server members constructed at compile time, never dynamically registered at runtime -- a deliberate design divergence from c-RCP's generic pluggable mock harness, not an oversight (see cpp's own REQ-MOCK-001/002 titles, 'nine/ten-endpoint register map')." + }, + { + "id": "REQ-MOCK-008", + "title": "rcp_mock_server_add_endpoint() rejects a duplicate byte_bus_id or a full endpoint table", + "text": "rcp_mock_server_add_endpoint() shall return RCP_MOCK_ERR_DUPLICATE_BUS_ID, without adding a slot, when byte_bus_id is already registered, and RCP_MOCK_ERR_CAPACITY when srv already holds RCP_MOCK_MAX_ENDPOINTS endpoints.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-008 there is 'GPIO write with evt_ack set produces an Acknowledge response' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-008 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: GENUINE GAP: follows directly from REQ-MOCK-007's absence -- with no add_endpoint() at all, there is no duplicate-byte_bus_id or full-table rejection logic to check (grep for DUPLICATE_BUS_ID/CAPACITY-shaped symbols in mock.hpp/server.hpp: no matches)." + }, + { + "id": "REQ-MOCK-009", + "title": "rcp_mock_server_remove_endpoint() removes a registered slot and updates svr_ep_count, or reports not-found", + "text": "rcp_mock_server_remove_endpoint() shall, for a registered byte_bus_id, free its queued requests, remove its slot, decrement svr_ep_count, and return true; for an unregistered byte_bus_id it shall leave srv unchanged and return false.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-009 there is 'SPI transfer answers with the scripted POCI-in bytes for the addressed channel' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-009 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: GENUINE GAP: no remove_endpoint() (or equivalent) exists anywhere in include/rcp/mock.hpp or include/rcp/server.hpp (grep confirms). Endpoints cannot be removed at runtime in cpp's fixed-set design -- same root cause as REQ-MOCK-007/008." + }, + { + "id": "REQ-MOCK-010", + "title": "rcp_mock_server_set_endpoint_enable() sets a registered endpoint's ep_enable, or reports not-found", + "text": "rcp_mock_server_set_endpoint_enable() shall set the ep_enable flag of the endpoint at a registered byte_bus_id and return true, or return false without effect for an unregistered byte_bus_id.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-010 there is 'dispatch to an unmapped byte_bus_id reports invalid_parameter' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-010 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested (positive path), under a different id; negative path untested: admission(byte_bus_id) (mock.hpp:724-735) returns a `server::Endpoint*` (nullptr for any byte_bus_id outside the fixed 10) through which server::Endpoint::set_enable()/ep_enable() (server.hpp:406-411) is reached directly -- 'expose the real subsystem object, don't wrap every method' convention. Positive path tested: tests/test_mock.cpp:1167/1181 (tagged [REQ-SRV-017]). No dedicated test exercises admission() on an out-of-range byte_bus_id returning nullptr for this specific call, and no Server-level bool-returning wrapper exists (unlike pending_count/watchdog_purge/notify_trigger, which ARE wrapped) -- caller must null-check admission() itself." + }, + { + "id": "REQ-MOCK-011", + "title": "rcp_mock_server_endpoint_queue_len() returns a registered endpoint's queue length, or 0", + "text": "rcp_mock_server_endpoint_queue_len() shall return the number of requests currently queued on the endpoint at a registered byte_bus_id, or 0 for an unregistered byte_bus_id.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-011 there is 'I2C dispatch applies Table 33 Row 2 evt[2:0] classification before touching endpoint state' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-011 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested (positive path), under a different id; negative path untested: server::Endpoint::queue_len() (server.hpp:505), reached via admission(byte_bus_id). Tested at tests/test_mock.cpp:1053, 1094, 1112, 1451 (various TEST_CASEs). Same caveat as REQ-MOCK-010's own collision entry -- no dedicated Server-level wrapper, and no test of the 'unregistered id' 0-return path." + }, + { + "id": "REQ-MOCK-012", + "title": "rcp_mock_server_dispatch() consults rcp_lifecycle_should_accept() before anything else", + "text": "rcp_mock_server_dispatch() shall call lifecycle.h's rcp_lifecycle_should_accept() with srv's own state and the given time_sync_supported/avtp_subtype/acf_msg_type/byte_bus_id, and shall return RCP_MOCK_DISPATCH_DROPPED with *out_response left zeroed whenever it returns false, without consulting any endpoint slot.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-012 there is 'Operational I2C requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-012 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: GENUINE GAP: a free function rcp::lifecycle::should_accept(state, time_sync_supported, avtp_subtype, byte_bus_id, ...) DOES exist (lifecycle.hpp:591-663, REQ-LIFECYCLE-014..017/028/029/032/033), but grep confirms it is never called anywhere in include/rcp/mock.hpp. Instead, mock::Server::dispatch() (mock.hpp:1011-1041) first switches on req.byte_bus_id to route to one of 10 dispatch_() functions, and only INSIDE each one does operational_requests_allowed() (mock.hpp:2688-2690: a bare `lifecycle_.state() == ServerState::RcpConfigured` check with none of should_accept()'s TSCF/NTSCF/discovery-window/avtp_subtype logic) run -- AFTER endpoint routing, not before. A genuine architectural divergence from this requirement's specific claim (which function is called, and its position in the pipeline), not just an API-shape translation." + }, + { + "id": "REQ-MOCK-013", + "title": "rcp_mock_server_dispatch() reports an unregistered byte_bus_id once admission succeeds", + "text": "rcp_mock_server_dispatch() shall return RCP_MOCK_DISPATCH_ERR_UNKNOWN_BUS, with *out_response left zeroed, when rcp_lifecycle_should_accept() accepts the frame but byte_bus_id names no registered endpoint.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-013 there is 'ADC dispatch applies Table 33 Row 2 evt[2:0] classification before consuming a scripted sample' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-013 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: GENUINE GAP + confirmed conformance divergence, flagged but NOT fixed in this batch: mock.hpp's dispatch() fallback branch for an unmapped byte_bus_id (mock.hpp:1039-1040) calls set_error_response()/builds a REAL wire ErrorResponse (WireErrorCode::InvalidParameter) -- NOT the zeroed/no-response outcome this requirement (and TC18 §12.9.1, and c-RCP post-audit) require. This is the SAME already-catalogued, already-tested behavior as cpp's own REQ-MOCK-010 ('dispatch to an unmapped byte_bus_id reports invalid_parameter', tests/test_mock.cpp:993-1005) -- a confirmed, test-verified TC18 conformance concern, but intentionally not touched by this batch since fixing it would change an already-catalogued, already-tested cpp behavioral claim (REQ-MOCK-010) rather than close a mere traceability gap. Left for a dedicated conformance-fix batch. See REQ-MOCK-030's own pending entry below for the same root cause under its free id." + }, + { + "id": "REQ-MOCK-014", + "title": "rcp_mock_server_dispatch() runs the endpoint's handler immediately when its queue is enabled", + "text": "rcp_mock_server_dispatch() shall, for a registered and ep_enable=true endpoint, invoke its registered rcp_mock_endpoint_handler_fn synchronously with the given request/request_len and that endpoint's own user_data, populate *out_response from it, and return RCP_MOCK_DISPATCH_OK.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-014 there is 'Operational ADC requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI/I2C' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-014 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested: admit_and_classify()'s `AdmitOutcome::ExecuteNow` branch (mock.hpp:2272-2285) -- the caller (each dispatch__inner()) runs its handler body immediately; endpoints default-construct enabled (server.hpp: `explicit Endpoint(bool ep_enable = true)`). Every basic per-type 'plain request answers with...' test exercises this path, e.g. tests/test_mock.cpp:174-208 (tagged [REQ-MOCK-007], cpp's own colliding id)." + }, + { + "id": "REQ-MOCK-015", + "title": "rcp_mock_server_dispatch() queues the request without running the handler when the endpoint is disabled", + "text": "rcp_mock_server_dispatch() shall, for a registered and ep_enable=false endpoint, append the request to that endpoint's queue without invoking its handler, leave *out_response zeroed, and return RCP_MOCK_DISPATCH_QUEUED.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-015 there is 'PWM_IN dispatch applies Table 33 Row 2 evt[2:0] classification before touching the scripted measurement' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-015 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested: admit_and_classify()'s `AdmitOutcome::Queued` branch (mock.hpp:2287-2305) sets out_resp to default-constructed (or Acknowledge iff evt_ack) and returns DispatchErrc::queued. Directly tested: tests/test_mock.cpp:1034-1074 ('A disabled endpoint queues a request instead of executing it...')." + }, + { + "id": "REQ-MOCK-016", + "title": "rcp_mock_server_drain_endpoint() runs the oldest queued request's handler once re-enabled", + "text": "rcp_mock_server_drain_endpoint() shall, for a registered endpoint with ep_enable=true and a non-empty queue, dequeue its oldest request, invoke its registered handler with that request, populate *out_response from it, and return true.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-016 there is 'Operational PWM_IN requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI/I2C/ADC' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-016 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested, as a composite split across two calls rather than one atomic function: drain_one() (mock.hpp:779-782) dequeues the raw frame only -- per its own doc comment, this mock has no generic byte-level handler entry point, so a caller decodes it via acf::decode_acf_abb() and calls the matching dispatch_*() to actually run the handler. The full end-to-end flow (drain, decode, redispatch through the real handler, get a real response) is exercised in tests/test_mock.cpp:1150-1195 (tagged [REQ-SRV-017]), asserting the redispatched response is a genuine WriteResponse and the GPIO state actually changed -- behaviorally equivalent end-to-end, an intentional scope reduction documented in this file's own header comment." + }, + { + "id": "REQ-MOCK-017", + "title": "rcp_mock_server_drain_endpoint() reports nothing to drain without invoking the handler", + "text": "rcp_mock_server_drain_endpoint() shall return false, with *out_response left zeroed and without invoking any handler, when the addressed endpoint's queue is empty or its ep_enable is false.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-017 there is 'LIN dispatch applies Table 33 Row 2 evt[2:0] classification before touching endpoint state' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-017 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested: server::Endpoint::drain_one() (server.hpp:497-504) returns false immediately when `!ep_enable_ || queue_len_ == 0`, reached via mock.hpp:779-782. Tested: tests/test_mock.cpp:1179 (`REQUIRE_FALSE(server.drain_one(...))` while still disabled), same TEST_CASE as REQ-MOCK-016's own collision entry above." + }, + { + "id": "REQ-MOCK-018", + "title": "rcp_mock_server_drain_endpoint() reports an unregistered byte_bus_id", + "text": "rcp_mock_server_drain_endpoint() shall return false, with *out_response left zeroed, when byte_bus_id names no registered endpoint.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-018 there is 'Operational LIN requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI/I2C/ADC/PWM_IN' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-018 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented, untested: the `ep != nullptr` guard in mock.hpp:779-782 -- for any byte_bus_id outside the 10 fixed constants, admission() returns nullptr and drain_one() short-circuits to false. Logic present and trivially correct by construction, but grep across tests/test_mock.cpp finds no TEST_CASE calling drain_one() (or pending_count()/watchdog_purge()) with an out-of-range byte_bus_id." + }, + { + "id": "REQ-MOCK-019", + "title": "rcp_mock_server_dispatch_frame() splits and dispatches every member of a multi-request frame", + "text": "rcp_mock_server_dispatch_frame() shall split frame into its constituent ACF messages (scheduler.h rcp_sched_split_frame_members()) and dispatch each one individually via rcp_mock_server_dispatch(), addressed to its own decoded byte_bus_id, writing each member's outcome into out_results in frame order and returning the count dispatched.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-019 there is 'CAN dispatch applies Table 33 Row 2 evt[2:0] classification before touching endpoint state' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-019 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested: dispatch_frame() (mock.hpp:1541-1549) delegates to dispatch_frame_impl() (mock.hpp:2400 onward), which splits via split_frame_members(), peek-decodes each member's byte_bus_id, dispatches each through decode_and_dispatch(), and appends one FrameMemberResult per member in frame order. Tested: tests/test_mock.cpp:2383-2449 (two TEST_CASEs). NOTE: cpp's own colliding REQ-MOCK-019 catalog entry ('CAN dispatch applies Table 33 Row 2 evt[2:0] classification...') is ALSO the bracket-tag these dispatch_frame tests carry ([REQ-MOCK-019]) -- a pre-existing intra-file Catch2 bracket-tag collision between two unrelated behaviors sharing one tag string, independent of this c-RCP comparison and harmless for cpfusa's own manifest-comment-based gate (bracket tags aren't traced), but worth a future cleanup pass." + }, + { + "id": "REQ-MOCK-020", + "title": "rcp_mock_server_dispatch_frame() fails safe on an unparseable frame or an undecodable member", + "text": "rcp_mock_server_dispatch_frame() shall return 0 without dispatching anything when frame does not parse as a well-formed sequence of ACF messages, and shall report RCP_MOCK_DISPATCH_ERR_UNKNOWN_BUS for any individual member whose byte_message_info header fails to decode, without invoking any handler for that member.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-020 there is 'Operational CAN requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI/I2C/ADC/PWM_IN/LIN' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-020 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested: mock.hpp:2405-2407 (`if (real_count == 0) return 0;`) and mock.hpp:2444-2456 (sets `DispatchErrc::unknown_bus` and `continue`s, skipping dispatch_member(), on a per-member decode failure). Tested: tests/test_mock.cpp:2450-2500 (two TEST_CASEs). Same intra-file bracket-tag-collision caveat as REQ-MOCK-019's own collision entry above (id 020 is also cpp's own 'CAN gating before RCP_CONFIGURED' title's bracket tag)." + }, + { + "id": "REQ-MOCK-021", + "title": "rcp_mock_server_dispatch() routes a request through the request_type-aware admission path", + "text": "rcp_mock_server_dispatch() shall, after rcp_lifecycle_should_accept() admission and endpoint lookup, route the request through rcp_server_endpoint_admit() and map its outcome onto RCP_MOCK_DISPATCH_OK (running the endpoint's handler synchronously), _QUEUED, _PENDING, _CANCELLED (having applied the named cancellation against that endpoint's request store), or _REJECTED. A standard request shall behave exactly as before: executed immediately when the endpoint is enabled, queued when it is not.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-021 there is 'UART dispatch applies Table 33 Row 2 evt[2:0] classification before touching either queue, routing Plain requests on req.op' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-021 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested: admit_and_classify() (mock.hpp:2272-2340+) maps server::Endpoint::admit_with_ack()'s AdmitOutcome (ExecuteNow/Queued/Pending/Cancellation/Suspended/Rejected) onto out_resp/out_ec exactly as claimed, calling apply_cancellation() for the Cancellation case. Exercised across many tests: tests/test_mock.cpp:1034-1148, 1197-1224, 2555-2649." + }, + { + "id": "REQ-MOCK-022", + "title": "rcp_mock_server_t owns the sequencer-state table compound requests read", + "text": "rcp_mock_server_set_sequencer_count() shall replace and free any sequencer table the server already held with a freshly allocated rcp_sequencer_table_new(count), and rcp_mock_server_sequencers() shall return that table. A server whose table is the unsupported ({NULL,0}) one shall never report a compound or compound-wait request as due.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-022 there is 'Operational UART requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI/I2C/ADC/PWM_IN/LIN/CAN' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-022 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: GENUINE GAP: grep for 'sequencer'/'Sequencer' in include/rcp/mock.hpp finds only one incidental comment (mock.hpp:~1234) -- no set_sequencer_count()/sequencers() method or owned SequencerTable member exists on mock::Server. server::TickContext::sequencers (server.hpp:303-305) is a caller-supplied `request::SequencerTable*`, defaulting to nullptr, never owned or substituted by mock::Server. Directly related to REQ-MOCK-024's own gap below -- the whole 'server owns the table' concept is absent." + }, + { + "id": "REQ-MOCK-023", + "title": "rcp_mock_server_tick() executes at most one due conditional request", + "text": "rcp_mock_server_tick() shall select the highest-priority due stored request on the addressed endpoint via rcp_server_endpoint_select_due(), run that request's own stored frame through that endpoint's registered handler, finalize it via rcp_server_endpoint_complete(), and return true. It shall return false, leaving *out_response zeroed and executing nothing, when the byte_bus_id names no endpoint or no stored request is due.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-023 there is 'ISELED dispatch applies Table 33 Row 2 evt[2:0] classification before touching endpoint state, decoding/encoding through ISELED's own Figure 40/41 codec' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-023 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: GENUINE GAP (partial -- bookkeeping present, handler execution absent, and unlike REQ-MOCK-016's own drain_one()+dispatch() composite, no test demonstrates the full two-step flow end-to-end for tick() specifically): tick() (mock.hpp:828-836) calls server::Endpoint::select_due() and complete() correctly (the 'select highest-priority due request + finalize' half), but per its own doc comment deliberately does NOT itself decode or execute the due frame -- 'a caller decodes out_frame... and dispatches it itself,' the same split pattern as drain_one(). Unlike REQ-MOCK-016, whose composite IS demonstrated end-to-end with a real response/state-change assertion, tests/test_mock.cpp:1256-1281 (tagged [REQ-TIMED-012], not a mock-admission id) only asserts frame equality and pending_count() dropping to 0 -- no test anywhere proves a caller can actually decode+redispatch a tick()-selected frame to completion the way REQ-MOCK-016's own test proves for drain_one()." + }, + { + "id": "REQ-MOCK-024", + "title": "rcp_mock_server_tick() evaluates conditions against the server's own sequencer table", + "text": "rcp_mock_server_tick() shall evaluate execution conditions against the server's own sequencer table, ignoring and overwriting any sequencers pointer the caller supplied in ctx, so a caller cannot have a request evaluated against a table the server does not own.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-024 there is 'Operational ISELED requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI/I2C/ADC/PWM_IN/LIN/CAN/UART' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-024 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: GENUINE GAP (opposite behavior): direct consequence of REQ-MOCK-022's own gap -- tick() (mock.hpp:828-836) passes the caller-supplied `ctx` (server::TickContext, including its `sequencers` field) straight into `ep->select_due(ctx, &index)` unmodified; there is no server-owned table to substitute, so the caller has full, unguarded control over which SequencerTable* is evaluated -- the OPPOSITE of this requirement's 'ignoring and overwriting any sequencers pointer the caller supplied' guarantee." + }, + { + "id": "REQ-MOCK-025", + "title": "rcp_mock_server_notify_trigger() reaches every endpoint", + "text": "rcp_mock_server_notify_trigger() shall offer the observed (source_ep, signal_nr) occurrence to every registered endpoint's request store, since a triggered request stored on one endpoint routinely waits on a different endpoint's trigger signal, and shall return the total number of stored requests that counted it.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-025 there is 'MDIO dispatch applies Table 33 Row 2 evt[2:0] classification before touching endpoint state, packing/unpacking mdio_payload through this mock's own u16 wire shape' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-025 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested, under a different id: notify_trigger() (mock.hpp:790-796) loops over all 10 admission endpoints (`server::Endpoint* eps[] = {&gpio_admission_, ..., &mdio_admission_}`), calling notify_trigger() on each and summing matches. Tested: tests/test_mock.cpp:1226-1254 (tagged [REQ-SRV-018]), asserting cross-endpoint fan-out (a Triggered request stored on SPI fires on a source_ep=99 notification observed via GPIO). Filed under REQ-SRV-018 in cpp's catalog, matching this requirement's claim exactly." + }, + { + "id": "REQ-MOCK-026", + "title": "rcp_mock_server_watchdog_purge() purges pending requests on the addressed endpoint", + "text": "rcp_mock_server_watchdog_purge() shall apply rcp_server_endpoint_watchdog_purge() to the addressed endpoint and return how many requests it purged. (This entry's own text previously also described rcp_mock_server_dispatch_frame()'s chain-sequencing CHAIN_ERROR/CHAIN_ABORTED behavior -- already REQ-MOCK-029's own separate, correctly-tagged requirement, and dispatch_frame() itself is tagged REQ-MOCK-019/REQ-MOCK-020/REQ-MOCK-029 at the source, never REQ-MOCK-026. Trimmed to this id's own actual, sole tagged implementation.)", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "tc18_master_id": "", + "gap": "NOT a behavioral gap by itself -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 13 (MOCK/PWRMODE), the final batch). This id is already taken in .fusa-reqs.json by cpp-RCP's own pre-existing, unrelated content: REQ-MOCK-026 there is 'Operational MDIO requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI/I2C/ADC/PWM_IN/LIN/CAN/UART/ISELED' -- a completely different behavioral claim (Table 33 evt[2:0] classification per peripheral endpoint type) than c-RCP's real REQ-MOCK-026 above (the rcp_mock_server_* C API). Cross-checked against cpp-RCP's own actual implementation for THIS c-RCP claim specifically: Genuinely implemented+tested: watchdog_purge(byte_bus_id) (mock.hpp:762-765) forwards to `ep->watchdog_purge()`; server::Endpoint::watchdog_purge() (server.hpp:915) calls purge_non_safety(). Tested: tests/test_mock.cpp:1197-1224 (`REQUIRE(server.watchdog_purge(mock::kGpioByteBusId) == 1);`), the same TEST_CASE as cpp's own free-id REQ-MOCK-027 entry above (bracket-tagged [REQ-MOCK-027] there, since that id was free while this one collides)." + }, + { + "id": "REQ-MOCK-030", + "title": "rcp_mock_server_dispatch() drops a request silently when byte_bus_id names no registered endpoint", + "text": "rcp_mock_server_dispatch(), when the (stream_id, byte_bus_id) pair names no registered endpoint (find_slot_on_stream() returns NULL), shall leave out_response zeroed (no response sent) and return RCP_MOCK_DISPATCH_ERR_UNKNOWN_BUS. Corrected 2026-08-10 (c-RCP-AUDIT-06, issue #256 Group H): this entry previously required a wire EP_NOT_FOUND error response for this case, a real conformance bug introduced by an earlier milestone (issue #163 Phase 1) that misapplied TC18 Table 30's EP_NOT_FOUND (8) code -- that code's own Table 30 description is scoped to \"if a Trigger request refers to a nonexisting EP\" (a Trigger request's own trigger_source_ep sub-field naming a nonexistent EP, a case this codebase does not implement at all, per ROADMAP.md milestone 120's own note), not to the addressed endpoint of the request itself. TC18 §12.9.1 explicitly and unambiguously requires the opposite of what this entry previously claimed: an unregistered byte_bus_id is dropped with no response. RCP_MOCK_DISPATCH_ERR_UNKNOWN_BUS's own internal dispatch-result semantics (distinguishing this case for chained-request cascade purposes in rcp_mock_server_dispatch_frame()) are unaffected -- only the wire response this function used to send has been removed. Corrected further 2026-08-14 (c-RCP-AUDIT-21, issue #432): the lookup itself is now genuinely scoped by stream_id, not byte_bus_id alone -- see REQ-MOCK-031, which this entry's own citation of §12.9.1's stream_id clause had already anticipated but the code had not yet implemented.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "", + "gap": "Confirmed absent/opposite (2026-08-22): mock.hpp's dispatch() fallback branch for an unmapped byte_bus_id (mock.hpp:1039-1040) sends a genuine wire ErrorResponse (WireErrorCode::InvalidParameter) via set_error_response(), not a zeroed/silent-drop response -- test-confirmed at tests/test_mock.cpp:993-1005 (TEST_CASE 'dispatch to an unmapped byte_bus_id reports invalid_parameter', already catalogued as cpp's own REQ-MOCK-010). Same root-cause divergence as REQ-MOCK-013's own pending (collision) entry -- that id's c-RCP text is dispatch()'s pre-audit-fix version of this exact claim, corrected by c-RCP's own 2026-08-10 audit (c-RCP-AUDIT-06, issue #256 Group H) to require silent drop per TC18 §12.9.1. This is a real, test-verified TC18 conformance concern in cpp-RCP, flagged here but NOT fixed in this batch, since a fix would change an already-catalogued, already-tested cpp behavioral claim (REQ-MOCK-010) -- left for a dedicated conformance-fix batch outside Phase 6's catalog-derivation scope." + }, + { + "id": "REQ-MOCK-031", + "title": "Endpoint lookup at every real dispatch entry point is scoped by (stream_id, byte_bus_id), not byte_bus_id alone", + "text": "rcp_mock_server_dispatch()/_dispatch_tscf()/_dispatch_multi_response()/_dispatch_e2e()/_dispatch_e2e_fragment()/_dispatch_frame()/_dispatch_frame_e2e() shall resolve the addressed endpoint via find_slot_on_stream(), which matches a slot registered by rcp_mock_server_add_endpoint_on_stream() only when the request's own stream_id equals that slot's recorded stream_id (a slot registered by the plain, unscoped rcp_mock_server_add_endpoint() continues to match any stream_id, preserving that call's pre-existing global-uniqueness behavior). This lets the same byte_bus_id validly address two different endpoints registered on two different stream_ids. Corrected 2026-08-14 (c-RCP-AUDIT-21, issue #432): find_slot()/find_slot_const() and rcp_mock_server_add_endpoint() previously treated byte_bus_id as unique across the whole server regardless of stream_id, honoring only the response-shape half of §12.9.1's own quoted sentence (REQ-MOCK-030's own history) while leaving the stream-scoping half unimplemented despite already being cited here.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "", + "gap": "Confirmed absent (2026-08-22): no find_slot_on_stream()/per-stream endpoint duplication concept exists anywhere in include/rcp/mock.hpp (grep confirms zero matches). mock.hpp's own class-level comment (~mock.hpp:2547-2552) explicitly documents this as a deliberate architectural decision: \"this rewrite's own Server hosts exactly one registered endpoint per byte_bus_id (no per-stream endpoint duplication concept exists here)... admission(byte_bus_id) alone is already unambiguous.\" Closing this gap would require redesigning Server's fixed 10-endpoint-slot model to support per-stream endpoint duplication -- an architecturally substantial change, not attempted in this batch." + }, + { + "id": "REQ-MOCK-032", + "title": "Byte-bus-id-only endpoint accessors gain stream-scoped _on_stream() counterparts", + "text": "rcp_mock_server_remove_endpoint_on_stream()/_set_endpoint_enable_on_stream()/_set_endpoint_req_crc_enable_on_stream()/_set_endpoint_rx_enforce_e2e_on_stream()/_drain_endpoint_on_stream()/_stash_deferred_response_on_stream()/_take_deferred_response_on_stream()/_tick_on_stream()/_pending_count_on_stream()/_watchdog_purge_on_stream() shall resolve the addressed slot via find_slot_on_stream()/find_slot_on_stream_const() (stream_id, byte_bus_id), leaving each of their own plain, unscoped, byte_bus_id-only counterparts (rcp_mock_server_remove_endpoint() etc., already REQ-MOCK-009/010/016-018/023-024/026-027, REQ-E2E-021/031, REQ-GPIO-036) completely unchanged in signature and behavior for their own ~100+ existing call sites -- the same \"new function, not a breaking change\" pattern REQ-MOCK-031/rcp_mock_server_add_endpoint_on_stream() already established. Issue #447 (c-RCP-AUDIT-26): the #432 fix correctly stream-scoped every real dispatch entry point via find_slot_on_stream(), but left this specific list of accessors -- which operate on already-admitted/queued per-slot state, not on an incoming request's own carried stream_id -- still resolving by the unscoped find_slot(), silently picking whichever matching-byte_bus_id slot came first by array index once two slots legitimately share one byte_bus_id on different stream_ids (a capability the #432 fix itself introduced). rcp_mock_server_broadcast_safe_state() is deliberately NOT in this list -- see REQ-MOCK-033.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "", + "gap": "Confirmed absent (2026-08-22): grepped include/rcp/mock.hpp for the whole '_on_stream' accessor family this requirement names (remove_endpoint/set_endpoint_enable/set_endpoint_req_crc_enable/set_endpoint_rx_enforce_e2e/drain_endpoint/stash_deferred_response/take_deferred_response/tick/pending_count/watchdog_purge, each _on_stream()) -- zero matches. Direct consequence of REQ-MOCK-031's own gap: there is no stream-scoped slot duplication to build these accessors against." + }, + { + "id": "REQ-MOCK-033", + "title": "rcp_mock_server_broadcast_safe_state() resolves each bound byte_bus_id via find_slot_on_stream(), not byte_bus_id alone", + "text": "rcp_mock_server_broadcast_safe_state() shall resolve each byte_bus_id EP_ID_config reports bound to request_stream_index via find_slot_on_stream(), keyed by the real wire stream_id request_stream_index itself resolves to (srv->request_stream_cfg[request_stream_index-1].rx_stream_id), not the plain, unscoped find_slot() it used before this fix. Unlike REQ-MOCK-032's own accessor list, this function's own signature is unchanged and it gains no new _on_stream() variant: request_stream_index already disambiguates which request stream is escalating, so the fix is internal to this function's own existing lookup, correcting a genuine defect (silently purging whichever of two byte_bus_id-sharing slots came first by array index, once two slots legitimately share one byte_bus_id on different stream_ids per REQ-MOCK-031) rather than adding new API surface. Issue #447 (c-RCP-AUDIT-26).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "", + "gap": "Confirmed absent (2026-08-22): no function named broadcast_safe_state (or equivalent) exists anywhere in include/rcp/mock.hpp -- grep finds only one forward-looking comment (mock.hpp:~2658) acknowledging it as future/unported scope (\"REQ-E2E-029/030/045 logic (broadcast_safe_state and friends, c-RCP's own mock.c)\"). Not yet ported at all, independent of the stream-scoping question REQ-MOCK-031/032 raise." + }, + { + "id": "REQ-PWRMODE-004", + "title": "rcp_pwrmode_transition() implements the general power-mode transition table", + "text": "rcp_pwrmode_transition() shall succeed with RCP_PWRMODE_START_HOT for Normal<->StandBy and for a mode transitioning to itself; shall succeed with RCP_PWRMODE_START_COLD for Normal/StandBy->Sleep, any mode->Unpowered, and Unpowered->Normal; and shall fail with RCP_PWRMODE_ERR_INVALID_TRANSITION (leaving *mode unchanged) for Sleep->Normal and for any other transition not named above.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "", + "gap": "GENUINE ARCHITECTURAL/CONFLICTING GAP (reclassified from an initial 'small fix' assessment upon closer inspection -- see verification notes): PowerManager::enter_low_power() (the shared body of enter_standby()/enter_sleep()) currently accepts a transition ONLY from PowerMode::Normal, refusing Sleep entry from StandBy with PowerErrc::invalid_transition -- diverging from this requirement's Normal/StandBy->Sleep COLD-start rule. However, that exact StandBy->Sleep refusal is ALREADY an established, catalogued, tested cpp-RCP behavior: REQ-PWR-002's own test ('enter_standby/enter_sleep are refused outside of Normal', tests/test_powerstate.cpp) explicitly asserts calling enter_sleep() while in StandBy returns invalid_transition and leaves mode() unchanged. A literal fix for this requirement would directly contradict that pre-existing, protected REQ-PWR-002 claim -- out of this batch's explicit instruction not to touch REQ-PWR-*. Resolving this requires a real design decision (does StandBy->Sleep become newly reachable, changing REQ-PWR-002's own tested contract, or does cpp-RCP deliberately not support that leg of c-RCP's general transition table) this batch does not have standing to make unilaterally -- filed as a genuine gap rather than guessed at. The self-transition leg (e.g. entering StandBy while already in StandBy succeeds HOT) is separately unrepresentable in cpp-RCP's current API shape (no natural caller would invoke it) and is not fixed here either." + }, + { + "id": "REQ-PWRMODE-012", + "title": "rcp_pwrmode_wake_from_sleep() classifies hot vs. cold wake starts", + "text": "rcp_pwrmode_wake_from_sleep() shall fail with RCP_PWRMODE_ERR_INVALID_TRANSITION when *mode != RCP_PWRMODE_SLEEP; otherwise it shall set *mode to RCP_PWRMODE_NORMAL and report *out_start_kind as RCP_PWRMODE_START_HOT when !rcp_pwrmode_hotstart_required(path) or when handshake is non-NULL and rcp_pwrmode_handshake_is_complete(handshake), and as RCP_PWRMODE_START_COLD otherwise.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "", + "gap": "PowerManager::start_kind_on_exit()/pending_start_kind() report a FIXED Hot/Cold classification based solely on which low-power mode is being left (StandBy=Hot, Sleep=Cold, constexpr, no runtime state consulted) -- never on whether the wake handshake (begin_wake_from_sleep()/note_wakeup_attempt_sent()/acknowledge_wakeup()) actually completed. A genuine, confirmed divergence from this requirement's central claim (classification depends on handshake OUTCOME, not a fixed property of the mode being left) -- pinned by an existing REQ-PWR-013 test asserting exactly the fixed classification. Fixing it requires deciding whether StartKind's meaning changes from 'fixed per low-power mode' to 'handshake-outcome-dependent', whether a new post-acknowledge_wakeup() accessor is needed, and how that interacts with the existing REQ-PWRMODE-015 retention guarantee -- nontrivial design decisions touching an already-tested REQ-PWR-013 claim, not attempted in this batch." + }, + { + "id": "REQ-PWRMODE-017", + "title": "The wake response is sent on the responder stream configured for the original sleep/standby request", + "text": "IMPLEMENTED: rcp_powerstate_manager_handshake_begin() (powerstate.c) now takes a required resp_stream_id parameter (this codebase's established \"caller supplies already-classified inputs\" convention -- this module owns no register-map instance and no transport of its own, so it cannot itself resolve which response stream regmap.h's request_stream_cfg/response_queue_cfg pairing designates; the caller, which does have that access, resolves and supplies it). Recorded per-addr alongside the handshake itself; a new rcp_powerstate_manager_wake_response_stream_id() getter returns it so a caller transmits rcp_powerstate_manager_encode_wakeup_probe()'s own output (and the eventual wake notification) on the correct stream rather than assuming addr's own request stream, which TC18 does not guarantee are the same stream.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "tc18": "", + "gap": "No stream-id concept of any kind exists in PowerManager (grepped the whole tree -- zero hits for a stream-id parameter anywhere in powerstate.hpp). PowerManager is a single-instance state machine with no per-request/per-addr bookkeeping at all. Closing this requires deciding how (or whether) PowerManager should be associated with a specific request/response stream pairing -- a concept that lives in regmap.hpp's request_stream_cfg/response_queue_cfg model, which powerstate.hpp deliberately does not depend on (per its own file-header rationale for keeping register-map coupling out of this header). A genuine multi-component design decision, not a one-line addition." + }, + { + "id": "REQ-PWRMODE-019", + "title": "Completing the wake handshake re-enables every used endpoint and response queue", + "text": "PARTIAL: rcp_pwrmode_handshake_resume_queues() (power.h) deliberately advances only a state enum -- power.h's own file header states it never touches server.h at all, matching this codebase's established \"pure primitive, caller composes\" layering (lifecycle.h/discovery.h use the identical pattern). The composition this requirement actually needs now lives at mock.h's new rcp_mock_server_pwrmode_resume(srv, hs): calls resume_queues(hs) first, then (iff it succeeds) calls rcp_server_endpoint_set_enable() on every registered endpoint, closing TC18 §12.4.1's \"all used endpoints... will be enabled\" for this test double. Still not modeled anywhere in this codebase: response-queue objects (rcp_regmap_response_queue_cfg_t is inert, no STREAM_UID/queue_size/storage -- see test_response_queue_has_no_identity_size_or_storage()) and heartbeat-stream re-emission (see test_flush_triggers_and_heartbeat_are_absent()) -- both separate, already-tracked architecture gaps this fix cannot close, so \"response queue\"/heartbeat resumption remains genuinely unimplemented. STALE-TEXT CORRECTED, CLOSED 2026-08-14 (tc18-gap post-backlog audit): this entry's own text above referenced test_response_queue_has_no_identity_size_or_storage() and test_flush_triggers_and_heartbeat_are_absent() as its evidence for 'response queue... not modeled' -- neither test exists in this codebase anymore (grep-confirmed), both superseded by real work: response_queue_cfg[] storage (REQ-RMAP-034/059-061, mock.c) and heartbeat-stream re-emission (REQ-RMAP-065/SRV-017, rcp_mock_server_check_response_queue_heartbeat()) both now exist. rcp_mock_server_pwrmode_resume()'s own doc comment (mock.h) is corrected to match. The remaining question -- does TC18 §12.4.1's 'response queues will be [re-]enabled' clause need its own wiring beyond the endpoint-enable half rcp_mock_server_pwrmode_resume() already performs -- resolves to no: neither rcp_regmap_response_queue_cfg_t nor rcp_mock_server_check_response_queue_heartbeat() carries any independent 'enabled/disabled' state of its own (unlike an endpoint's own ep_enable) for sleep to have disabled in the first place, so there is nothing left for wake to reverse -- the clause is vacuously satisfied by this codebase's own stateless response-queue design. (Whether heartbeat emission itself should be SUPPRESSED while asleep is a distinct question this entry's own §12.4.1 citation does not ask -- that citation is specifically about the wake handshake's own re-enable obligation, not about sleep-side gating -- and is left open as its own, separately-scoped concern if TC18 elsewhere requires it.)", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "tc18": "", + "gap": "PowerManager::acknowledge_wakeup() fires Hooks::reenable_response_ack_queues() -- matching c-RCP's own power.h-side 'pure primitive, caller composes' half -- but no equivalent to c-RCP's mock.h-side rcp_mock_server_pwrmode_resume() (which actually iterates every registered endpoint and calls set_enable(true)) exists anywhere in mock.hpp. PowerManager is not currently wired into mock.hpp's Server at all (confirmed by grep -- no cross-reference either direction), so there is no existing integration point to attach a 're-enable every registered endpoint' composition to; doing so requires deciding whether/how PowerManager and Server should be connected, an architectural decision out of this batch's scope." + }, + { + "id": "REQ-PWRMODE-021", + "title": "StandBy is entered only in response to an RCP request, never from a network signal", + "text": "power.h's rcp_pwrmode_commit_network_sleep() is the sole entry point a caller integrating a real TC14/TC10 network signal uses, and it has no target-mode parameter at all -- unlike rcp_pwrmode_commit_entry(), which the SleepCMD (RCP-request) path uses. The only power mode a network trigger can ever produce is RCP_PWRMODE_SLEEP, making the StandBy exclusivity impossible to violate by construction rather than merely true by omission (there being no network path modelled at all, the prior state).", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "tc18": "", + "gap": "No network-triggered sleep entry point exists anywhere in cpp-RCP (grepped 'TC14'/'TC10'/'network_sleep'/'commit_network' -- only 2 hits, both unrelated WakeupSource::Network enum comments in wakeup.hpp). enter_standby()/enter_sleep() are the only entry points, both driven identically regardless of trigger source -- so StandBy-exclusivity cannot currently be violated, but only because the mechanism it is meant to guard against (a real network-sleep path) does not exist yet. Adding one requires modeling out-of-band PHY-level signal integration and deciding how it interacts with the existing atomic enter_low_power() design -- a genuine architectural addition, not attempted in this batch." + }, + { + "id": "REQ-PWRMODE-022", + "title": "Sleep can also be initiated by a valid network TC14/TC10 sleep request", + "text": "power.h's rcp_pwrmode_commit_network_sleep(mode, gate, response_sent, out_start_kind) delegates to the same rcp_pwrmode_commit_entry() a normal (RCP-request) sleep entry uses, target fixed to RCP_PWRMODE_SLEEP -- 'the same conditions apply as for a normal sleep request' (TC18's own text). TC14/TC10 sleep signaling is PHY/MAC-level out-of-band control, not an RCP/ACF wire message this library's decode layer could ever parse; matching rcp_pwrmode_handshake_iface_reenabled()'s own network_available convention, a caller supplies 'a valid TC14/TC10 sleep request was received' as an already-classified fact simply by calling this function, with no frame/byte-buffer parameter to give since none exists for this library to inspect.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "tc18": "", + "gap": "Same root cause as REQ-PWRMODE-021's own gap: no network-sleep entry point exists in cpp-RCP at all. A real implementation would need to be designed together with REQ-PWRMODE-026's own response_sent-gating fix (see that entry), since a correct network-sleep entry point needs the same transmission-ordering guarantee a fixed enter_sleep()/enter_standby() would need -- not attempted in this batch." + }, + { + "id": "REQ-PWRMODE-026", + "title": "The mode change happens only after the sleep/standby response has been transmitted", + "text": "power.h's rcp_pwrmode_commit_entry() takes a required response_sent bool and refuses the entry (RCP_PWRMODE_ERR_ENTRY_REFUSED, mode left unchanged) unless it is true, mirroring rcp_pwrmode_handshake_iface_reenabled()'s own network_available convention -- this library does no I/O of its own and cannot observe transmission completion directly, so response_sent is the caller's own proof that the sleep/standby response has actually been transmitted before the mode transition it gates is permitted to run.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "tc18": "", + "gap": "PowerManager::enter_standby()/enter_sleep() (via enter_low_power()) commit mode_ = target synchronously inside the same call that decides admission, with NO response_sent parameter or any other mechanism to defer the commit until wire transmission of the sleep/standby response is confirmed. A caller following the obvious pattern (call enter_sleep(), then encode+send the response) would already have changed the server's mode BEFORE the response is transmitted -- the exact ordering violation this requirement exists to prevent. Fixing this requires splitting enter_standby()/enter_sleep() into a two-phase API (a separate admission-check call and a response_sent-gated commit call, mirroring c-RCP's own check_entry()/commit_entry() split) -- a breaking change to PowerManager's existing call contract and to its own REQ-PWR-002/003/004/005-tagged test assumptions, not a local addition. The most safety-relevant gap found in this module: as designed, a transmission failure or crash between 'decide to sleep' and 'actually send the confirmation' leaves the server silently in a low-power mode its peer was never told about." + }, + { + "id": "REQ-PWRMODE-027", + "title": "A refused network sleep request suppresses the TC14/TC10 LPS confirmation", + "text": "power.h's rcp_pwrmode_commit_network_sleep() has no PHY-signalling surface of its own (this library never touches hardware, the same scoping precedent as network_available), so its RCP_PWRMODE_OK vs. RCP_PWRMODE_ERR_ENTRY_REFUSED return value IS the confirmation signal: a caller integrating a real TC14/TC10 PHY asserts LPS only on RCP_PWRMODE_OK, never on RCP_PWRMODE_ERR_ENTRY_REFUSED, honouring 'the network PHY shall not signal a TC14/TC10 LPS as confirmation' when the go-to-sleep conditions are not fulfilled.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "tc18": "", + "gap": "Same root cause as REQ-PWRMODE-021/022's own gaps -- no network-sleep entry point of any kind exists in cpp-RCP, so there is no return-value/confirmation-signal mechanism to evaluate either. Would be closed automatically once REQ-PWRMODE-021/022's network-sleep entry point is designed and added, provided that new entry point's error_code doubles as the LPS-confirmation signal the way c-RCP's commit_network_sleep() does -- no separate work needed once that's done." } ] -} +} \ No newline at end of file diff --git a/.fusa-reqs.json b/.fusa-reqs.json index d8eaa41..02348fe 100644 --- a/.fusa-reqs.json +++ b/.fusa-reqs.json @@ -11831,6 +11831,295 @@ "scope": "tc18", "status": "implemented", "tc18_master_id": "TC18-RELAY-5.2-001" + }, + { + "id": "REQ-WDG-010", + "title": "The per-stream watchdog is kicked by every request received on that stream", + "text": "rcp::mock::Server's request-reception path shall call rx_watchdog_kick() (which forwards to rcp::watchdog::Manager::on_request_received()) for a request stream's stream_id on every request successfully received on that stream, so the per-stream watchdog measures the gap between consecutive requests from that RC Client (TC18 §12.7.7, \"the watchdog is reset with each request received from this RC Client\"). Server::dispatch_e2e_core() and Server::dispatch_frame_e2e()'s per-member loop both call rx_watchdog_kick(stream_id, ...) unconditionally, before any admission/validation check -- TC18's rule is about RECEIPT, not successful validation or execution, so a request this call goes on to reject still kicks. rcp::sim::Simulator independently wires the same rcp::watchdog::Manager hook for the simulation harness.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.7, TC18.txt L2936-L2938" + }, + { + "id": "REQ-E2E-033", + "title": "Each ACF message in a multi-ACF AVTPDU carries and is verified against its own CRC32", + "text": "When an AVTPDU carries more than one ACF message, a separate CRC32 shall be computed, appended to, and verified for each E2E-protected ACF message individually -- never one CRC across the whole AVTPDU payload (TC18 §13.6). rcp::e2e::wrap()/unwrap() already work at this per-message granularity; Server::dispatch_frame_e2e() composes that across a real multi-member frame -- each member, split from the frame, is routed through Server::dispatch_e2e() independently, so a corrupted trailer on one member neither blocks nor is masked by another member's valid one.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.6, TC18.txt L3789-3791", + "tc18_master_id": "TC18-13.6-002" + }, + { + "id": "REQ-E2E-039", + "title": "The ms bit decides whether a message carries a CRC32 trailer in its last quadlet", + "text": "A message whose ACF header ms bit is 0 shall carry the CRC32 in its last quadlet, and a message whose ms bit is 1 shall carry no CRC32 at all, the CRC appearing only on the final (ms=0) fragment of a multi-segment message (TC18 §13.6). Server::dispatch_e2e_fragment_core() reads the ms bit itself, via the decoded ACF header, to decide trailer handling: an ms=1 (intermediate) fragment is fed to the reassembler with no CRC trailer expected at all; an ms=0 (final) fragment is treated as carrying the CRC32 in its own trailing octets, verified via the same fragmented-CRC formula REQ-E2E-038 exercises.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.6, TC18.txt L3821-3823", + "tc18_master_id": "TC18-13.6-007" + }, + { + "id": "REQ-E2E-041", + "title": "A CRC mismatch on a protected stream generates an error response as well as skipping execution", + "text": "When a request arrives on an E2E-protected stream and its CRC32 does not match, the RC Server shall not execute the request AND shall generate an error response carrying the CRC-mismatch wire error code (acf::WireErrorCode::PociFailure -- TC18 §12.9.6 Table 30 / §13.6). Server::dispatch_e2e_core() and Server::dispatch_e2e_fragment_core() both build a genuine ErrorResponse carrying acf::WireErrorCode::PociFailure on an e2e::E2eErrc::crc_error verdict, addressed to the request's own byte_bus_id/transaction_num, without ever routing the request further for execution.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.6, TC18.txt L3827-3828", + "tc18_master_id": "TC18-13.6-001" + }, + { + "id": "REQ-E2E-047", + "title": "A reassembled fragmented request too large to re-encode as a single ACF frame is rejected with a genuine Table 27 error response, not silently dropped", + "text": "When a fragmented request's reassembled payload cannot be re-encoded as a single ACF_ABB/ACF_GBB frame because it exceeds that message type's own maximum payload (acf::kAcfAbbMaxPayload/kAcfGbbMaxPayload), the RC Server shall answer with a genuine regmap::RegMapErrc::request_rejected error response addressed to the request's own byte_bus_id/transaction_num, not silently drop the request (TC18 §13.7.11.3, \"...which requires segmentation, which is supported by the fields 'ms' and 'segment_num'.\"). Server::dispatch_e2e_fragment_core() checks the reassembled payload's size against these ceilings BEFORE attempting to re-encode it, and on overflow builds a real error response rather than returning silently, mirroring c-RCP issue #614/#616.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.7.11.3 (\"...which requires segmentation, which is supported by the fields 'ms' and 'segment_num'.\"), TC18.txt L5859-5860", + "tc18_master_id": "TC18-13.7.11.3-002" + }, + { + "id": "REQ-MOCK-027", + "title": "rcp_mock_server_pending_count() reports an endpoint's conditional-request store occupancy, 0 if unknown", + "text": "Server::pending_count(byte_bus_id) shall return the number of conditional requests currently pending in the addressed endpoint's request store (server::Endpoint::pending_count()), or 0 if byte_bus_id names no operational endpoint.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-MOCK-028", + "title": "rcp_mock_server_dispatch() sends REQUEST_NOT_FOUND when a clear-single target is not found", + "text": "Server::dispatch(), when a decoded ClearSingle cancellation's target clear_transaction_num is not found in the addressed endpoint's request store (server::Endpoint::cancel_single() reports server::CancelResult::NotFound), shall populate out_response with a real TC18 §11.2.3.3 error response carrying acf::WireErrorCode::RequestNotFound.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-MOCK-029", + "title": "rcp_mock_server_dispatch_frame() sends a per-member CHAIN_ERROR/CHAIN_ABORTED error response", + "text": "Server::dispatch_frame() shall, for each frame member found chain_error (no predecessor within the frame) or chain_aborted (a prior member in the same chain errored and this member's own cs selected abort-on-error, or the abort has already latched), populate that member's own FrameMemberResult::response with a real TC18 error response carrying acf::WireErrorCode::ChainError/ChainAborted -- one independent response per affected member, not a single shared response for the whole broken chain.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-001", + "title": "rcp_pwrmode_string() returns a unique human-readable name", + "text": "to_string(PowerMode) shall return a unique, non-empty name for each of Normal/StandBy/Sleep/Unpowered (\"normal\"/\"standby\"/\"sleep\"/\"unpowered\"), and \"unknown\" for any other value.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-002", + "title": "rcp_pwrmode_strerror() returns a non-NULL message for every error code", + "text": "power_category()'s own std::error_category::message() shall return a non-empty message for each PowerErrc value, and a non-empty default message for any unrecognized value -- std::error_category::message() can never return a null string by construction, satisfying the 'never NULL' half of this requirement automatically.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-005", + "title": "rcp_pwrmode_hotstart_required() is required for every wake path, network included", + "text": "PowerManager models exactly one hot-start-from-Sleep handshake (begin_wake_from_sleep()/note_wakeup_attempt_sent()/acknowledge_wakeup()), with no per-wake-path branch of any kind -- there is no code path by which a network-triggered wake (or any other future wake-source classification) could bypass the handshake, satisfying this requirement by construction rather than via an explicit wake-path parameter.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-006", + "title": "rcp_pwrmode_handshake_init() sets the initial handshake state", + "text": "PowerManager's constructor (given a Config{wakeup_repeat_limit}) zero-initializes wake_attempts_ and wake_stage_ (WakeStage::Idle) as member defaults; begin_wake_from_sleep() additionally resets wake_attempts_ to 0 on each (re-)begin -- together giving every fresh or restarted handshake the same not-started/zero-attempts/configured-limit starting state c-RCP's own explicit init function establishes.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-007", + "title": "rcp_pwrmode_handshake_iface_reenabled() implements handshake step (a)", + "text": "PowerManager::begin_wake_from_sleep() requires mode() == Sleep, runs Hooks::reenable_network_interface() (step (a)), resets wake_attempts_, and advances wake_stage() to HandshakeActive; otherwise (mode() != Sleep) it leaves state unchanged and returns PowerErrc::not_asleep.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-008", + "title": "rcp_pwrmode_handshake_wakeup_attempt() implements handshake step (b)", + "text": "PowerManager::note_wakeup_attempt_sent() requires wake_stage() == HandshakeActive, increments a per-handshake attempt counter each call, and advances wake_stage() to Failed once that counter exceeds Config::wakeup_repeat_limit (this implementation's own bound -- not spec-derived, matching c-RCP's own note that TC18 itself defines no such limit); otherwise the handshake stays HandshakeActive, awaiting acknowledge_wakeup().", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-009", + "title": "rcp_pwrmode_handshake_resume_queues() implements handshake step (c)", + "text": "PowerManager::acknowledge_wakeup() requires wake_stage() == HandshakeActive, runs Hooks::reenable_response_ack_queues() (step (c)), and advances the manager directly to mode() == Normal / wake_stage() == Complete -- folding echo-recognition, step (c), and the mode transition into one call rather than three separate ones.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-010", + "title": "rcp_pwrmode_handshake_is_complete() reports handshake completion", + "text": "A caller compares PowerManager::wake_stage() directly against WakeStage::Complete -- an exposed enum accessor rather than a dedicated boolean predicate, the same C++ idiom this class uses throughout.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-011", + "title": "rcp_pwrmode_handshake_has_failed() reports handshake failure", + "text": "A caller compares PowerManager::wake_stage() directly against WakeStage::Failed, the same accessor REQ-PWRMODE-010 uses.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-013", + "title": "rcp_pwrmode_check_entry() gates StandBy/Sleep entry requests", + "text": "PowerManager::enter_standby()/enter_sleep() (via the shared enter_low_power()) refuse entry into StandBy/Sleep whenever WakeupEndpoint::wakeup_message_pending() is true (the unacknowledged-wake-up-event condition), or either of Hooks::endpoints_idle/Hooks::response_ack_queues_empty is set and reports false. An unset hook is treated as 'check passes' (mirroring rcp::lifecycle::PlausibilityCheck's own convention) -- a deliberate divergence from c-RCP's 'NULL gate is refused' fail-safe default, a pre-established codebase convention rather than a defect specific to this requirement.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-015", + "title": "Configuration data and wake sources survive StandBy", + "text": "PowerManager::resume_from_standby() returns directly to Normal without invoking any reinit/reenable hook -- proven by a dedicated test asserting the network-reenable hook is never called on this path, unlike the hot-start-from-Sleep handshake which always does.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-016", + "title": "Hot start checks network availability before sending a WakeUp", + "text": "PowerManager::begin_wake_from_sleep(bool network_available = true) checks network_available before running Hooks::reenable_network_interface() or advancing wake_stage() -- a false value is a free, uncounted retry (wake_stage() stays Idle, no hook fires, wake_attempts() untouched), matching TC18 §12.4.1's network-availability-gates-step-(a) ordering. Defaults to true, so every pre-existing caller's behavior is unchanged.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-018", + "title": "WakeUp repetition stops on any valid AVTPDU from the sleep-request client", + "text": "PowerManager::acknowledge_wakeup() carries no built-in notion of 'echo' at all -- a caller recognizing ANY valid frame from the sleep-request client, not only a literal WakeUp-message echo, can call it directly to complete the handshake, independent of note_wakeup_attempt_sent()'s own per-attempt tracking.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-020", + "title": "A network wake runs the same hot-start handshake a pin wake does, not a bypass", + "text": "Same reasoning as REQ-PWRMODE-005: PowerManager models exactly one handshake path with no pin/network distinction, so no code path could bypass it for a network-triggered wake specifically.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-023", + "title": "A sleep/standby request from an authorized client applies to the entire RC Server", + "text": "rcp::wakeup::sleepcmd_writable(writer) returns writer.via_root_client_ep0, gating SleepCMD write access to the root client via EP0 before any pwrmode admission logic runs. PowerManager itself also operates on a single server-wide mode_ member, never per-endpoint, satisfying this requirement's second half (a sleep/standby request from an authorized client necessarily applies to the entire RC Server, by construction).", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-024", + "title": "A wake-source event during sleep-request processing aborts the sleep entry", + "text": "PowerManager::enter_low_power() performs its wakeup_message_pending()/endpoints_idle/response_ack_queues_empty check and the mode_ = target commit in the same synchronous call -- there is no separate, earlier 'admit' step, so no window exists in which a wake-source event could be lost between check and commit. The check IS the commit, closing this race by construction rather than via an explicit two-phase re-validation (see REQ-PWRMODE-026's own pending entry for the tradeoff this same atomicity causes).", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-025", + "title": "The sleep/standby refusal conditions are evaluated server-wide, across every EP and every responder queue", + "text": "PowerManager::Hooks::endpoints_idle/response_ack_queues_empty are single, global, parameterless std::function hooks with no per-endpoint scoping possible in the API shape at all, forcing any caller to aggregate every endpoint/queue's own state server-wide before calling enter_standby()/enter_sleep() -- the same 'caller ANDs everything' convention c-RCP's own gate fields document.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-003", + "title": "rcp_pwrmode_cold_start_lifecycle_target() names the cold-start re-init target", + "text": "cold_start_lifecycle_target() names the lifecycle::ServerState a cold start's own re-init sequence targets. Superseded in substance by REQ-PWRMODE-014's own recovered_state-aware text -- cpp-RCP's single implementation directly realizes the corrected (014) behavior described there rather than an unconditional always-HwUnconfigured return; see REQ-PWRMODE-014's own text for the full behavior.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-014", + "title": "A cold start restores the configured lifecycle state, not always HW_UNCONFIGURED", + "text": "cold_start_lifecycle_target(lifecycle::ServerState recovered_state) shall return recovered_state unchanged when it is one of lifecycle::ServerState's three valid values (HwUnconfigured/HwConfigured/RcpConfigured), so a cold start's own re-init sequence can restore the RC Server's configured lifecycle state rather than always resetting to HwUnconfigured; an unrecognized/corrupt recovered_state is treated as 'nothing recovered' and maps to HwUnconfigured, this function's own fail-safe default. This header owns no NVM access or default-configuration table of its own -- recovered_state is the caller's own already-recovered fact, the same 'caller supplies already-classified inputs' convention Hooks establishes.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-PWRMODE-028", + "title": "Sleep-request processing order: stop admitting requests, drain, then acknowledge", + "text": "server::Endpoint::set_admission_suspended(true)/admission_suspended() (TC18 §13.7.2.3 step 1: 'on receipt of a sleep request the server shall stop entering incoming requests into endpoint queues') stops admit()/admit_with_ack() from queuing or executing new requests once suspended, without touching anything already queued or executing -- a caller drains remaining requests, then transmits the sleep/standby acknowledge response, implementing the 'stop admitting, drain, then acknowledge' order this requirement names. submit() (the lower-level queue primitive) deliberately does not itself consult admission_suspended_ -- a caller wanting this suspension semantics for standard requests must route them through admit()/admit_with_ack().", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented" } ] -} +} \ No newline at end of file diff --git a/include/rcp/mock.hpp b/include/rcp/mock.hpp index 4372a50..99b0e5d 100644 --- a/include/rcp/mock.hpp +++ b/include/rcp/mock.hpp @@ -24,6 +24,14 @@ // fusa:req REQ-MOCK-024 // fusa:req REQ-MOCK-025 // fusa:req REQ-MOCK-026 +// fusa:req REQ-WDG-010 +// fusa:req REQ-E2E-033 +// fusa:req REQ-E2E-039 +// fusa:req REQ-E2E-041 +// fusa:req REQ-E2E-047 +// fusa:req REQ-MOCK-027 +// fusa:req REQ-MOCK-028 +// fusa:req REQ-MOCK-029 // In-process RC Server simulator — a small, representative OPEN Alliance // TC18 Remote Control Protocol Specification v0.5.1_RC server built diff --git a/include/rcp/powerstate.hpp b/include/rcp/powerstate.hpp index 498b38d..87a2d02 100644 --- a/include/rcp/powerstate.hpp +++ b/include/rcp/powerstate.hpp @@ -12,6 +12,24 @@ // fusa:req REQ-PWR-012 // fusa:req REQ-PWR-013 // fusa:req REQ-PWR-014 +// fusa:req REQ-PWRMODE-001 +// fusa:req REQ-PWRMODE-002 +// fusa:req REQ-PWRMODE-003 +// fusa:req REQ-PWRMODE-005 +// fusa:req REQ-PWRMODE-006 +// fusa:req REQ-PWRMODE-007 +// fusa:req REQ-PWRMODE-008 +// fusa:req REQ-PWRMODE-009 +// fusa:req REQ-PWRMODE-010 +// fusa:req REQ-PWRMODE-011 +// fusa:req REQ-PWRMODE-013 +// fusa:req REQ-PWRMODE-014 +// fusa:req REQ-PWRMODE-015 +// fusa:req REQ-PWRMODE-016 +// fusa:req REQ-PWRMODE-018 +// fusa:req REQ-PWRMODE-020 +// fusa:req REQ-PWRMODE-024 +// fusa:req REQ-PWRMODE-025 // Power management — the OPEN Alliance TC18 Remote Control Protocol // Specification v0.5.1_RC's actual power-mode model (`Normal`/`StandBy`/ @@ -58,6 +76,7 @@ // disclaimer as every other endpoint/lifecycle header in this codebase. #pragma once +#include #include #include @@ -119,6 +138,36 @@ constexpr StartKind start_kind_on_exit(PowerMode from) noexcept { return from == PowerMode::StandBy ? StartKind::Hot : StartKind::Cold; } +// cold_start_lifecycle_target reports the lifecycle::ServerState a cold +// start's own re-init sequence should target, given the caller's own +// already-recovered fact of what state was persisted (e.g. read back from +// NVM, or device defaults absent NVM) — REQ-PWRMODE-003/014 (TC18 §12.3, +// §12.4.1): "After a cold start the RC Server will be in its configured +// lifecycle state," recovered from NVM where present rather than always +// reset to HwUnconfigured. This header owns no NVM access and no +// default-configuration table of its own (the same "caller supplies +// already-classified inputs" convention PowerManager::Hooks establishes) +// -- recovered_state is the caller's own already-recovered fact, returned +// unchanged when it is one of lifecycle::ServerState's three valid values. +// Any other value (unrecognized or corrupt) is treated as "nothing +// recovered," never as an unvalidated advanced state, and maps to +// lifecycle::ServerState::HwUnconfigured, this function's own fail-safe +// default. This header never itself calls +// lifecycle::ServerLifecycle::advance() -- it only names the target state +// for a caller's own re-init sequence to drive toward, mirroring +// PowerManager's own "primitives, not a scheduler" scoping (see this +// file's header comment). +constexpr lifecycle::ServerState cold_start_lifecycle_target(lifecycle::ServerState recovered_state) noexcept { + switch (recovered_state) { + case lifecycle::ServerState::HwUnconfigured: + case lifecycle::ServerState::HwConfigured: + case lifecycle::ServerState::RcpConfigured: + return recovered_state; + default: + return lifecycle::ServerState::HwUnconfigured; + } +} + // ── Errors ──────────────────────────────────────────────────────────────────── enum class PowerErrc : int { @@ -128,6 +177,9 @@ enum class PowerErrc : int { response_ack_queue_not_empty = 4, // entry refusal: caller's response_ack_queues_empty hook reported false not_asleep = 5, // wake-from-sleep handshake step requested while mode() != Sleep handshake_repeat_limit_exceeded = 6, // WakeUp message repeated cfg.wakeup_repeat_limit times with no echo + network_not_available = 7, // REQ-PWRMODE-016: begin_wake_from_sleep() called with + // network_available=false -- a free, uncounted retry; + // wake_stage() is left at Idle, not HandshakeActive. }; inline const std::error_category& power_category() noexcept { @@ -147,6 +199,8 @@ inline const std::error_category& power_category() noexcept { return "rcp/powerstate: wake-from-sleep handshake requested while not in Sleep"; case PowerErrc::handshake_repeat_limit_exceeded: return "rcp/powerstate: WakeUp message repeat limit exceeded without an echo"; + case PowerErrc::network_not_available: + return "rcp/powerstate: network interface not yet available -- a free, uncounted retry"; default: return "rcp/powerstate: unknown error"; } @@ -258,9 +312,26 @@ class PowerManager { // (extraction §3.3): step 1, network-interface re-enablement, runs // synchronously here via Hooks::reenable_network_interface. Requires // mode() == Sleep. - std::error_code begin_wake_from_sleep() noexcept { + // + // REQ-PWRMODE-016 (TC18 §12.4.1): network availability is checked + // BEFORE any WakeUp message is sent — network_available is the + // caller's own already-classified fact (e.g. "BEACONs detected by the + // PHY"; this header reads no hardware itself, matching every other + // Hooks-driven check in this class). Defaults to true so every + // pre-existing caller (and every REQ-PWR-*-tagged test) that never + // passed this argument keeps its exact prior behavior. When false, + // this is a cheap, retriable "not yet": wake_stage() is left at Idle + // (not HandshakeActive), reenable_network_interface() is NOT called, + // and wake_attempts_ is untouched — these retries are NOT counted + // against cfg_.wakeup_repeat_limit, which governs only the + // WakeUp-message repetition inside note_wakeup_attempt_sent() once + // this step has actually advanced. A caller polls this again once the + // network comes up. + std::error_code begin_wake_from_sleep(bool network_available = true) noexcept { if (mode_ != PowerMode::Sleep) return make_error_code(PowerErrc::not_asleep); + if (!network_available) + return make_error_code(PowerErrc::network_not_available); if (hooks_.reenable_network_interface) hooks_.reenable_network_interface(); wake_attempts_ = 0; wake_stage_ = WakeStage::HandshakeActive; diff --git a/include/rcp/wakeup.hpp b/include/rcp/wakeup.hpp index dc93e5b..f56de7f 100644 --- a/include/rcp/wakeup.hpp +++ b/include/rcp/wakeup.hpp @@ -32,6 +32,7 @@ // fusa:req REQ-WAKEUP-034 // fusa:req REQ-WAKEUP-035 // fusa:req REQ-WAKEUP-036 +// fusa:req REQ-PWRMODE-023 // WakeUp endpoint (ep_type 0x01) — TC18's dedicated power-management // endpoint: the fixed-opcode SleepCMD request/response, the fixed-opcode diff --git a/tests/test_mock.cpp b/tests/test_mock.cpp index 98374f2..69091cf 100644 --- a/tests/test_mock.cpp +++ b/tests/test_mock.cpp @@ -24,6 +24,14 @@ // fusa:test REQ-MOCK-024 // fusa:test REQ-MOCK-025 // fusa:test REQ-MOCK-026 +// fusa:test REQ-WDG-010 +// fusa:test REQ-E2E-033 +// fusa:test REQ-E2E-039 +// fusa:test REQ-E2E-041 +// fusa:test REQ-E2E-047 +// fusa:test REQ-MOCK-027 +// fusa:test REQ-MOCK-028 +// fusa:test REQ-MOCK-029 // Tests for rcp/mock.hpp — the in-process RC Server simulator (ROADMAP.md // milestone 56, "Test & Simulation Harness Rebuild", v2.12.0). See diff --git a/tests/test_powerstate.cpp b/tests/test_powerstate.cpp index 23a0f2f..0039e30 100644 --- a/tests/test_powerstate.cpp +++ b/tests/test_powerstate.cpp @@ -12,6 +12,24 @@ // fusa:test REQ-PWR-012 // fusa:test REQ-PWR-013 // fusa:test REQ-PWR-014 +// fusa:test REQ-PWRMODE-001 +// fusa:test REQ-PWRMODE-002 +// fusa:test REQ-PWRMODE-003 +// fusa:test REQ-PWRMODE-005 +// fusa:test REQ-PWRMODE-006 +// fusa:test REQ-PWRMODE-007 +// fusa:test REQ-PWRMODE-008 +// fusa:test REQ-PWRMODE-009 +// fusa:test REQ-PWRMODE-010 +// fusa:test REQ-PWRMODE-011 +// fusa:test REQ-PWRMODE-013 +// fusa:test REQ-PWRMODE-014 +// fusa:test REQ-PWRMODE-015 +// fusa:test REQ-PWRMODE-016 +// fusa:test REQ-PWRMODE-018 +// fusa:test REQ-PWRMODE-020 +// fusa:test REQ-PWRMODE-024 +// fusa:test REQ-PWRMODE-025 // Tests for rcp/powerstate.hpp — the TC18 power-mode model, entry-refusal // conditions, and hot-start-from-Sleep handshake (ROADMAP.md milestone 53, @@ -280,3 +298,61 @@ TEST_CASE("PowerErrc reports a non-empty message in its own category", "[powerst REQUIRE(ec.category() == power_category()); REQUIRE_FALSE(ec.message().empty()); } + +// ── cold_start_lifecycle_target (REQ-PWRMODE-003/014) ─────────────────────── + +TEST_CASE("cold_start_lifecycle_target returns a valid recovered_state unchanged", + "[powerstate][REQ-PWRMODE-003][REQ-PWRMODE-014]") { + REQUIRE(cold_start_lifecycle_target(rcp::lifecycle::ServerState::HwUnconfigured) == + rcp::lifecycle::ServerState::HwUnconfigured); + REQUIRE(cold_start_lifecycle_target(rcp::lifecycle::ServerState::HwConfigured) == + rcp::lifecycle::ServerState::HwConfigured); + REQUIRE(cold_start_lifecycle_target(rcp::lifecycle::ServerState::RcpConfigured) == + rcp::lifecycle::ServerState::RcpConfigured); +} + +TEST_CASE("cold_start_lifecycle_target falls back to HwUnconfigured for an unrecognized value", + "[powerstate][REQ-PWRMODE-003][REQ-PWRMODE-014]") { + auto bogus = static_cast(0xFF); + REQUIRE(cold_start_lifecycle_target(bogus) == rcp::lifecycle::ServerState::HwUnconfigured); +} + +// ── begin_wake_from_sleep's network_available gate (REQ-PWRMODE-016) ──────── + +TEST_CASE("begin_wake_from_sleep defaults to network_available=true, preserving every " + "pre-existing caller's exact behavior", + "[powerstate][REQ-PWRMODE-016]") { + rcp::wakeup::WakeupEndpoint wep; + int reenable_calls = 0; + PowerManager::Hooks hooks; + hooks.reenable_network_interface = [&] { ++reenable_calls; }; + PowerManager mgr(wep, hooks); + + REQUIRE_FALSE(mgr.enter_sleep()); + REQUIRE_FALSE(mgr.begin_wake_from_sleep()); // no explicit argument — defaults to true + REQUIRE(reenable_calls == 1); + REQUIRE(mgr.wake_stage() == WakeStage::HandshakeActive); +} + +TEST_CASE("begin_wake_from_sleep(false) is a free, uncounted retry that leaves wake_stage() at " + "Idle and never touches the network-reenable hook", + "[powerstate][REQ-PWRMODE-016]") { + rcp::wakeup::WakeupEndpoint wep; + int reenable_calls = 0; + PowerManager::Hooks hooks; + hooks.reenable_network_interface = [&] { ++reenable_calls; }; + PowerManager mgr(wep, hooks); + + REQUIRE_FALSE(mgr.enter_sleep()); + + auto ec = mgr.begin_wake_from_sleep(/*network_available=*/false); + REQUIRE(ec == make_error_code(PowerErrc::network_not_available)); + REQUIRE(mgr.wake_stage() == WakeStage::Idle); // still Idle, not HandshakeActive + REQUIRE(reenable_calls == 0); // network hook never fired + REQUIRE(mgr.wake_attempts() == 0); // not counted against the repeat limit + + // A later retry once the network comes up succeeds normally. + REQUIRE_FALSE(mgr.begin_wake_from_sleep(/*network_available=*/true)); + REQUIRE(reenable_calls == 1); + REQUIRE(mgr.wake_stage() == WakeStage::HandshakeActive); +} diff --git a/tests/test_wakeup.cpp b/tests/test_wakeup.cpp index 58c063f..cd898c7 100644 --- a/tests/test_wakeup.cpp +++ b/tests/test_wakeup.cpp @@ -32,6 +32,7 @@ // fusa:test REQ-WAKEUP-034 // fusa:test REQ-WAKEUP-035 // fusa:test REQ-WAKEUP-036 +// fusa:test REQ-PWRMODE-023 // Tests for rcp/wakeup.hpp — the WakeUp endpoint type, re-derived from // c-RCP's tests/test_ep_wakeup.c (ROADMAP.md Phase 17, cpp-RCP issue #129,