Skip to content

feat: implement Table 30 Row-2 evt[2:0] validation — CAN (5th endpoint type) - #118

Merged
SoundMatt merged 1 commit into
mainfrom
feat/tc18-row2-can-evt-classification
Aug 20, 2026
Merged

SoundMatt merged 1 commit into
mainfrom
feat/tc18-row2-can-evt-classification

Conversation

@SoundMatt

Copy link
Copy Markdown
Owner

What

Implements TC18 §13.5 Table 33 "Row 2" evt[2:0] request classification for the CAN endpoint type (include/rcp/can.hpp, ep_type 0x0B, §13.7.11) — the 5th of the 8 endpoint types sharing this row ({ADC, PWM_IN, I²C, LIN, CAN, UART, ISELED, MDIO}), following the exact pattern already merged for I2C (#114), ADC (#115), PWM_IN (#116), and LIN (#117).

Row 2's rule (already implemented as the shared rcp::endpoint::evt_row2_kind_of predicate, not reinvented here):

  • evt[2:0] == 000b → Plain — ordinary request
  • evt[2:0] == 001b–110b → Reserved — reject with EndpointErrc::reserved_evt_row2 (wire error UNSUPPORTED_CMD)
  • evt[2:0] == 111b → ConfigWrite — §12.7.1 functional-config write, not yet implemented for any endpoint type in this codebase; reported via a new CanErrc::config_write_not_supported (wire error UNSUPPORTED_CMD)

Changes

  • include/rcp/can.hpp: new CanEndpoint::handle_request(uint8_t evt_op, CanDataFrame frame) entry point. Classifies via evt_row2_kind_of first: Plain delegates straight to the existing transmit(std::move(frame)) unchanged; Reserved returns EndpointErrc::reserved_evt_row2 without touching last_tx_; ConfigWrite returns the new CanErrc::config_write_not_supported = 4.
  • include/rcp/mock.hpp: CAN wired in as the mock server's 7th representative endpoint at byte_bus_id 7 (GPIO=1, SPI=2, I2C=3, ADC=4, PWM_IN=5, LIN=6, CAN=7) — can() accessor, dispatch_can(), wire_error_code_for mappings for every CanErrc value, endpoint_count/ep_id_mapping updated to seven. REQ-MOCK-001's text now correctly says "seven endpoints" and lists CAN at byte_bus_id 7 (PR feat: implement Table 30 Row-2 evt[2:0] validation — PWM_IN (3rd endpoint type) #116 left this stale for feat: implement Table 30 Row-2 evt[2:0] validation — LIN (4th endpoint type) #117 to fix — checked it here so this PR doesn't repeat that).
  • .fusa-reqs.json: REQ-CANEP-008/REQ-CANEP-009 (endpoint-level) and REQ-MOCK-019/REQ-MOCK-020 (mock-dispatch-level) added. Every tc18_master_id independently checked against RELAY's docs/tc18-master-catalog.json: TC18-13.5-001 (Table 33 — its own summary text names CAN explicitly) and TC18-12.7.1-001 (the evt[2:0]=111b config-write clause) — both re-verified against TC18.txt directly rather than trusted blindly. REQ-CANEP-008 tagged severity: "cybersecurity", matching the REQ-ADC-008/REQ-PWM-008/REQ-LINEP-005 precedent, so the cybersecurity-tagged ratio stays at 17% (69/398, integer division) — unchanged from before this PR.
  • Tests: tests/test_can.cpp (Plain-delegates, all-6-Reserved-values-rejected, ConfigWrite-rejected-without-touching-state, evt_op masking, and a FrameFormat/CAN-ID independence guard) and tests/test_mock.cpp (same three-way dispatch through mock::Server, plus the pre-RCP_CONFIGURED gating test), mirroring LIN's test shape.

CAN-specific judgment calls (verified directly against TC18.txt)

  1. No remote-frame concept, no TriggerRegistry — both were already deliberate omissions in this header (per its own pre-existing comments) and neither was added here. Confirmed directly: TC18.txt states outright "Sending remote frames is not supported" (§13.7.11.3).
  2. evt[2:0] is independent of CAN XL's own FrameFormat/frame-format sub-field. Figure 40 (§13.7.11.3) draws the Message Info octet's evt[2:0] bits and the Payload's FrameFormat/CAN ID/CAN data fields as distinct, non-overlapping regions of the request — verified directly in TC18.txt, not assumed. This is exactly the class of mistake this codebase has had to remove before (see rcp/iseled.hpp's and rcp/mdio.hpp's own header comments on previously-invented, non-spec field encodings, and the identical caution LIN's own handle_request comment gives about its compound-wait text). A new test case ("...independent of FrameFormat/CAN-ID...") asserts Reserved/ConfigWrite rejection is identical across Classical/FD/XL.
  3. No set_can_response()-shaped test-scripting hook. Unlike I2C/LIN/ADC/PWM_IN, whose Plain requests read back mock-scripted data, CAN's Plain request is a fire-a-frame TX whose outcome is fully and deterministically determined by the request's own frame contents (can::validate_frame) — there's no external "current bus state" for such a hook to script. Documented explicitly in mock.hpp rather than adding a hook that would have nothing to do. dispatch_can() treats the request payload as the frame's raw data bytes (fixed standard id, FrameFormat::Classical) since no CAN wire codec (Figure 40 decode) exists anywhere in this codebase yet — same "not real wire conformance" disclaimer already used throughout mock.hpp.

Mutation test

Temporarily changed the ConfigWrite branch in CanEndpoint::handle_request to call transmit() instead of returning config_write_not_supported, rebuilt, and confirmed exactly 2 test_can assertions + 1 test_mock assertion failed (proving they're load-bearing). Reverted cleanly — git diff showed no stray leftovers, full suite green again afterward.

Verification

  • cmake -S . -B build -G Ninja && cmake --build build: clean build, 0 warnings from this change.
  • ctest --test-dir build: 100% tests passed, 0 failed (53/53 suites).
  • cpfusa check --strict: 0 errors (2 warnings + 1 info, byte-identical output to main before this PR — pre-existing .fusa-evidence.json/HARA005 conditions, not introduced here).
  • cpfusa trace: 398/398 (100%) requirements annotated and tested.
  • Cybersecurity-tagged ratio: 69/398 = 17% (integer division), unchanged from before this PR.

Not merging myself — opening for review per usual process.

…t type)

TC18 section 13.5 Table 33 Row 2's 3-way evt[2:0] classification
(Plain/Reserved/ConfigWrite), applied to the CAN controller endpoint
(section 13.7.11), following the exact shape already merged for I2C (#114),
ADC (#115), PWM_IN (#116), and LIN (#117):

- rcp::can::CanEndpoint::handle_request(evt_op, frame) classifies via
  the shared rcp::endpoint::evt_row2_kind_of before touching any
  transfer state: Plain (000b) delegates straight to the existing
  transmit(); Reserved (001b-110b) returns
  endpoint::EndpointErrc::reserved_evt_row2 without recording anything
  as transmitted; ConfigWrite (111b) returns the new
  CanErrc::config_write_not_supported (section 12.7.1 functional-config
  write is out of scope for this milestone, same as the other four
  endpoint types).
- rcp::mock::Server wires CAN in as its 7th representative endpoint
  (byte_bus_id 7), with dispatch_can(), wire_error_code_for mappings
  for every CanErrc value, and REQ-MOCK-001 updated to describe seven
  endpoints including CAN at byte_bus_id 7 (avoiding the stale-text
  mistake #116 left for #117 to fix).
- .fusa-reqs.json: REQ-CANEP-008/009 and REQ-MOCK-019/020 added, each
  tc18_master_id independently checked against RELAY's
  docs/tc18-master-catalog.json (TC18-13.5-001 for Table 33,
  TC18-12.7.1-001 for the config-write clause — both already verified
  to describe CAN by name). REQ-CANEP-008 tagged
  severity=cybersecurity, matching the REQ-ADC-008/REQ-PWM-008/
  REQ-LINEP-005 precedent, keeping the cybersecurity-tagged ratio at
  17% (69/398, integer division) — unchanged from before this PR.

CAN-specific judgment calls, verified directly against TC18.txt:
- No remote-frame concept and no TriggerRegistry for CAN (both by
  design per this header's own pre-existing comments) — neither was
  added. TC18.txt confirms explicitly: "Sending remote frames is not
  supported" (section 13.7.11.3).
- evt[2:0] classification is verified independent of CAN XL's own
  FrameFormat sub-field: Figure 40 (section 13.7.11.3) draws the
  Message Info octet's evt[2:0] bits and the Payload's
  FrameFormat/CAN-ID/CAN-data fields as distinct, non-overlapping
  regions of the request — confirmed directly against TC18.txt rather
  than assumed, to avoid the invented-field-encoding mistake this
  codebase has previously had to remove for ISELED/MDIO.
- No set_can_response()-shaped test hook: CAN's Plain request is a
  fire-a-frame TX whose outcome is fully determined by the request's
  own frame contents (already validated deterministically by
  can::validate_frame), unlike I2C/LIN/ADC/PWM_IN's read-back-shaped
  requests — there is no external 'response data' for such a hook to
  script, so none was added (documented in mock.hpp).

Mutation-tested: temporarily made the ConfigWrite branch call
transmit() instead of returning config_write_not_supported; rebuilt;
confirmed 2 test_can + 1 test_mock assertions failed as expected;
reverted cleanly (git diff clean, no stray leftovers) and confirmed
all tests pass again.

cpfusa check --strict: 0 errors (2 pre-existing warnings + 1 info,
byte-identical to main before this PR). cpfusa trace: 398/398 (100%)
requirements annotated and tested.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
@SoundMatt
SoundMatt merged commit 514d663 into main Aug 20, 2026
22 checks passed
@SoundMatt
SoundMatt deleted the feat/tc18-row2-can-evt-classification branch August 20, 2026 14:52
SoundMatt added a commit that referenced this pull request Aug 20, 2026
…nt type) (#119)

Wires rcp::endpoint::evt_row2_kind_of (TC18 §13.5 Table 33's shared
Plain/Reserved/ConfigWrite classification for the {ADC, PWM_IN, I2C,
LIN, CAN, UART, ISELED, MDIO} row) into UartEndpoint::handle_request,
following the shape already merged for I2C (#114), ADC (#115), PWM_IN
(#116), LIN (#117), and CAN (#118).

Independently reverified against TC18.txt (v0.5.1_RC): Table 33's own
text (§13.5, TC18.txt L4085-4092) lists UART by name in its Row 2 —
the existing rcp/uart.hpp header comment claiming UART "has no
evt[2:0]/channel decode of its own" described only the absence of a
channel/value selector (unlike SPI's own dedicated row), not an
exemption from Table 33's shared classification. UART's own request
handling (§13.7.8.1/§13.7.8.3, TC18.txt L5289-5406) — independent
TX/RX queues, the payload-less-read-only rule, and the
read_size-reached-vs-uart_timeout-elapsed race — is confirmed
orthogonal to evt[2:0] classification and is not folded into it here.

Scope difference from every prior batch in this series: UART was not
wired into rcp/mock.hpp at all before this change (grep for uart/Uart
on prior main returns nothing) — this is mock.hpp's FIRST UART wiring,
adding it as the 8th mock endpoint (byte_bus_id=8). UART also has two
separate request-side entry points (enqueue_tx for writes, handle_read
for reads) rather than one unified transfer()/transmit() call, so
UartEndpoint::handle_request classifies evt[2:0] exactly once and then
routes Plain requests on an explicit is_write argument — Reserved and
ConfigWrite reject before either queue is touched. mock.hpp's new
dispatch_uart follows dispatch_gpio's existing req.op-branching
convention to build the write vs. read response shape.

- include/rcp/uart.hpp: UartErrc::config_write_not_supported (5) +
  UartEndpoint::handle_request(evt_op, is_write, tx_bytes, read_size,
  elapsed_ms, uart_timeout_ms, out_data, out_timed_out).
- include/rcp/mock.hpp: kUartEndpointId/kUartByteBusId (8), uart_
  member + uart() accessor, dispatch_uart wired into dispatch(),
  wire_error_code_for mappings for config_write_not_supported (required)
  and read_size_exceeds_bound/tx_queue_overflow (reachable via dispatch).
- tests/test_uart.cpp, tests/test_mock.cpp: Plain (TX and RX routing),
  Reserved x6, ConfigWrite, and evt_op masking coverage at both the
  endpoint and mock-dispatch level, mirroring the CAN/LIN PRs' test
  shape.
- .fusa-reqs.json: REQ-UART-008 (cybersecurity-tagged, matching
  precedent's per-endpoint-classification tagging pattern)/REQ-UART-009,
  REQ-MOCK-001 (seven- -> eight-endpoint), REQ-MOCK-021/REQ-MOCK-022.
  tc18_master_id links (TC18-13.5-001, TC18-12.7.1-001) independently
  re-verified against RELAY's docs/tc18-master-catalog.json — both
  entries' own title/summary already name UART/evt[2:0]=111b exactly,
  reused unchanged from the CAN PR's precedent.

Verification: cmake+ninja build clean; ctest 53/53 passing; cpfusa
check --strict 0 errors; cpfusa trace 100% traced+tested; cpfusa trace
--sec-tested 17 passes (70/402 cybersecurity-tagged requirements,
~17.4%). Mutation-tested handle_request's ConfigWrite branch (made it
fall through to Plain's TX/RX routing) — REQ-UART-009 and REQ-MOCK-021
config-write tests failed as expected; cleanly reverted, confirmed via
git diff and a full green re-run.

Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
SoundMatt added a commit that referenced this pull request Aug 20, 2026
…ndpoint type) (#120)

Wires §13.5 Table 33 Row 2's Plain/Reserved/ConfigWrite evt[2:0]
classification into the ISELED endpoint (§13.7.12), the 7th of the 8
endpoint types sharing this row ({ADC, PWM_IN, I2C, LIN, CAN, UART,
ISELED, MDIO}), following the pattern established by I2C (#114), ADC
(#115), PWM_IN (#116), LIN (#117), CAN (#118), and UART (#119).

- rcp/iseled.hpp: add IseledEndpoint::handle_request(evt_op, request,
  response), which classifies evt[2:0] via the existing shared
  endpoint::evt_row2_kind_of() predicate before doing anything else —
  Plain (000b) delegates straight to the existing transact(request,
  response) unchanged, Reserved (001b-110b) returns
  endpoint::EndpointErrc::reserved_evt_row2 without recording
  anything, and ConfigWrite (111b) returns the new
  IseledErrc::config_write_not_supported (§12.7.1, deliberately
  deferred, same scope decision every prior Row 2 endpoint made).

  This does NOT touch, reinterpret, or extend IseledRequest's/
  IseledResponse's existing field shapes, validate_request/
  validate_response, or the encode_iseled_request/decode_iseled_request/
  encode_iseled_response/decode_iseled_response Figure 40/41 codec in
  any way — those were already corrected against TC18's real wire
  format under issue #71/cpp-RCP-02, and this PR is strictly the
  separate, orthogonal Table 33 evt[2:0] classification concern. No
  new encoding of any kind is invented here.

- rcp/mock.hpp: wire ISELED in as the 9th mock endpoint (byte_bus_id
  9), following UART's (#119) wiring shape: new iseled::IseledEndpoint
  member, kIseledEndpointId/kIseledByteBusId constants, dispatch_iseled
  routing (decodes the wire payload via ISELED's own pre-existing
  decode_iseled_request, drives handle_request, encodes the response
  back via encode_iseled_response — reusing existing codec functions,
  inventing nothing), a set_iseled_response() scripting hook mirroring
  set_i2c_response/set_lin_response, and wire_error_code_for mappings
  for IseledErrc. REQ-MOCK-001's endpoint count is updated to nine.

- .fusa-reqs.json: adds REQ-ISELED-006/007 and REQ-MOCK-023/024,
  reusing tc18_master_id TC18-13.5-001 (Table 33 Row 2, which
  explicitly names ISELED) and TC18-12.7.1-001 (§12.7.1 config-write),
  independently re-verified against RELAY's docs/tc18-master-catalog.json
  rather than assumed from precedent. REQ-ISELED-006 (the low-level
  classification requirement) is tagged severity: cybersecurity,
  matching every prior Row 2 endpoint's precedent.

- tests/test_iseled.cpp, tests/test_mock.cpp: Plain/Reserved(x6)/
  ConfigWrite coverage at both the IseledEndpoint::handle_request level
  and the mock dispatch level, plus pre-RCP_CONFIGURED gating.

Verification:
- cmake --build + ctest: 53/53 test binaries pass.
- cpfusa check --strict: 0 errors (2 pre-existing warnings/1 info
  unrelated to this change: FUSA004 evidence bundle, HARA005 hazard
  ASIL mismatch — both present on main before this PR).
- cpfusa trace: 406/406 requirements 100% annotated, 100% tested.
- cpfusa trace --sec-tested 17: passes, secTestedRequirements 71/406
  = 17.49%.
- Mutation test: temporarily made the ConfigWrite branch call
  transact() instead of returning config_write_not_supported;
  confirmed it broke 2 test_iseled assertions and 1 test_mock
  assertion; reverted cleanly (git diff showed no leftover) and
  confirmed tests pass again.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
SoundMatt added a commit that referenced this pull request Aug 20, 2026
… LAST endpoint type) (#121)

Implements TC18 (OPEN Alliance TC18 Remote Control Protocol Specification
v0.5.1_RC) §13.5 Table 33's "Row 2" evt[2:0] request classification for the
MDIO endpoint type (include/rcp/mdio.hpp), the eighth and LAST of the eight
endpoint types sharing this row ({ADC, PWM_IN, I2C, LIN, CAN, UART, ISELED,
MDIO}), following the pattern established by I2C (#114), ADC (#115),
PWM_IN (#116), LIN (#117), CAN (#118), UART (#119), and ISELED (#120).

Classification rule (via the shared rcp::endpoint::evt_row2_kind_of /
EndpointErrc::reserved_evt_row2 predicate already in rcp/endpoint.hpp — not
reinvented here):
  - evt[2:0] == 000b -> Plain: delegates to the existing transaction logic
  - evt[2:0] in 001b-110b -> Reserved: rejected with
    EndpointErrc::reserved_evt_row2 (wire error code UNSUPPORTED_CMD)
  - evt[2:0] == 111b -> ConfigWrite (§12.7.1, not yet implemented): reported
    via new MdioErrc::config_write_not_supported

## handle_request -> transact rename (naming collision)

MdioEndpoint already had a pre-existing method literally named
`handle_request(MdioRequest, MdioResponse&)` — the read/write register
transaction logic, unrelated to evt[2:0] classification, predating this
milestone. To free that name for the new evt[2:0]-classifying entry point:

  1. Renamed the existing method to `transact(MdioRequest, MdioResponse&)`,
     matching the name rcp/iseled.hpp's IseledEndpoint::transact already
     established for the identical "existing single-call transaction
     method, now wrapped by a classifying handle_request" role.
  2. Updated all 8 call sites in tests/test_mdio.cpp from `.handle_request(`
     to `.transact(`. Grepped the whole repo (including mock.hpp/mdio_.*)
     to confirm no other call sites existed before this PR (mock.hpp had
     zero MDIO wiring at all prior to this change).
  3. Added a NEW `handle_request(uint8_t evt_op, MdioRequest, MdioResponse&)`
     that classifies evt_op first: Plain delegates straight to transact()
     unchanged, Reserved returns EndpointErrc::reserved_evt_row2 without
     calling transact(), ConfigWrite returns MdioErrc::config_write_not_supported.

Confirmed clean: `grep -rn "\.handle_request("` across the repo shows zero
remaining 2-argument MdioEndpoint::handle_request call sites; every call
site now either invokes transact() directly (register-access tests) or the
new 3-argument evt[2:0]-classifying handle_request (classification tests).

## Addressing model — untouched

include/rcp/mdio.hpp's own header comment documents that this header
previously modeled MDIO addressing as an invented IEEE 802.3 Clause
22/Clause 45-style PHY-address scheme with no basis in TC18, corrected in
issue #72 to the real mdio_mode/mdio_address/mdio_payload model. This PR
does NOT touch, reinterpret, or extend MdioRequest's/MdioResponse's field
shapes or the mdio_mode/register_key logic in any way — Table 33's evt[2:0]
classification is a completely separate, orthogonal concern, and no new
MDIO wire encoding is invented anywhere in mdio.hpp itself.

## mock.hpp wiring (10th mock endpoint, byte_bus_id=10)

Wires MdioEndpoint into rcp/mock.hpp for the FIRST time (zero prior MDIO
wiring, verified via `grep -n "mdio\|Mdio\|MDIO" include/rcp/mock.hpp` on
main returning nothing), following UART's (#119) and ISELED's (#120)
wiring shape:
  - byte_bus_id/endpoint id 10, after ISELED's 9
  - REQ-MOCK-001's endpoint count updated eight -> nine -> ten (nine was
    already current on main from the ISELED PR)
  - new dispatch_mdio(), branching on req.op like dispatch_uart (since
    MdioRequest::is_write is intrinsic to the request itself, unlike
    I2C/LIN's op-agnostic full-duplex/raw-transfer dispatch)
  - Unlike ISELED, NO MDIO byte-level wire codec (Figure 43/Table 60)
    exists anywhere in this codebase — mdio.hpp deliberately stops at the
    (mode, mdio_address, mdio_payload) struct level. dispatch_mdio makes
    the same kind of deliberately simple, explicitly disclaimed choice
    dispatch_can makes for CAN's fixed CanIdentifier/FrameFormat: a fixed
    representative MdioMode::MmdSingleWord/mdio_address=0, with
    mdio_payload packed/unpacked via avtp::detail::put_u16/get_u16 (the
    same generic helper dispatch_ep0 already uses for its own magic-number
    encoding) purely so the mock has *some* round-trippable wire value —
    this is the mock's own simulation choice, not a TC18-derived encoding.
  - No set_mdio_response() hook: MDIO's register model is self-contained
    (a write round-trips straight back out on a subsequent read), unlike
    I2C/LIN/ISELED's external-bus scripting hooks.

## .fusa-reqs.json

Added REQ-MDIO-006/007 (evt[2:0] classification + config-write-not-yet-
supported) and REQ-MOCK-025/026 (mock dispatch classification + pre-
RCP_CONFIGURED gating), reusing TC18-13.5-001 and TC18-12.7.1-001 —
independently re-verified against RELAY's docs/tc18-master-catalog.json
(both entries confirm MDIO by name in Table 33's Row 2 list) and against
the actual TC18.txt line ranges cited (L4085-4092 and L2844-2865
respectively; both match the source text exactly). Also updated
REQ-MDIO-003/004's requirement text (transact rename), since those
describe the exact method being renamed. Tagged REQ-MDIO-006 (the
reserved-value-rejection requirement) severity: cybersecurity, matching
every prior Row-2 endpoint's pattern; sec-tested ratio after this change
is 72/410 = 17.56%, above cpfusa's 17% floor.

## Pre-existing citation spot-check (not touched by this diff)

Spot-checked MDIO's own PRE-EXISTING TC18.txt citations against the actual
document content at /Users/matt/Coding/SoundMatt/TC18.txt:
  - REQ-MDIO-001 (§13.2 Table 32, "L3998-4017"): confirmed exact match —
    Table 32's "0x0D MDIO" row is at line 4017, table starts ~3998.
  - REQ-MDIO-002 (§13.7.13.3 Table 60, "L6080-6088"): confirmed exact
    match — Table 60's mdio_mode/mdio_address/mdio_payload rows and its
    own caption line are within 6081-6088.
Unlike ISELED's citations in the rust-RCP repo (found ~400 lines stale in
PR #161), MDIO's pre-existing citations in cpp-RCP show NO drift against
the current TC18.txt — both line ranges verified correct as-is. No
corrective action taken (not needed).

Separately (not a TC18-citation issue, but noted for completeness):
REQ-MDIO-005's requirement text still references
`MdioErrc::phy_address_out_of_range`, a symbol that no longer exists in
mdio.hpp's enum — a leftover from the pre-issue-#72 Clause 22/Clause 45
addressing model that was apparently never updated when MdioErrc was
rebuilt. This requirement's text/enum-name mismatch is NOT part of this
PR's diff and is left as-is per the same "flag, don't silently rewrite"
policy applied to citation staleness; worth a small follow-up.

## Testing

- tests/test_mdio.cpp: renamed 8 call sites to transact(); added 6 new
  TEST_CASEs covering Plain/Reserved(x6)/ConfigWrite/masking/field-
  independence classification, plus a config_write_not_supported message
  sanity check.
- tests/test_mock.cpp: REQ-MOCK-001 nine -> ten endpoints; new MDIO section
  mirroring CAN/UART/ISELED's shape (write-then-read round-trip through
  dispatch(), Reserved x6, ConfigWrite, pre-RCP_CONFIGURED gating).
- Full build (cmake+Ninja) and `ctest --test-dir build`: 53/53 suites pass,
  0 failures.
- `cpfusa check --strict`: 0 errors (2 warnings/1 info are pre-existing
  baseline conditions, confirmed identical on main before this change —
  FUSA004 missing .fusa-evidence.json, HARA005 hazard ASIL mismatch).
- `cpfusa trace`: 410/410 requirements 100% annotated, 100% tested;
  `--sec-tested 17` passes (17.56% cybersecurity-tagged ratio).
- Mutation test: temporarily changed the ConfigWrite branch in
  MdioEndpoint::handle_request to call transact() instead of returning
  the error; rebuilt; confirmed 2 test_mdio assertions and 1 test_mock
  assertion failed as expected; reverted cleanly; confirmed all tests pass
  again and `grep -n "MUTATION-TEST"` finds nothing left behind.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant