v3.0.0: full ground-up rewrite, ported from c-RCP - #172
Merged
Conversation
…hase 16's organic conclusion The incremental path through v2.23.0 never reached TC18 RC5 conformance -- this codebase was built and 'conformance-fixed' against the pre-RC5 baseline the whole time (confirmed via a real defect: spi.hpp is missing RC5's spi_nr_cs 4-bit re-encoding and new deassert_cs_pause bit, ticket NXP_100, not just a citation gap). It also carries no MC/DC coverage, no requirement-atomicity audit, no fixed-capacity architecture, and a replay-detection mechanism implemented but never wired into mock.hpp's dispatch. Rather than patch all of that in place, v3.0.0 is reached by rebuilding this codebase from c-RCP's current (RC5-conformant, atomicity-audited, ASIL-D-hardened) content, module by module, on branch rewrite/v3-from-c-rcp. Adds Phase 17 documenting the phase breakdown, supersedes the six parity-gap issues (#122-127) filed earlier the same day once their scope was found too narrow. Tracked at #129. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
The v3.0.0 rewrite (Phase 17, issue #129) deliberately lives on this branch rather than main for however many sessions it takes, but ci.yml/dco.yml were still scoped to push/pull_request against main only -- meaning every phase PR against this branch got zero CI signal. Add this branch alongside main in both workflows' triggers so 'CI green before merge' (the discipline every phase PR is held to) actually means something here, not just on the eventual cutover PR. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
…ference (#130) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. First module pair: the wire-protocol core everything else in Phase 1 depends on. Real content deltas found and fixed against c-RCP's src/acf.c+include/rcp/ acf.h and src/avtp.c+include/rcp/avtp.h: - ACF_GBB Message Info geometry was wrong (regression from a prior v2.22.0 pass): message_timestamp was spliced *between* the header's two quadlets. c-RCP's rcp_acf_encode_gbb() and REQ-ACF-044 are unanimous the real layout is contiguous -- 8-byte header + 8-byte timestamp + payload. Every ACF_GBB frame this library emitted since v2.22.0 was unreadable by a conformant peer. Reverted to contiguous. - Added missing ACF protocol-logic surface (additive, no renames): pad_len(), peek_msg_type(), header_is_request(), request_header_constraints_valid(), evt_row2_is_plain()/ evt_requests_acknowledge() (Table 33), compound_wait_evt_valid()/ compound_wait_match() (all 8 TC18 §13.5.1 comparison modes), reg_write_len() (TC18 §13.7.1.2, RC5-corrected -2 formula), peek_gbb_request_type(), and response builders build_error_response()/build_acknowledge_response()/ build_acknowledge_rejected_response(). - avtp.hpp: NtscfHeader gained sv/version; TscfHeader gained sv/version/mr/tu/reserved0/reserved1 (round-tripped, decode-populated, defaults reproduce prior encode behavior exactly). Added extend_timestamp() (48-bit gPTP reconstruction), TscfFallback + should_drop_tscf(), tscf_reserved_all_zero() -- TC18 §13.3 rules, previously entirely absent. test_acf.cpp: 24 -> 63 test cases (267 assertions). test_avtp.cpp: 15 -> 34 test cases (86 assertions). The GBB geometry fix broke 3 test_e2e.cpp assertions that hardcoded the old (wrong) spliced layout as their expected value -- corrected those 3 assertions plus their surrounding comments to the contiguous layout; did not touch e2e.hpp's own production logic (that's Phase 2 scope). Deliberately NOT ported from c-RCP, with rationale: - c-RCP's stricter decode_acf_abb/_gbb (rejects a payload not exactly acf_msg_length*4 bytes, trims trailing pad) -- every real payload builder in this tree hands non-quadlet-aligned payloads to encode_* without pre-padding, and test_e2e.cpp asserts decode does NOT trim pad today. Left a TODO(phase1-followup) with the full rationale; revisit once a later phase makes every payload builder pre-pad via pad_len(). - c-RCP's always-recompute/always-auto-pad encode contract -- kept cpp-RCP's existing 0-means-auto-fill/nonzero-means-override contract, since e2e.hpp's apply_acf_length_adjustment() (CRC trailer accounting) is load-bearing on it and e2e.hpp is out of Phase 1 scope. - c-RCP's transport vtable + in-process loopback transport -- cpp-RCP's own idiom (l2.hpp/udp.hpp's concrete per-transport classes, no shared interface, documented in l2.hpp's own header comment) is deliberately different; porting a shared vtable would contradict it. Verified: full tree rebuilds clean (217/217 targets), 53/53 ctest suites pass (100%), zero call-site fixes needed outside the 5 files touched. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ty, ported from c-RCP (#131) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Second batch of Phase 1, alongside request/sequencer/scheduler (separate PR). - fragment.hpp (NEW): cpp-RCP has never had multi-AVTPDU fragmentation -- this codebase made an explicit no-go call at ROADMAP.md milestone 52 (v2.8.0). Ported rcp::fragment::plan_count()/plan() (encode) and Reassembler (decode) from c-RCP's fragment.h/fragment.c (REQ-FRAG-*, 18 requirements). Fixed-capacity: Reassembler embeds a std::array<uint8_t, 4096> instead of c-RCP's realloc()-grown buffer -- no heap allocation at all, stronger than c-RCP's own C version. Ported c-RCP issues #614/#616's oversized-reassembly lesson as a design principle: this module stays ACF-agnostic (matching c-RCP -- the real fix there lived in mock.c's dispatch layer, not fragment.c) and always reports the true, complete reassembled length, so a future integrator (Phase 3/4 wiring, out of scope here) can check against an ACF frame ceiling before re-encoding, exactly the check c-RCP's dispatch layer had to learn to add. 30 test cases, 132 assertions. - respqueue.hpp (NEW): the per-response/ack-stream TX queue TC18 Sec12.7.9 describes -- also entirely absent before. Ported from c-RCP's respqueue.h/respqueue.c (REQ-RMAP-059/061/062/063/064/065/085): octet-budgeted FIFO, per-message max_avtpdu_size_octets ceiling, and TC18 Sec12.9.4/Sec12.9.5's evict-lowest-sequence_num overflow policy (not FIFO-oldest). kMaxEntries=64 from c-RCP's RCP_RESPQUEUE_MAX_ENTRIES, backing a fixed std::array of entry slots. 27 test cases, 405 assertions. - loan.hpp: found BufferPool::pool_ was an unbounded std::vector<unique_ptr<vector<uint8_t>>> with no capacity ceiling -- a real gap flagged in an earlier parity audit this session. Converted to a std::array<vector<uint8_t>*, 64> matching c-RCP's own RCP_LOAN_POOL_MAX_ENTRIES bound exactly (c-RCP bounds entry count only, not per-buffer size -- matched that, not a stricter policy). Once the free list is full, release now frees the buffer outright instead of growing storage further, mirroring c-RCP's pool_append() degradation. Also removed a redundant heap re-wrap the old code did on every reuse. Test count 6 -> 9 (new: pool sanity, destructor-frees- free-list, and a fixed-capacity overflow test that mutation-proves the bound). Not cleanly ported, documented in-file rather than left as a follow-up TODO (structural, not a gap to close later): c-RCP's RCP_FRAGMENT_REASM_ERR_ALLOC and its allocation-failure-injection test cases across all three modules have no C++ analog -- the fixed-capacity, no-heap design structurally eliminates the allocation-failure class, and cpp-RCP has no allocation-failure-injection seam yet (that's Phase 2's allocation-seam work). Verified: full tree rebuilds clean (221/221 targets), 55/55 ctest suites pass (100%). No endpoint file (can.hpp/spi.hpp/uart.hpp/ regmap.hpp's kOptFragmentation) touched -- wiring fragmentation into real dispatch is Phase 3/4 scope, not this pass. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…nformant reference (#132) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Third and final Phase 1 module: the conditional-request layer (compound, compound-wait, triggered, chained, timed, cancellation) plus the shared sequencer/scheduler state c-RCP keeps as 3 separate modules -- kept in cpp-RCP's existing single-module unification (request.hpp already did this better than c-RCP's own current split). Real content deltas found and fixed against c-RCP's request.c/ request_sequencer.c/scheduler.c: - Fixed cpp-RCP issue #58 (cs-bit polarity inverted in compound_wait_check_of()/should_execute_chained()) as part of this port, confirmed against c-RCP's rcp_chained_advance(): was 'cs || !predecessor_errored' (cs=true never aborted anything regardless of outcome), now '!cs || !predecessor_errored', matching RCP_CHAINED_CS_CONTINUE_ON_ERROR(0)/ABORT_ON_ERROR(1). - compound_wait_check_of()/CompoundWaitCheck had no basis in c-RCP -- traced through rcp_compound_encode_request()/_decode_request(), cs is hardcoded to 0 and never surfaced for compound/compound-wait. Removed; the real comparison-mode selector is the ACF header's evt[2:0] (acf::compound_wait_evt_valid/compound_wait_match, from the acf/avtp port), now exposed via CompoundRequest's evt_op field. - cancel_single() collapsed two distinct c-RCP outcomes (NOT_FOUND vs NOT_CANCELLABLE) into one. Added RequestErrc::request_not_cancellable, split the cases. - SequencerTable::try_advance's '+1 wrapping' model was wrong -- replaced with CompoundStep + advance_guard()/start_condition_met()/ apply_next_state()/tick()/wait_tick(), matching c-RCP's explicit next_state field and the guard/start-condition distinction (rcp_compound_advance_guard() vs rcp_compound_start_condition_met()). Also newly ported (behavior c-RCP has that cpp-RCP never had): structured wire sub-fields + encode/decode for every request kind, reserved-octet and evt/hs/cs wire validation, TriggeredRuntime, wraparound-safe timed admission mapped onto acf::WireErrorCode, and frame_timing_consistent. Fixed-capacity: RequestLedger::records_ (unconditional-push_back std::vector) -> BoundedVector<RequestRecord, 64>. c-RCP has no ledger to port a bound from (its request store is a pure-function library) -- this is this port's own decision. Chose 64, not 256 (the full transaction_num range): at 256/256 every possible id is already a collision, making the capacity check unreachable. Mutation-tested (disabled the full() guard, confirmed the new overflow test failed, restored). Not ported, by design (noted in-header, not a follow-up TODO): c-RCP's positional (has_predecessor/chain_position) chain model -- cpp-RCP's existing predecessor/successor graph in RequestLedger is strictly more capable and stays as the one chained-cascade model. Left as // TODO(phase1-followup): REQ-SEQ-013/014's per-sequencer owner + fail-closed access control (needs regmap.hpp storage, out of this pass's request.hpp-only scope); rcp_compound_peek_request_type()/ rcp_timed_encode_request_tscf() convenience wrappers (composable today from existing acf/avtp primitives). 12 TEST_CASEs (REQ-SEQ-only) -> 135 TEST_CASEs / 610 assertions, covering REQ-CMP/TRIG/CHAIN/TIMED/CANCEL/SEQ/SCHED. Verified: full tree rebuilds clean (221/221 targets, incl. the just- merged fragment/respqueue), 55/55 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. No call sites outside request.hpp/test_request.cpp required changes (e2e.hpp/watchdog.hpp/authz.hpp all kept their exact prior signatures against the new API). Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…m, ported from c-RCP (#133) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. First batch of Phase 2, alongside e2e/ lifecycle (separate PR). watchdog.hpp: investigation established cpp-RCP's StreamWatchdog/Manager is not a stale transliteration target -- it's a deliberate, already- shipped v2.10.0 design (ROADMAP.md milestone 54) with its own REQ-WDG-001..008 catalog that doesn't correspond 1:1 to c-RCP's REQ-WDG-001..012. c-RCP's own watchdog has since evolved to a background-thread rcp_watchdog_keeper_t (a later c-RCP redesign, not what this codebase's design was ever meant to mirror) -- porting that would contradict this codebase's own stated 'no clock/thread of its own' convention and break sim.hpp/mdns.hpp's existing dependents. Treated as a content audit instead, mapping each of c-RCP's 12 REQ-WDG-* against what transfers vs. doesn't. One genuine gap found and fixed: the c-RCP-17 fixed-capacity conversion (RCP_WATCHDOG_MAX_STREAMS /RCP_WATCHDOG_MAX_CALLBACKS, both 16) had never been ported -- Manager used unbounded std::map/std::vector, unlike every other Phase 17 module. Converted to fixed std::array-backed storage with swap-removal (matching loan.hpp's free-list technique); register_stream/subscribe now return std::error_code (new stream_capacity_exceeded/ callback_capacity_exceeded) instead of silently discarding overflow. alloc.hpp (NEW): cpp-RCP had zero allocation-failure-injection capability -- confirmed zero try/catch(std::bad_alloc) anywhere in the tree. c-RCP's own mechanism (a global rcp_alloc_set_hooks() function- pointer table) is deliberately NOT ported literally -- it's exactly the pattern that needed a later access-control retrofit in c-RCP itself (issue #600/PR #615, rcp_alloc_lock_hooks()) after any caller could silently redirect the allocator backing safety code. Built a small, instance-owned rcp::alloc::FaultInjector instead (mutex-guarded, arm(count)/should_fail()), matching faultinject.hpp's existing idiom. Phase 1's fixed-capacity modules (request/fragment/respqueue/loan) were checked first and found to not need it -- their capacity-exceeded paths are std::array-backed and never touch the heap, so they're already directly testable and can't be meaningfully fault-injected. Wired FaultInjector into loan.hpp's BufferPool::loan() cache-miss branch instead (its one real heap allocation), as a genuine, non-artificial demonstration it composes with a real caller -- optional constructor parameter, defaulted nullptr, fully backward-compatible. REQ-ALLOC-* (11 in c-RCP) breakdown, documented in alloc.hpp's header: none transfer as written -- 001/002/010/011 describe the global hook table this design deliberately lacks; 003-006 describe C's four separate allocator entry points, no C++ equivalent without a global operator new override; 007-009 (lock/unlock/query) exist only to retrofit access control onto a table that doesn't exist here. The one concept that transfers -- deterministic, portable allocation-failure testability -- is what FaultInjector::should_fail() provides. Call sites touched: sim.hpp's Simulator::register_stream widened from void to std::error_code to propagate the new capacity-exceeded result (existing callers discard it as a statement, no break). test_watchdog: 12 -> 15 test cases, 137 assertions. test_alloc (new): 13 test cases, 98 assertions. Verified: full tree rebuilds clean (223/223 targets), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…rstated H-004 claim (#134) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Second and final batch of Phase 2 -- the safety-critical core: CRC32 safe points, safety-request handling, replay/staleness detection, and the RC-Server lifecycle state machine. Real content deltas found and fixed against c-RCP's e2e.h/e2e.c and lifecycle.h/lifecycle.c: - CRC coverage was missing 3 leading bytes (c-RCP issue #465): avtp_subtype/header_octet1/tu -- TC18 Figures 20/21's own 'header CRC' region -- were absent entirely from coverage_buffer()/ compute_crc()/verify_crc(). A conformant peer's CRC32 would never have matched this library's prior output. - wrap()/unwrap() didn't exist at all -- only append_crc() (appends at buffer end), which produces the wrong wire order [header][real][pad][CRC] whenever a payload carries trailing pad bytes; TC18 requires [header][real][CRC][pad] (c-RCP issue #420). - RxSequenceGuard (REQ-E2E-028/029, the mechanism a prior audit this session flagged as implemented-but-unwired) was substantively wrong, not just incomplete: compared sequence numbers as a non-wrapping uint32_t (would spuriously reject every request once an 8-bit AVTPDU sequence_num wraps) and never consulted rx_seq_safestate_enable at all. Replaced with c-RCP's RFC 1982 forward-window comparison. - endpoint_in_configured_safe_state() was missing REQ-SEQ-012's fail-closed rule -- a disabled sequencer (state==0) could satisfy a safe-state check if rx_safe_sequencer_state was also misconfigured to 0. - Missing entirely, now ported: fragment_carries_crc()/ compute_fragmented_crc() (REQ-E2E-010/038), crc_error_should_enter_ safe_state() (REQ-E2E-045), StreamFaultTracker (REQ-E2E-021), StreamStatus (REQ-E2E-046), length_with_crc()/ data_length_for_protected_members() (REQ-E2E-004/037), overflow_should_enter_safe_state() (REQ-E2E-030). lifecycle.hpp had only a bare 3-state machine with no real access-control layer -- c-RCP's whole issue #198 (writer authorization, unicast enforcement, idle-gating) was absent. Added, additive: EndpointPlausibility/RequestStreamPlausibility/PlausibilitySnapshot + check_hw_cfg()/check_rcp_cfg() (real TC18 Sec12.3.1.2 plausibility content, not just an opaque callback); WriterCtx; ServerLifecycle::transition() (full writer-authorization + idle-gating state machine, including the RCP_CONFIGURED->HW_CONFIGURED demotion and both authorized resets advance() never modeled); Disposition/ should_accept() (per-state request filtering); field_writable()/ field_write_error() family (register-locking-by-state). advance() kept unchanged (mock.hpp and existing tests depend on its differing same-state semantics, a documented pre-existing design choice); transition() coexists as the richer, spec-complete entry point. HARA.md/FORMAL_VERIFICATION.md correction: HARA.md's H-004 section described RxSequenceGuard::check in present-tense mitigating language with SG-004 listed as a clean ASIL-B mitigation and no caveat. Added a 'STATUS CORRECTED' block: the mechanism is implemented and unit-tested but never instantiated outside its own test -- not in mock::Server's dispatch or any transport Server -- citing c-RCP's #601/#606 resolution as the parallel precedent, stating H-004 is 'effectively Open' pending that wiring (Phase 4/server-dispatch scope, not this pass -- mock.hpp was NOT touched, RxSequenceGuard is referenced only in e2e.hpp itself). FORMAL_VERIFICATION.md got the same caveat, plus a second, independently-found correction: tla/RxSequenceGuard.tla itself still models the old non-wrapping comparison, not the RFC 1982 algorithm now in e2e.hpp -- flagged as a real, un-re-derived divergence and tracked as a follow-up (re-deriving the TLA+ spec is its own formal-modeling task). Fixed-capacity: StreamFaultTracker uses a std::array<Slot, 16>, no heap allocation -- bound taken directly from c-RCP's RCP_E2E_STREAM_FAULT_TRACKER_MAX_STREAMS. lifecycle.c confirmed to have no alloc.h/heap dependency; no fixed-capacity work needed there. Left as // TODO(phase2-followup): apply_acf_length_adjustment() doesn't fail-safe on 9-bit field overflow the way c-RCP's adapt_acf_msg_length() does (low real exposure, but a fallible return type would touch every existing call site) -- named gap, not silently dropped. RxSequenceGuard.tla's staleness (above) is a second, separate tracked follow-up. test_e2e.cpp: 32 -> 72 TEST_CASEs. test_lifecycle.cpp: 16 -> 47 TEST_CASEs. Verified: full tree rebuilds clean (223/223 targets, incl. the merged watchdog/alloc-seam batch), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. No call sites outside target files required changes (regmap.hpp/mock.hpp depend on lifecycle.hpp, watchdog.hpp depends on e2e.hpp, all compile and pass unchanged since every new piece is additive). Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…7 missing i2c entry (#135) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. First of Phase 3's Wave 1 batches to land, alongside can/lin and adc/gpio (separate PRs). ROADMAP.md fix: Phase 17's endpoint-module list omitted i2c, a real 11th endpoint type both c-RCP and cpp-RCP have. Added. Both target modules were pre-rewrite (v2.20.0-era) content this rewrite's Phase 1/2 never touched -- not incremental drift, a wholesale mismatch against c-RCP's current design, confirmed against ep_i2c.h/.c and ep_iseled.h/.c: - i2c.hpp: the 'pilot' I2cMode/i2c_mode_of (a 2-way decode of AcfMessageInfo::hs) and a local compound_wait_matches_bits helper were both invented and wrong. c-RCP's i2c_mode is a persistent 5-way preset (STANDARD..ULTRA_FAST) stored in a Table 49 register block, never carried on hs; I2C has no endpoint-specific compound-wait logic in c-RCP at all -- acf.hpp's own compound_wait_match() (ported in Phase 1) is the correct generic primitive. Removed both. Ported: I2cFunctionalCfg + lifecycle-gated setters, the Table 49 register block, I2cDir- parameterized bidirectional transfer request/response codec (c-RCP REQ-I2C-010/018/023), i2c_mode_valid. - iseled.hpp: the old IseledRequest/IseledResponse structs (12-bit Instruction/Address/Data ACF-payload fields) modeled the wrong layer -- c-RCP's ACF codec treats the payload as opaque raw bytes (same as I2C/ LIN); the 12-bit-field decode belongs to ISELED's physical symbol layer, not the ACF wire codec. Replaced wholesale with c-RCP's real content: native 4-bit/5-bit even-parity bit framing, an independent CRC-8 integrity layer, requires_isp_n (issue #270 polarity fix), IseledFunctionalCfg + Table 58 register block, and the write-direction vs. read-direction request split (issue #471). fragment.hpp usage: c-RCP's ep_iseled.c genuinely #includes rcp/fragment.h and calls its plan_count()/plan() directly -- ported identically (response_fragment_count()/encode_response_fragmented()). I2C has no fragmentation in c-RCP; none added. Fixed-capacity: EpFuncBlock in both modules is a fixed std::array (11/14 octets), matching c-RCP's fixed-size register block. test_i2c.cpp: 14 -> 44 TEST_CASEs (203 assertions). test_iseled.cpp: 24 -> 75 TEST_CASEs (337 assertions). Call sites touched: mock.hpp's dispatch_iseled()/set_iseled_response() and 3 test_mock.cpp ISELED test cases depended on the removed Iseled Request/Response types -- updated dispatch_iseled to pass the raw ACF payload straight to IseledEndpoint::handle_request (mirroring dispatch_i2c's existing pattern), set_iseled_response to take std::vector<uint8_t>, removed the stale field_out_of_range wire-error mapping. i2c.hpp needed no mock.hpp changes (I2cEndpoint::handle_request's signature was unchanged). Not touched here, explicitly tracked for later phases: cpp-RCP's .fusa-reqs.json still has only 7 stale REQ-ISELED-*/REQ-I2C-* entries (Phase 6 scope); ISELED response fragmentation's mock-dispatch wiring is Phase 4 scope (only ep_iseled.c-side encode was ported here). Verified: full tree rebuilds clean (223/223 targets), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ia fragment.hpp (#136) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Second of Phase 3's Wave 1 batches, alongside iseled/i2c and adc/gpio (separate PRs). can.hpp: the pre-rewrite header was validation-only -- its own v2.24.0 ROADMAP.md note said 'no ACF-level wire encode/decode ... anywhere in this codebase'. Ported the real wire codec from ep_can.c's rcp_ep_can_encode_frame_request/response, _decode_frame_request/ response. FrameFormat widened from an invented 3-value {Classical,Fd,Xl} to c-RCP's real 6-value Table 57 set (Cbff/Ceff/Fbff/ Feff/XlClassicalPl/XlNewPl), each with its own 11-/29-bit id width derived from format -- fixes a real defect where the old CanIdentifier::extended flag was settable independently of FrameFormat, permitting contradictory states. CanBitTimingPhase::sync_jump_width corrected uint16_t->uint8_t; default prescaler corrected 1->0 to match c-RCP's zero-init contract. Ported CanFunctionalConfig (three bit- timing sets, delay compensation, xl_filters[4]) and the Table 56 EP_func register block, porting forward c-RCP issue #470's enable&clr bit-4 fix (was bit 1) directly rather than re-introducing the bug. CAN XL fragmentation (c-RCP issues #610/#611/#612/#613) wired via Phase 1's fragment.hpp: frame_request_fragment_count()/ encode_frame_request_fragmented() and the response-side count/encode/decode/reassemble quartet, all built on fragment::plan_count()/plan()/Segment and fragment::Reassembler. Combined-payload and fragment-plan buffers are fixed std::array (2058 octets / 256 segments, c-RCP issue #521's bounds) -- no heap allocation. Tests round-trip a worst-case 2048-octet CAN XL write request and read response end to end through Reassembler, and prove the c-RCP #614/#616 oversized-reassembly lesson holds (kErrTooLarge, not silent truncation, both immediately and partway through a multi-fragment sequence). lin.hpp: ported response_matches() (thin wrapper over the already- ported acf::compound_wait_match), LinTrigger/trigger_fires(), LinFunctionalConfig + Table 55 register block, and the real encode_command_request/decode_command_request/encode_response/ decode_response wire codec -- none of this existed in the pre-rewrite header. Fixed-capacity: kXlMaxEncodedLen/kMaxFragmentSegments/kMaxXlFilters all std::array-backed, ported from c-RCP's own bound constants. CanDataFrame::data/LinEndpoint's byte buffers deliberately stayed std::vector<uint8_t> -- mock.hpp assigns from a const std::vector<uint8_t>&, which wouldn't compile against a fixed array. test_can.cpp: 20 -> 43 TEST_CASEs (267 assertions). test_lin.cpp: 12 -> 28 TEST_CASEs (103 assertions). Call sites touched: none. mock.hpp confirmed unchanged and its own test suite passes against the new headers unmodified. Left as // TODO(phase3-followup) in both headers: once regmap.hpp's functional-config split is re-derived from c-RCP (Phase 17 item 4), recompose the five local ep_enable/ep_clear_req_storage/ ep_req_crc_enable/ep_response_ts_enable/ep_suppress_response flags onto that shared struct instead of carrying local duplicates. can.hpp also documents (not a TODO, a deferral matching c-RCP's own disposition) that Table 56's acceptance-filter register region has a genuine unresolved address-collision defect in the primary source, so this module's register-block model deliberately stops at kEpFuncLen. Verified: full tree rebuilds clean (223/223 targets), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ng regression (#137) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Third of Phase 3's Wave 1 batches, alongside iseled/i2c and can/lin (separate PRs). adc.hpp (55 requirements) was substantially wrong, not just incomplete: - Critical bug: the pre-rewrite pilot-era AdcEndpoint::request_reading averaged adc_combine_avg_values interval-means down to *one* output value. c-RCP's own file header records this exact mistake was already made and fixed once in c-RCP's history -- adc_combine_avg_values is a packing count, not a second reduction; a response carries N values, never one (Sec13.7.9.2/.3). Fixed: average_interval/ collect_response_values now port rcp_ep_adc_average_interval()/ rcp_ep_adc_collect_response_values() 1:1; AdcEndpoint redesigned around execute_measurement_cycle()/response_ready()/ collect_response(). - Field-naming bug: AdcAveragingConfig::adc_avg_intervals_per_request was actually being used for a layer-1 samples-per-interval count -- c-RCP's adc_samples_per_avg_interval's role, not layer 2's meaning. - Missing entirely, now ported: wall-clock inter-sample spacing validation (REQ-ADC-033), cadence classification (REQ-ADC-037/053), Table 53 threshold/measurement-finished triggers (REQ-ADC-031/ 048-052), the full EP_func register block + apply_reconfig (REQ-ADC-035/036/038-040, closing the evt[2:0]=111b gap the old header stubbed as config_write_not_supported), lifecycle-authorized functional-config setters, real ACF-level read-request/response/ reconfig wire codec. gpio.hpp (46 requirements) core was already correct; extended: - Bug found and fixed: apply_gpio_write's Subtract semantics computed current-operand; c-RCP's rcp_ep_gpio_apply_write() (REQ-GPIO-011) defines the row normatively as request minus current. Fixed locally in gpio.hpp, not in the shared endpoint.hpp helper, to avoid silently changing pwm.hpp's PWM_OUT semantics (out of this phase's scope, not yet re-verified). - Missing entirely, now ported: Table 43 wire trigger-signal numbering (REQ-GPIO-034), per-pin debounce filtering (REQ-GPIO-035/044), response-timing classification (REQ-GPIO-036), reserved evt[2:0]= 100b wire rejection (REQ-GPIO-012/045/046), the full EP_func register block + apply_reconfig (REQ-GPIO-013/038-042, with c-RCP's own resolved gpio_debounce_IO31 editorial-defect offset at 0x0028), functional-config setters, real ACF-level wire codec. - rcp_ep_gpio_toggle_pin_direction() (no longer a real TC18 mechanism per c-RCP's own comment) deliberately not ported -- low-value, non-wire caller convenience. Fixed-capacity: AdcEndpoint's pending-averaged-value accumulator is a std::array<AdcAvgValue, 255>-backed BoundedVector (255 = Table 54's own 8-bit adc_combine_avg_values register width). GPIO's existing std::array<uint8_t, kMaxPins> pattern extended to GpioFunctionalConfig::pins/debounce. test_adc.cpp: 18 -> 62 TEST_CASEs. test_gpio.cpp: 20 -> 62 TEST_CASEs. Call sites touched: mock.hpp's dispatch_adc() rewritten against the new AdcEndpoint API and to route evt[2:0]=111b through the now-real adc::apply_reconfig instead of rejecting it; wire_error_code_for() updated (removed config_write_not_supported, added reconfig_short/ reconfig_out_of_range mappings). tests/test_mock.cpp: two ADC config-write tests updated for the now-real reconfig behavior. Left as // TODO(phase3-followup): rcp_ep_gpio_toggle_pin_direction() not ported (noted in gpio.hpp's header, no stub added). Full dispatch-level wiring (response-shape composition, RxSequenceGuard, fragmentation) for both endpoints' new EP_func/reconfig paths remains mock.hpp's own Phase 4 scope -- this pass's minimal dispatch_adc wiring is enough to exercise apply_reconfig and keep the tree green, not a full Phase 4 implementation. Verified: full tree rebuilds clean (223/223 targets), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…n regression (#138) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. First of Phase 3 Wave 2's three batches, alongside spi/uart and pwm/wakeup (separate PRs). c-RCP's largest single endpoint source file (ep_mdio.c, 1195 lines) -- mdio.hpp needed the largest single content expansion of any Phase 3 batch (309 -> 1354 lines). - A prior session's own 'addressing-model fix' (issue #72) was itself wrong and has been reverted: it discarded the real Clause-22/Clause-45 (MMD) addressing (5-bit prtad, plus regad directly or devad+16-bit regad) for an invented opaque mdio_address field, on the theory that Clause 22/45 had 'no basis' in TC18. c-RCP's ep_mdio.h/.c (rcp_ep_mdio_addr_t, rcp_ep_mdio_addr_valid(), rcp_ep_mdio_burst_next_regad()) keeps this addressing as real, load-bearing content -- restored as MdioAddr/addr_valid()/ burst_next_regad() (REQ-MDIO-001/002). - No ACF-level wire codec existed at all -- ported the complete read/write request/response pair for both the MMD family and the entirely separate MMS family (REQ-MDIO-012-019/025-028). - MMS (Memory Map Selector) addressing family was entirely missing -- added MdioMmsAddr, mms_addr_valid()/mms_uses_32bit_words()/ mms_burst_next_addr()/mms_mode_for_word_count(), plus 32-bit word packing (REQ-MDIO-022/024). - REQ-MDIO-024 (the external-spec ambiguity) reflected explicitly: TC18 itself never specifies mdio_address's bit layout for MMS mode (spec-defects item 55, still open); c-RCP closed it with a documented, externally-sourced assumption (OPEN Alliance 10BASE-T1x MAC-PHY Serial Interface Spec V1.1's 4-bit MMS selector + 16-bit ADDR) -- carried forward as MdioMmsAddr{mms, addr}, kept characterized as PARTIAL not IMPLEMENTED, matching c-RCP's own .fusa-reqs.json status. - Table 59 functional-config register block was entirely missing -- added, with no base_clk row (unlike every other endpoint type, since Table 59 genuinely defines none) (REQ-MDIO-009/010/020/023). - Invented TriggerRegistry/MdioSignal::TransferComplete removed -- c-RCP's own file header states MDIO has no trigger-signal table at all, mirroring can.hpp's CanEndpoint (no TriggerRegistry either). Fixed-capacity: EpFuncBlock is std::array<uint8_t, 6> (narrower than sibling endpoints since MDIO has no base_clk row). test_mdio.cpp: 12 -> 121 TEST_CASEs (420 assertions). Call sites touched: none. MdioRequest/MdioResponse/MdioEndpoint's transact/handle_request/last_request kept fully source-compatible, so mock.hpp's dispatch_mdio() and its own MDIO tests compile and pass unmodified. Verified: full tree rebuilds clean (223/223 targets), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
… operand order (#140) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Third of Phase 3 Wave 2's three batches, alongside mdio and spi/uart (separate PRs). ## pwm.hpp - Fixed the same operand-order bug class found and fixed in gpio.hpp during PR #137: PWM_OUT's Subtract conditional-request handling used current - operand instead of c-RCP's normative request - current. This is deliberately fixed per-endpoint (not centralized into endpoint.hpp), matching the same caution documented in #137 that this bug needed independent re-verification at every endpoint using Subtract semantics, not an assumption that one fix covers all of them -- confirmed here to be a real, independent instance of the same class, not a duplicate report. - Ported against c-RCP's current RC5-conformant ep_pwm.h/.c. ## wakeup.hpp - Prior cpp-RCP wakeup.hpp content was a wholesale invention with no real relationship to c-RCP's actual ep_wakeup.h/.c -- re-derived from scratch against c-RCP's current implementation. - REQ-WAKEUP-020/026's regmap.c-scoped behavior intentionally NOT ported here (out of scope for the endpoint module itself; deferred to Phase 4's regmap port, noted in-file). ## Call sites None touched -- mock.hpp and powerstate.hpp (and their tests) confirmed compatible with the new pwm.hpp/wakeup.hpp APIs without modification. ## Verification Full tree rebuilds clean (223/223 targets), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build in the worktree, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…pause fix (#139) * rewrite(phase3): port spi and uart from c-RCP, RC5 nr_cs/deassert_cs_pause fix Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Second of Phase 3 Wave 2's three batches, alongside mdio and pwm/wakeup (separate PRs). ## spi.hpp - The RC5 wire-format defect that originally motivated this whole rewrite (see #129's own opening rationale) is now actually fixed: spi_nr_cs narrowed from an 8-bit count to a 4-bit (count-1) field, plus a new deassert_cs_pause bit (RC5 ticket NXP_100). Confirmed against c-RCP's current ep_spi.h/.c bit layout, not just cited from memory. - Compound-wait handling for SPI transactions ported (SPI has its own distinct compound-wait semantics from the generic request.hpp path, per c-RCP's ep_spi.c). - Fixed-capacity conversions applied throughout (no dynamic allocation on the transfer path). ## uart.hpp - nr_bits bug fixed: prior cpp-RCP state miscomputed the bit-width field against c-RCP's actual ep_uart.c encoding. - Ported against c-RCP's current RC5-conformant ep_uart.h/.c. ## Call sites mock.hpp: one enum-rename fix (spi::SpiErrc::channel_out_of_range -> bad_channel) to match the renamed error enumerator; no other call sites needed changes. test_spi.cpp / test_uart.cpp expanded to match the new content (compound wait, nr_cs/deassert_cs_pause, nr_bits coverage). ## Verification Full tree rebuilds clean (223/223 targets), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build in the worktree, not just the porting agent's own report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * fix(spi): avoid cpfusa cyber CYBER002 false-positive on bare 'RC4' token The bare substring 'RC4' in a comment describing TC18 spec revision history was matched by cpfusa's weak-symmetric-cipher heuristic (CYBER002/CWE-327, 'Weak symmetric cipher (DES/3DES/RC4) detected') as if it were the RC4 stream cipher, failing PR #139's cpfusa cyber --strict gate (the only [ERROR]-level finding in that run; the other 568 findings in the log are pre-existing [WARNING]-level noise that doesn't gate). c-RCP's own ep_spi.h uses the same bare 'RC4' token in its equivalent comment and isn't subject to this particular gate. Reworded to 'the RC 4 revision' (space inserted) -- same meaning, no contiguous 'RC4' substring for the scanner to match. No functional or documentation-content change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --------- Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…eneric/functional split (#142) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 4 batch A of a two-batch regmap port (batch B — HW pin map, named-signal index, request-stream/response-queue config, EP-ID map, optional-subsystem sections — is a separate later PR). c-RCP's regmap.h/.c (3549+2662 = 6211 lines) is regmap's own content source of truth; cpp-RCP's prior regmap.hpp (534 lines) was a stub with real content gaps, matching ROADMAP.md's own flagged '~7x more complete in c-RCP' finding that originally scoped this phase. ## Ported (c-RCP file:line -> cpp-RCP) - EP0/root-client model (regmap.h:358-371, regmap.c:12-15) -> regmap::is_ep0(), kEp0. - svr_implemented_options (REQ-RMAP-030, regmap.h:384-413): the 5 real bits (kOptCompoundWait/Trigger/Chained/TimeSync/EnhCancel); kept legacy kOptConditionalRequests/kOptSafetyRequests/kOptFragmentation (still referenced by request.hpp/e2e.hpp) with a comment explaining they have no TC18 basis per REQ-RMAP-004..008's retirement. - General register map (regmap.h:416-1141, regmap.c:41-288): new GeneralMap struct (~40 fields, REQ-RMAP-023/026-039), render(), encode_read_response()/decode_read_response()/decode_write_request() (Table 20 wire codec), GeneralMapErrc. - Root-client/writer_ctx (regmap.h:1152-1211, regmap.c:1684-1741): EpClient, writer_ctx() deriving lifecycle::WriterCtx. - SvrEpCfg (TC18 Sec13.7.1.2, regmap.h:1211-1303): svr_discovery_timeout, svr_ep_status. - EndpointGenericConfig (regmap.h:1310-1608): real fields (ep_type/ep_used/ep_delay_time/ep_req_storage_size/ep_description/ ep_tx_buffer_size/ep_rx_buffer_size), ep_generic_cfg:: namespace with boundary conversions (issue #311 batches 2-4), render(), apply_reconfig() (per-field, matching c-RCP's documented rationale), EpGenericCfgReconfigErrc. ## Deliberate deferrals (marked TODO(phase4-batch-b) in-file) - writer_ctx()'s via_valid_stream_association always returns false (fail-closed) until batch B's EP-ID/byte_bus_id association table lands (needs a request_stream_index field EpIdMappingEntry doesn't have yet). - HW pin map, request-stream-cfg, response-queue-cfg, EP-ID map, optional-subsystem config, ep_functional_cfg_t: untouched, batch B. ## Cross-cutting note c-RCP's ep_generic_cfg_t has no per-role CRC fields (that's ep_functional_cfg_t's, batch B); cpp-RCP's pre-existing EndpointGenericConfig already had three (ep_req/ack/response_crc_enable) depended on by e2e.hpp/test_e2e.cpp -- kept, marked in-file as pre-existing/non-c-RCP content rather than silently dropped or relocated (relocation needs ep_functional_cfg_t, batch B's scope). ## Call sites mock.hpp: RegisterMap::endpoint_count -> general.svr_ep_count, magic -> general.magic (2 lines). test_mock.cpp: matching 4-line fix. No other files touched. ## Verification test_regmap.cpp: 558 -> 1105 lines, 28 -> 57 TEST_CASEs (327 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Heavy regmap dependents (test_e2e, test_watchdog, test_request, test_discovery, test_mock, test_gpio, test_adc) spot-checked with unchanged assertion counts. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…pp (#141) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Runs independently of the regmap batches (no shared files) -- part of the {server, discovery} parallel wave the Phase 4 scoping pass called out. c-RCP's server.h/.c (673+1045 = 1718 lines) had no cpp-RCP home at all: endpoint.hpp is narrower (evt[2:0] write-semantics/trigger-signal tables, confirmed by reading both files in full), and request.hpp already deliberately owns the request.c/request_sequencer.c/scheduler.c merge from Phase 1 (RequestLedger, SequencerTable, TriggeredRuntime, select_next_due) -- neither had server.c's own ep_enable pre-load queue or its fixed-capacity conditional-request admission/scheduling store. New include/rcp/server.hpp (1180 lines) mirrors c-RCP's own module boundary, built on rcp::request's primitives rather than duplicating them. ## Ported (c-RCP server.h/.c -> rcp::server::Endpoint) All 21 public functions, REQ-SRV-001..042, REQ-PWRMODE-028, REQ-CANCEL-012, REQ-ACF-021/032, REQ-TIMED-012: - submit/set_enable/drain_one/queue_len -- ep_enable pre-load FIFO, including the config-write (evt[2:0]=111b) fast path, the CompoundWait carve-out (REQ-SRV-015), and the REQ-SRV-016 acknowledge-on-queue fix. - admit/admit_with_ack -- request_type-aware routing (AdmitOutcome:: ExecuteNow/Queued/Pending/Cancellation/Rejected/Suspended), REQ-ACF-021's response-frame rejection, REQ-TIMED-012's TSCF gate via admit_under_tscf_gate. - select_due/complete -- tick evaluation (TickContext) and repeat-count/re-arm rule. - notify_trigger, GptpTriggerState/gptp_trigger_evaluate (Table 37). - chain_predecessor_done, pending() (chain_group/chain_position). - cancel_all/cancel_single/cancel_non_safestate/watchdog_purge/ cancel_chain_from, CancelLifecycle/CancelResult/cancel_attempt/ cancel_chain_should_cascade. ## Deliberate design decisions (documented in-file) - select_due() inlines its own rank+FIFO comparator rather than calling request::select_next_due() (which takes a std::vector&) -- avoids a per-tick heap allocation on this safety-relevant path. - kMaxQueuedFrames = 32: c-RCP's ep_enable queue is genuinely unbounded (realloc-doubling, no cap); since this rewrite requires fixed-capacity storage on admission-path structures, a full queue reproduces c-RCP's own documented 'still returns false (queued), nothing actually stored' allocation-failure contract exactly -- no caller-visible behavior changed, only a probabilistic failure became deterministic. - cancel_chain_from/cancel_chain_should_cascade reintroduce c-RCP's positional chain model (chain_group/chain_position) rather than RequestLedger's graph-based cascade -- this store isn't a RequestLedger and has no graph; matches c-RCP's own layering (caller assigns position after seeing a whole frame's member layout). No content bugs found versus c-RCP; the logic mapped cleanly onto rcp::request's existing primitives. ## Deferred mock.c-level integration (dispatch_frame, apply_cancellation, response-shape wiring, stream-wide overflow broadcast) -- explicitly scoped to the later mock.hpp batch, which lands last in Phase 4. Integration points this needs are documented in server.hpp's header comment (admit_with_ack per inbound frame, select_due/complete per tick, notify_trigger + gptp_trigger_evaluate on gPTP lock changes, chain_predecessor_done, set_admission_suspended around SleepCMD drain, watchdog_purge/cancel_* from watchdog/cancellation handling). ## Verification New tests/test_server.cpp: 47 TEST_CASEs, 260 assertions. Full tree rebuilds clean (0 errors, 0 warnings), 57/57 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…P-ID map, optional subsystems (#143) * rewrite(phase4): port regmap batch A from c-RCP — general map, EP0, generic/functional split Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 4 batch A of a two-batch regmap port (batch B — HW pin map, named-signal index, request-stream/response-queue config, EP-ID map, optional-subsystem sections — is a separate later PR). c-RCP's regmap.h/.c (3549+2662 = 6211 lines) is regmap's own content source of truth; cpp-RCP's prior regmap.hpp (534 lines) was a stub with real content gaps, matching ROADMAP.md's own flagged '~7x more complete in c-RCP' finding that originally scoped this phase. ## Ported (c-RCP file:line -> cpp-RCP) - EP0/root-client model (regmap.h:358-371, regmap.c:12-15) -> regmap::is_ep0(), kEp0. - svr_implemented_options (REQ-RMAP-030, regmap.h:384-413): the 5 real bits (kOptCompoundWait/Trigger/Chained/TimeSync/EnhCancel); kept legacy kOptConditionalRequests/kOptSafetyRequests/kOptFragmentation (still referenced by request.hpp/e2e.hpp) with a comment explaining they have no TC18 basis per REQ-RMAP-004..008's retirement. - General register map (regmap.h:416-1141, regmap.c:41-288): new GeneralMap struct (~40 fields, REQ-RMAP-023/026-039), render(), encode_read_response()/decode_read_response()/decode_write_request() (Table 20 wire codec), GeneralMapErrc. - Root-client/writer_ctx (regmap.h:1152-1211, regmap.c:1684-1741): EpClient, writer_ctx() deriving lifecycle::WriterCtx. - SvrEpCfg (TC18 Sec13.7.1.2, regmap.h:1211-1303): svr_discovery_timeout, svr_ep_status. - EndpointGenericConfig (regmap.h:1310-1608): real fields (ep_type/ep_used/ep_delay_time/ep_req_storage_size/ep_description/ ep_tx_buffer_size/ep_rx_buffer_size), ep_generic_cfg:: namespace with boundary conversions (issue #311 batches 2-4), render(), apply_reconfig() (per-field, matching c-RCP's documented rationale), EpGenericCfgReconfigErrc. ## Deliberate deferrals (marked TODO(phase4-batch-b) in-file) - writer_ctx()'s via_valid_stream_association always returns false (fail-closed) until batch B's EP-ID/byte_bus_id association table lands (needs a request_stream_index field EpIdMappingEntry doesn't have yet). - HW pin map, request-stream-cfg, response-queue-cfg, EP-ID map, optional-subsystem config, ep_functional_cfg_t: untouched, batch B. ## Cross-cutting note c-RCP's ep_generic_cfg_t has no per-role CRC fields (that's ep_functional_cfg_t's, batch B); cpp-RCP's pre-existing EndpointGenericConfig already had three (ep_req/ack/response_crc_enable) depended on by e2e.hpp/test_e2e.cpp -- kept, marked in-file as pre-existing/non-c-RCP content rather than silently dropped or relocated (relocation needs ep_functional_cfg_t, batch B's scope). ## Call sites mock.hpp: RegisterMap::endpoint_count -> general.svr_ep_count, magic -> general.magic (2 lines). test_mock.cpp: matching 4-line fix. No other files touched. ## Verification test_regmap.cpp: 558 -> 1105 lines, 28 -> 57 TEST_CASEs (327 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Heavy regmap dependents (test_e2e, test_watchdog, test_request, test_discovery, test_mock, test_gpio, test_adc) spot-checked with unchanged assertion counts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * rewrite(phase4): port regmap batch B from c-RCP — HW pins, streams, EP-ID map, optional subsystems Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 4 batch B of the two-batch regmap port; batch A (general map, EP0, generic/functional config split, merged as #141) is the prerequisite this builds on. ## Ported (c-RCP regmap.h/.c -> cpp-RCP regmap.hpp) - EpFunctionalCfg (regmap.h:1614-1627) -- content-modeling only, see reconciliation below. - HW pin mapping: HwPinMapEntry (regmap.h:1685-1697, replaces the old pin_id/function placeholder -- zero consumers existed for it anywhere in the tree), hw_pin::k* bit constants (Table 22), hw_pin_map::render/apply_reconfig, HwPinMapReconfigErrc (REQ-RMAP-040/041). - NamedSignal enum + named_signal_string()/named_signal_ep_signal_nr() (regmap.h:1798-1906). - RequestStreamConfig extended (not replaced -- rx_wd_timeout_interval etc. kept verbatim since e2e.hpp/watchdog.hpp/sim.hpp and their tests depend on those exact names) with rx_secure_channel_index/ rx_ack_stream_index/rx_resp_stream_index(default 1)/ rx_stream_max_request_size; request_stream_cfg:: wd_timeout_ms_to_ticks/_ticks_to_ms, render/apply_reconfig (TC18 0.5.1_RC5 issue #458 4-bit reconciliation, AND-not-OR coupling), resolve_index. - ResponseQueueConfig replaced (old shape had zero real consumers; comments in respqueue.hpp/deadline.hpp already referenced fields that never existed) with c-RCP's real per-queue row + response_queue_stream_id(), render/apply_reconfig. - EpIdMappingEntry extended with request_stream_index/crc_required appended last (preserves existing 2-element brace-inits in mock.hpp/tests) + ep_id_map::{is_ascending, effective_count, row_init_default, render, apply_reconfig, has_single_client_per_ep, shared_bus_homogeneous, ep_type_has_fixed_ep_id, byte_bus_ids_for_stream, is_valid_association}. - OptionalSubsystemCfg + optional_subsystem_cfg::apply_reconfig (REQ-RMAP-039), wired into RegisterMap as 4 new fields. ## Batch-A TODOs closed - writer_ctx()'s via_valid_stream_association now calls the real ep_id_map::is_valid_association() instead of the fail-closed stub. - EndpointGenericConfig's 3 pre-existing CRC fields: confirmed as a deliberate, distinct mechanism (not c-RCP's ep_req_crc_enable) -- every concrete endpoint type from this project's own Phase 3 (gpio.hpp, adc.hpp, etc.) already independently ported c-RCP's real functional-cfg fields inline into its own struct, so EpFunctionalCfg is content-modeling only, matching SvrEpCfg's existing disposition. ## Deliberately deferred (documented in-file) c-RCP's EP0 address-routed dispatcher (regmap.h:3072-3549, ~500 lines combining every sub-table with lifecycle authorization + sequencer ownership) and the sequencer-table wire codec -- neither is named in batch B's scope; only their content/mechanism pieces were ported. No bugs found versus c-RCP during the port (render/apply_reconfig logic, including the AND-not-OR bit coupling and saturation rules, reproduced faithfully); one self-contradictory assertion in this batch's own new test was caught and fixed before it reached the build. ## Verification regmap.hpp: 1328 -> 2654 lines. test_regmap.cpp: 1105 -> 1904 lines, 59 -> 120 TEST_CASEs (332 -> 2433 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --------- Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…idation gaps (#144) * rewrite(phase4): port regmap batch A from c-RCP — general map, EP0, generic/functional split Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 4 batch A of a two-batch regmap port (batch B — HW pin map, named-signal index, request-stream/response-queue config, EP-ID map, optional-subsystem sections — is a separate later PR). c-RCP's regmap.h/.c (3549+2662 = 6211 lines) is regmap's own content source of truth; cpp-RCP's prior regmap.hpp (534 lines) was a stub with real content gaps, matching ROADMAP.md's own flagged '~7x more complete in c-RCP' finding that originally scoped this phase. ## Ported (c-RCP file:line -> cpp-RCP) - EP0/root-client model (regmap.h:358-371, regmap.c:12-15) -> regmap::is_ep0(), kEp0. - svr_implemented_options (REQ-RMAP-030, regmap.h:384-413): the 5 real bits (kOptCompoundWait/Trigger/Chained/TimeSync/EnhCancel); kept legacy kOptConditionalRequests/kOptSafetyRequests/kOptFragmentation (still referenced by request.hpp/e2e.hpp) with a comment explaining they have no TC18 basis per REQ-RMAP-004..008's retirement. - General register map (regmap.h:416-1141, regmap.c:41-288): new GeneralMap struct (~40 fields, REQ-RMAP-023/026-039), render(), encode_read_response()/decode_read_response()/decode_write_request() (Table 20 wire codec), GeneralMapErrc. - Root-client/writer_ctx (regmap.h:1152-1211, regmap.c:1684-1741): EpClient, writer_ctx() deriving lifecycle::WriterCtx. - SvrEpCfg (TC18 Sec13.7.1.2, regmap.h:1211-1303): svr_discovery_timeout, svr_ep_status. - EndpointGenericConfig (regmap.h:1310-1608): real fields (ep_type/ep_used/ep_delay_time/ep_req_storage_size/ep_description/ ep_tx_buffer_size/ep_rx_buffer_size), ep_generic_cfg:: namespace with boundary conversions (issue #311 batches 2-4), render(), apply_reconfig() (per-field, matching c-RCP's documented rationale), EpGenericCfgReconfigErrc. ## Deliberate deferrals (marked TODO(phase4-batch-b) in-file) - writer_ctx()'s via_valid_stream_association always returns false (fail-closed) until batch B's EP-ID/byte_bus_id association table lands (needs a request_stream_index field EpIdMappingEntry doesn't have yet). - HW pin map, request-stream-cfg, response-queue-cfg, EP-ID map, optional-subsystem config, ep_functional_cfg_t: untouched, batch B. ## Cross-cutting note c-RCP's ep_generic_cfg_t has no per-role CRC fields (that's ep_functional_cfg_t's, batch B); cpp-RCP's pre-existing EndpointGenericConfig already had three (ep_req/ack/response_crc_enable) depended on by e2e.hpp/test_e2e.cpp -- kept, marked in-file as pre-existing/non-c-RCP content rather than silently dropped or relocated (relocation needs ep_functional_cfg_t, batch B's scope). ## Call sites mock.hpp: RegisterMap::endpoint_count -> general.svr_ep_count, magic -> general.magic (2 lines). test_mock.cpp: matching 4-line fix. No other files touched. ## Verification test_regmap.cpp: 558 -> 1105 lines, 28 -> 57 TEST_CASEs (327 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Heavy regmap dependents (test_e2e, test_watchdog, test_request, test_discovery, test_mock, test_gpio, test_adc) spot-checked with unchanged assertion counts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * rewrite(phase4): port regmap batch B from c-RCP — HW pins, streams, EP-ID map, optional subsystems Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 4 batch B of the two-batch regmap port; batch A (general map, EP0, generic/functional config split, merged as #141) is the prerequisite this builds on. ## Ported (c-RCP regmap.h/.c -> cpp-RCP regmap.hpp) - EpFunctionalCfg (regmap.h:1614-1627) -- content-modeling only, see reconciliation below. - HW pin mapping: HwPinMapEntry (regmap.h:1685-1697, replaces the old pin_id/function placeholder -- zero consumers existed for it anywhere in the tree), hw_pin::k* bit constants (Table 22), hw_pin_map::render/apply_reconfig, HwPinMapReconfigErrc (REQ-RMAP-040/041). - NamedSignal enum + named_signal_string()/named_signal_ep_signal_nr() (regmap.h:1798-1906). - RequestStreamConfig extended (not replaced -- rx_wd_timeout_interval etc. kept verbatim since e2e.hpp/watchdog.hpp/sim.hpp and their tests depend on those exact names) with rx_secure_channel_index/ rx_ack_stream_index/rx_resp_stream_index(default 1)/ rx_stream_max_request_size; request_stream_cfg:: wd_timeout_ms_to_ticks/_ticks_to_ms, render/apply_reconfig (TC18 0.5.1_RC5 issue #458 4-bit reconciliation, AND-not-OR coupling), resolve_index. - ResponseQueueConfig replaced (old shape had zero real consumers; comments in respqueue.hpp/deadline.hpp already referenced fields that never existed) with c-RCP's real per-queue row + response_queue_stream_id(), render/apply_reconfig. - EpIdMappingEntry extended with request_stream_index/crc_required appended last (preserves existing 2-element brace-inits in mock.hpp/tests) + ep_id_map::{is_ascending, effective_count, row_init_default, render, apply_reconfig, has_single_client_per_ep, shared_bus_homogeneous, ep_type_has_fixed_ep_id, byte_bus_ids_for_stream, is_valid_association}. - OptionalSubsystemCfg + optional_subsystem_cfg::apply_reconfig (REQ-RMAP-039), wired into RegisterMap as 4 new fields. ## Batch-A TODOs closed - writer_ctx()'s via_valid_stream_association now calls the real ep_id_map::is_valid_association() instead of the fail-closed stub. - EndpointGenericConfig's 3 pre-existing CRC fields: confirmed as a deliberate, distinct mechanism (not c-RCP's ep_req_crc_enable) -- every concrete endpoint type from this project's own Phase 3 (gpio.hpp, adc.hpp, etc.) already independently ported c-RCP's real functional-cfg fields inline into its own struct, so EpFunctionalCfg is content-modeling only, matching SvrEpCfg's existing disposition. ## Deliberately deferred (documented in-file) c-RCP's EP0 address-routed dispatcher (regmap.h:3072-3549, ~500 lines combining every sub-table with lifecycle authorization + sequencer ownership) and the sequencer-table wire codec -- neither is named in batch B's scope; only their content/mechanism pieces were ported. No bugs found versus c-RCP during the port (render/apply_reconfig logic, including the AND-not-OR bit coupling and saturation rules, reproduced faithfully); one self-contradictory assertion in this batch's own new test was caught and fixed before it reached the build. ## Verification regmap.hpp: 1328 -> 2654 lines. test_regmap.cpp: 1105 -> 1904 lines, 59 -> 120 TEST_CASEs (332 -> 2433 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * rewrite(phase4): port discovery from c-RCP, fix claim-release and validation gaps Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Closes the gap on discovery.hpp, which the Phase 4 scoping pass found already about 1/3 of the way there (core request/response and the DiscoveryClaim state machine already worked). ## Kept as-is (already correct) kDiscoveryByteBusId/kDiscoveryRegisterAddress/kDiscoveryDefaultReadSize, make_discovery_request/encode_discovery_request, should_answer_discovery, and the core DiscoveryClaim claim/timeout state machine (ClaimOutcome, on_discovery_request, may_configure, has_active_claim/current_holder) -- all already matched c-RCP's semantics. ## Ported (c-RCP src/discovery.c/include/rcp/discovery.h) - should_drop_discovery() (c-RCP:56-59, REQ-DISC-001): generalized the NTSCF-only check to any non-NTSCF subtype, not just TSCF. - Shared detail::decode_common_frame() (c-RCP:68-102) for request/response/fragment decode -- see bug fix below. - DiscoveryResult, make_discovery_response, encode_discovery_response, decode_discovery_response (c-RCP:174-245, REQ-DISC-009..014), 14-octet general slice with the documented svr_version-is-32-bit-not-16 layout, pinned by an octet-address test. - Fragmented response path (c-RCP:250-378, REQ-DISC-025..028): discovery_response_fragment_count, encode/decode fragment, decode_discovery_reassembled_response, built on rcp::fragment following uart.hpp's existing wiring idiom (std::vector-returning, not c-RCP's fixed 255-entry stack array -- this codebase already heap-allocates on every other wire-encode path, so that particular ASIL-D optimization doesn't apply here). - DiscoveryCache (c-RCP:440-503, REQ-DISC-023/030): thin client-side convenience, std::vector-backed by design (not a protocol state machine, no fixed-capacity requirement). - release() on DiscoveryClaim (REQ-DISC-022, was missing entirely). - Expanded DiscoveryErrc from 1 to 5 values (short_frame, tscf_headed_request_dropped, bad_msg_type, wrong_bus, wrong_op), numbered to mirror c-RCP's rcp_discovery_errc_t 1:1. ## Bugs found and fixed 1. decode_discovery_request never validated byte_bus_id/op at all (REQ-DISC-006/007 untested and unenforced) -- fixed via the shared decode_common_frame. 2. DiscoveryClaim::on_configuration_request() previously *released* the claim on the first config write (holder_.reset()), contradicting c-RCP's rcp_discovery_claim_note_config_write() (c-RCP src/discovery.c:423-430), which *extends* the deadline and keeps the claim held so a claimant can issue multiple writes over time. Fixed to refresh claimed_at_, matching c-RCP's own test_claim_config_write_refreshes_deadline_for_claimant, now ported. ## regmap.hpp interplay Confirmed c-RCP's discovery.c never calls rcp_regmap_ep_id_map_is_valid_association() -- that's lifecycle.hpp's concern (via_valid_stream_association), not discovery's. Documented in-file rather than wiring it in. ## Deferred Wiring any of this into mock.hpp's dispatch -- out of scope, part of the later mock.hpp batch that closes out Phase 4. ## Verification discovery.hpp: 270 -> 818 lines. test_discovery.cpp: 230 -> 812 lines, ~20 -> 49 TEST_CASEs (686 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 57/57 ctest suites pass (100%) -- independently re-verified from a fresh build against the fully-merged regmap base (both batches), not just the porting agent's own report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --------- Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ld, add test_adapt.cpp (#145) * rewrite(phase4): port regmap batch A from c-RCP — general map, EP0, generic/functional split Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 4 batch A of a two-batch regmap port (batch B — HW pin map, named-signal index, request-stream/response-queue config, EP-ID map, optional-subsystem sections — is a separate later PR). c-RCP's regmap.h/.c (3549+2662 = 6211 lines) is regmap's own content source of truth; cpp-RCP's prior regmap.hpp (534 lines) was a stub with real content gaps, matching ROADMAP.md's own flagged '~7x more complete in c-RCP' finding that originally scoped this phase. ## Ported (c-RCP file:line -> cpp-RCP) - EP0/root-client model (regmap.h:358-371, regmap.c:12-15) -> regmap::is_ep0(), kEp0. - svr_implemented_options (REQ-RMAP-030, regmap.h:384-413): the 5 real bits (kOptCompoundWait/Trigger/Chained/TimeSync/EnhCancel); kept legacy kOptConditionalRequests/kOptSafetyRequests/kOptFragmentation (still referenced by request.hpp/e2e.hpp) with a comment explaining they have no TC18 basis per REQ-RMAP-004..008's retirement. - General register map (regmap.h:416-1141, regmap.c:41-288): new GeneralMap struct (~40 fields, REQ-RMAP-023/026-039), render(), encode_read_response()/decode_read_response()/decode_write_request() (Table 20 wire codec), GeneralMapErrc. - Root-client/writer_ctx (regmap.h:1152-1211, regmap.c:1684-1741): EpClient, writer_ctx() deriving lifecycle::WriterCtx. - SvrEpCfg (TC18 Sec13.7.1.2, regmap.h:1211-1303): svr_discovery_timeout, svr_ep_status. - EndpointGenericConfig (regmap.h:1310-1608): real fields (ep_type/ep_used/ep_delay_time/ep_req_storage_size/ep_description/ ep_tx_buffer_size/ep_rx_buffer_size), ep_generic_cfg:: namespace with boundary conversions (issue #311 batches 2-4), render(), apply_reconfig() (per-field, matching c-RCP's documented rationale), EpGenericCfgReconfigErrc. ## Deliberate deferrals (marked TODO(phase4-batch-b) in-file) - writer_ctx()'s via_valid_stream_association always returns false (fail-closed) until batch B's EP-ID/byte_bus_id association table lands (needs a request_stream_index field EpIdMappingEntry doesn't have yet). - HW pin map, request-stream-cfg, response-queue-cfg, EP-ID map, optional-subsystem config, ep_functional_cfg_t: untouched, batch B. ## Cross-cutting note c-RCP's ep_generic_cfg_t has no per-role CRC fields (that's ep_functional_cfg_t's, batch B); cpp-RCP's pre-existing EndpointGenericConfig already had three (ep_req/ack/response_crc_enable) depended on by e2e.hpp/test_e2e.cpp -- kept, marked in-file as pre-existing/non-c-RCP content rather than silently dropped or relocated (relocation needs ep_functional_cfg_t, batch B's scope). ## Call sites mock.hpp: RegisterMap::endpoint_count -> general.svr_ep_count, magic -> general.magic (2 lines). test_mock.cpp: matching 4-line fix. No other files touched. ## Verification test_regmap.cpp: 558 -> 1105 lines, 28 -> 57 TEST_CASEs (327 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Heavy regmap dependents (test_e2e, test_watchdog, test_request, test_discovery, test_mock, test_gpio, test_adc) spot-checked with unchanged assertion counts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * rewrite(phase4): port regmap batch B from c-RCP — HW pins, streams, EP-ID map, optional subsystems Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 4 batch B of the two-batch regmap port; batch A (general map, EP0, generic/functional config split, merged as #141) is the prerequisite this builds on. ## Ported (c-RCP regmap.h/.c -> cpp-RCP regmap.hpp) - EpFunctionalCfg (regmap.h:1614-1627) -- content-modeling only, see reconciliation below. - HW pin mapping: HwPinMapEntry (regmap.h:1685-1697, replaces the old pin_id/function placeholder -- zero consumers existed for it anywhere in the tree), hw_pin::k* bit constants (Table 22), hw_pin_map::render/apply_reconfig, HwPinMapReconfigErrc (REQ-RMAP-040/041). - NamedSignal enum + named_signal_string()/named_signal_ep_signal_nr() (regmap.h:1798-1906). - RequestStreamConfig extended (not replaced -- rx_wd_timeout_interval etc. kept verbatim since e2e.hpp/watchdog.hpp/sim.hpp and their tests depend on those exact names) with rx_secure_channel_index/ rx_ack_stream_index/rx_resp_stream_index(default 1)/ rx_stream_max_request_size; request_stream_cfg:: wd_timeout_ms_to_ticks/_ticks_to_ms, render/apply_reconfig (TC18 0.5.1_RC5 issue #458 4-bit reconciliation, AND-not-OR coupling), resolve_index. - ResponseQueueConfig replaced (old shape had zero real consumers; comments in respqueue.hpp/deadline.hpp already referenced fields that never existed) with c-RCP's real per-queue row + response_queue_stream_id(), render/apply_reconfig. - EpIdMappingEntry extended with request_stream_index/crc_required appended last (preserves existing 2-element brace-inits in mock.hpp/tests) + ep_id_map::{is_ascending, effective_count, row_init_default, render, apply_reconfig, has_single_client_per_ep, shared_bus_homogeneous, ep_type_has_fixed_ep_id, byte_bus_ids_for_stream, is_valid_association}. - OptionalSubsystemCfg + optional_subsystem_cfg::apply_reconfig (REQ-RMAP-039), wired into RegisterMap as 4 new fields. ## Batch-A TODOs closed - writer_ctx()'s via_valid_stream_association now calls the real ep_id_map::is_valid_association() instead of the fail-closed stub. - EndpointGenericConfig's 3 pre-existing CRC fields: confirmed as a deliberate, distinct mechanism (not c-RCP's ep_req_crc_enable) -- every concrete endpoint type from this project's own Phase 3 (gpio.hpp, adc.hpp, etc.) already independently ported c-RCP's real functional-cfg fields inline into its own struct, so EpFunctionalCfg is content-modeling only, matching SvrEpCfg's existing disposition. ## Deliberately deferred (documented in-file) c-RCP's EP0 address-routed dispatcher (regmap.h:3072-3549, ~500 lines combining every sub-table with lifecycle authorization + sequencer ownership) and the sequencer-table wire codec -- neither is named in batch B's scope; only their content/mechanism pieces were ported. No bugs found versus c-RCP during the port (render/apply_reconfig logic, including the AND-not-OR bit coupling and saturation rules, reproduced faithfully); one self-contradictory assertion in this batch's own new test was caught and fixed before it reached the build. ## Verification regmap.hpp: 1328 -> 2654 lines. test_regmap.cpp: 1105 -> 1904 lines, 59 -> 120 TEST_CASEs (332 -> 2433 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 56/56 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * rewrite(phase4): port discovery from c-RCP, fix claim-release and validation gaps Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Closes the gap on discovery.hpp, which the Phase 4 scoping pass found already about 1/3 of the way there (core request/response and the DiscoveryClaim state machine already worked). ## Kept as-is (already correct) kDiscoveryByteBusId/kDiscoveryRegisterAddress/kDiscoveryDefaultReadSize, make_discovery_request/encode_discovery_request, should_answer_discovery, and the core DiscoveryClaim claim/timeout state machine (ClaimOutcome, on_discovery_request, may_configure, has_active_claim/current_holder) -- all already matched c-RCP's semantics. ## Ported (c-RCP src/discovery.c/include/rcp/discovery.h) - should_drop_discovery() (c-RCP:56-59, REQ-DISC-001): generalized the NTSCF-only check to any non-NTSCF subtype, not just TSCF. - Shared detail::decode_common_frame() (c-RCP:68-102) for request/response/fragment decode -- see bug fix below. - DiscoveryResult, make_discovery_response, encode_discovery_response, decode_discovery_response (c-RCP:174-245, REQ-DISC-009..014), 14-octet general slice with the documented svr_version-is-32-bit-not-16 layout, pinned by an octet-address test. - Fragmented response path (c-RCP:250-378, REQ-DISC-025..028): discovery_response_fragment_count, encode/decode fragment, decode_discovery_reassembled_response, built on rcp::fragment following uart.hpp's existing wiring idiom (std::vector-returning, not c-RCP's fixed 255-entry stack array -- this codebase already heap-allocates on every other wire-encode path, so that particular ASIL-D optimization doesn't apply here). - DiscoveryCache (c-RCP:440-503, REQ-DISC-023/030): thin client-side convenience, std::vector-backed by design (not a protocol state machine, no fixed-capacity requirement). - release() on DiscoveryClaim (REQ-DISC-022, was missing entirely). - Expanded DiscoveryErrc from 1 to 5 values (short_frame, tscf_headed_request_dropped, bad_msg_type, wrong_bus, wrong_op), numbered to mirror c-RCP's rcp_discovery_errc_t 1:1. ## Bugs found and fixed 1. decode_discovery_request never validated byte_bus_id/op at all (REQ-DISC-006/007 untested and unenforced) -- fixed via the shared decode_common_frame. 2. DiscoveryClaim::on_configuration_request() previously *released* the claim on the first config write (holder_.reset()), contradicting c-RCP's rcp_discovery_claim_note_config_write() (c-RCP src/discovery.c:423-430), which *extends* the deadline and keeps the claim held so a claimant can issue multiple writes over time. Fixed to refresh claimed_at_, matching c-RCP's own test_claim_config_write_refreshes_deadline_for_claimant, now ported. ## regmap.hpp interplay Confirmed c-RCP's discovery.c never calls rcp_regmap_ep_id_map_is_valid_association() -- that's lifecycle.hpp's concern (via_valid_stream_association), not discovery's. Documented in-file rather than wiring it in. ## Deferred Wiring any of this into mock.hpp's dispatch -- out of scope, part of the later mock.hpp batch that closes out Phase 4. ## Verification discovery.hpp: 270 -> 818 lines. test_discovery.cpp: 230 -> 812 lines, ~20 -> 49 TEST_CASEs (686 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 57/57 ctest suites pass (100%) -- independently re-verified from a fresh build against the fully-merged regmap base (both batches), not just the porting agent's own report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * rewrite(phase4): fix adapt.hpp's missing read_size_or_segment_num field, add test_adapt.cpp Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Closes out adapt.hpp, the last piece before mock.hpp finishes Phase 4. ## The scoping claim did NOT fully hold up A prior scoping pass characterized adapt.hpp's simpler generic RequestFn/op+evt_op design as behaviorally equivalent to c-RCP's richer per-op field-table adapt.c/.h, with the only real gap being missing test coverage. That was a sizing estimate, not a line-by-line verification -- this batch actually did that comparison and found a genuine, load-bearing bug underneath the otherwise-correct shape. ## Bug found and fixed (real, not cosmetic) message_to_request()/response_to_message() never threaded the ACF header's own 12-bit read_size_or_segment_num field through at all, silently leaving it 0 for every relay::Message. This field is what uart.hpp/spi.hpp/adc.hpp/i2c.hpp (and mock.hpp's dispatch_uart()) directly depend on -- confirmed against their own doc comments and dispatch code. For I2C it is the ONLY thing selecting read vs. write direction (0 = write, nonzero = read): before this fix, an I2C read could never be requested through Adapt() at all, and a UART read would always drain 0 bytes regardless of what was actually buffered. c-RCP's own src/adapt.c threads the identical wire field through this same layer via 5 separate per-op table rows (rcp.uart.read_size / rcp.spi.read_size / rcp.adc.read_size / rcp.i2c.read_size / rcp.iseled.read_size, src/adapt.c ~246-359). Fixed here with one generic read_size_from_meta() (rcp.read_size, decimal, 0-4095 matching the ACF header's own field width) wired into both directions -- one key suffices since it's the same wire field regardless of endpoint type, consistent with how rcp.evt_op already covers GPIO/PWM_OUT/SPI generically. REQ-RELAY-005's .fusa-reqs.json text updated to mention read_size_or_segment_num. Purely additive (new optional meta key, old default-0 behavior preserved when absent) -- no call site needed updating. Checked redundancy.hpp, faultinject.hpp, cli.hpp (both its direct-flag send_one() path, unaffected, and its NDJSON send_stream() path, which already forwards meta generically and gains read_size support for free) plus their existing tests. ## Flagged, explicitly out of scope CAN's frame_format/arbitration_id and MDIO's clause/prtad/devad/regad have no path through Adapt() today -- a pre-existing, already- documented gap (no CAN wire codec exists yet in this layer; MDIO's address-packing helpers are namespace detail, not public), not an adapt.hpp regression. Inventing a convention here without an established downstream codec would be unfounded. ## Verification New tests/test_adapt.cpp: 508 lines, 29 TEST_CASEs, 67 assertions -- meta parsing (including read_size's new boundary/garbage cases), full message_to_request/response_to_message field mapping, opaque-payload passthrough, RequestFn error propagation, and end-to-end regression tests against mock::Server proving the fix (a UART read via Adapt() now correctly drains exactly rcp.read_size bytes, GPIO-write/SPI- channel evt_op equivalence). adapt.hpp: 228 -> 304 lines. Full tree rebuilds clean (0 errors, 0 warnings), 58/58 ctest suites pass (100%) -- independently re-verified from a fresh build against the merged discovery+regmap+server base, not just the porting agent's own report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --------- Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…146) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 4 batch A of a four-batch mock.hpp port -- the foundation the remaining three batches (B: response classifier + regmap-batch-B/discovery wiring; C: E2E RxSequenceGuard + StreamFaultTracker + RxWatchdog; D: fragmentation/respqueue + AVTPDU frame-level dispatch) build on. A fresh Phase 4 scoping pass found mock.hpp had NO admission, queueing, or scheduling logic at all: server::Endpoint (the just-merged server.hpp) was never referenced anywhere in mock.hpp; dispatch() was a flat byte_bus_id switch, and every dispatch_gpio/dispatch_spi/etc. handler ran synchronously and unconditionally behind only a lifecycle-state gate. Not a case of replacing ad hoc admission -- this adds admission from scratch, matching c-RCP's own mock.c design where every endpoint slot embeds rcp_server_endpoint_t queue. ## Ported (c-RCP src/mock.c -> mock::Server) - src/mock.c:35-233 (slot embedding rcp_server_endpoint_t queue) -> ten new server::Endpoint members, one per operational endpoint type. - src/mock.c:1277-1300 (admission_reject_response_shape) -> mock::admission_reject_response_shape(). - src/mock.c:1361-1423 (finish_admission) + :1549-1551 (admit_with_ack call site) -> mock::Server::admit_and_classify(), built on acf::make_response/acf::encode_error_payload and a new mock::make_acknowledge_rejected_response() (the Ack+err combo acf::make_response() alone can't express). - src/mock.c:1180-1245 (apply_cancellation) -> mock::Server::apply_cancellation() (ClearAll/ClearNonSafestate only; ClearSingle deferred, see below). - src/mock.c:2856-2870,3587-3628,3670-3717,3785-3802 (drain_endpoint, tick, notify_trigger, notify_gptp_lock_state, pending_count, watchdog_purge) -> thin Server passthroughs onto server::Endpoint. - REQ-SRV-015/016, REQ-PWRMODE-028, REQ-ACF-021, REQ-TIMED-012, REQ-CANCEL-012, REQ-SRV-018 citations preserved. ## dispatch_* changes All ten (dispatch_gpio...dispatch_mdio) now call admit_and_classify() immediately after the existing operational_requests_allowed() lifecycle gate; the handler body runs only on AdmitOutcome::ExecuteNow. Since every mock request is encoded ACF_ABB, only ExecuteNow/Queued/Suspended/Rejected are reachable through dispatch() -- Pending/Cancellation are implemented for classifier completeness and exercised directly via the new admission() accessor. ## New Server API admission(byte_bus_id), pending_count(), watchdog_purge(), drain_one(), notify_trigger(), notify_gptp_lock_state(), tick(). tick()/drain_one() deliberately don't decode+execute the returned frame -- this mock has no generic byte-level handler (each type has its own decoded-struct dispatch path) -- documented as the caller's job. ## Behavioral contract change (verified backward-compatible) dispatch() can now return with no wire response at all (out_resp.rsp == false) for admission outcomes TC18 sends nothing for -- signaled via new mock::DispatchErrc{queued,pending,cancelled,suspended}. Since every server::Endpoint defaults to enabled/not-suspended, all 8 external dependents (sim.hpp, cli.hpp, test_capi/test_relay/ test_redundancy/test_faultinject/bench_mock/command_latency_test) get ExecuteNow unconditionally and needed zero changes -- confirmed by full ctest passing before test_mock.cpp was touched. ## Deferred (TODO(phase4-batch-d) in-file) apply_cancellation()'s ClearSingle case is a documented no-op -- REQ-CANCEL-012's chain-cascade needs a decoded target transaction_num from a real GBB frame, unreachable until batch D's dispatch_frame(). ## Verification All 49 pre-existing test_mock.cpp cases pass unmodified (no admission-aware setup was actually needed -- verified empirically, not assumed). 11 new cases added: queued (no ack)/queued (with ack), config-write bypassing a disabled queue (REQ-SRV-015), admission_suspended (REQ-PWRMODE-028), REQ-ACF-021 response-as-request rejection (both ack shapes), drain_one() round-trip, pending_count()/ watchdog_purge() against a directly-admitted Triggered request, notify_trigger()/notify_gptp_lock_state() edge detection, two tick() cases (TSCF gate due / fail-closed while unlocked). mock.hpp: 917 -> 1387 lines. test_mock.cpp: 1006 -> 1285 lines, 49 -> 60 TEST_CASEs (503 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 58/58 ctest suites pass (100%) -- independently re-verified from a fresh build against the merged regmap+server+discovery+adapt base, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…egmap/discovery wiring (#147) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 4 batch B of a four-batch mock.hpp port, built on batch A's server::Endpoint admission wiring. ## Ported (c-RCP src/mock.c -> mock::Server) - Table 24 response suppression (src/mock.c:1716-1760, REQ-RMAP-048/049): new private static Server::suppress_response_per_stream_cfg(), resolving stream_id via regmap::request_stream_cfg::resolve_index() and zeroing out_resp/out_resp_payload when the resolved stream's rx_ack_stream_index (Acknowledge-classified responses) or rx_resp_stream_index (everything else) is 0. - Storage: Server::set_request_stream_cfg()/request_stream_cfg() and set_ep_id_map()/ep_id_map(), mirroring c-RCP's rcp_mock_server_set_request_stream_cfg()/_set_ep_id_map() (src/mock.c:444-460/565-580), bounds-checked against regmap::request_stream_cfg::kMaxEntries/regmap::ep_id_map::kMaxEntries, syncing svr_request_stream_cfg_capacity/svr_ep_bytebus_id_map_capacity (REQ-RMAP-034/037). - Discovery wiring (mock.h:501-529, src/mock.c:663-687, REQ-RMAP-066): discovery::DiscoveryClaim member, discovery_claim() getter, set_discovery_timeout_us() keeping it and regs_.svr_ep_cfg.svr_discovery_timeout in sync. Confirmed by direct source read that c-RCP's own mock.c never wires the claim into dispatch, so nothing beyond storage was added. ## dispatch_* changes Each of the ten operational dispatch_*() functions (gpio/spi/i2c/adc/ pwm_in/lin/can/uart/iseled/mdio) renamed to dispatch_*_inner() (unchanged batch-A body), with a new thin dispatch_*() wrapper calling _inner then suppress_response_per_stream_cfg() -- mirroring c-RCP's dispatch_plain()/dispatch_plain_inner() split, generalized per endpoint type. dispatch() gained a trailing, defaulted avtp::StreamId stream_id parameter threaded to all ten; the default resolves to "no stream configured," so no pre-existing call site's behavior changes. dispatch_ep0() left untouched (not one of the ten operational endpoints). ## Bug found and fixed make_initial_register_map()'s ten default ep_id_mapping rows all left request_stream_index at its struct default (0) -- TC18's own end-of-table sentinel -- which would make regmap::ep_id_map::effective_count() read the whole populated table as zero rows. Fixed to 1 (matching row_init_default()'s own power-on convention); also fixed svr_ep_bytebus_id_map_capacity to sync to 10. Neither was exercised by existing tests, so this is inert today but fixes future batch-C consumers (broadcast_safe_state, sequencer access control). ## Small addition to already-merged discovery.hpp Added DiscoveryClaim::set_timeout() -- batch B's set_discovery_timeout_us() needed a way to re-derive the claim's window from a new register value without resetting holder_/ claimed_at_ (c-RCP's own documented contract), and no such mutator existed. ## Verification Existing 60 test cases required no changes (default request_streams table is empty, so suppression is a no-op unless a test opts in). 11 new TEST_CASEs added: ep_id_mapping bug-fix assertions, bounds-check tests, 5 Table-24 suppression tests ported from c-RCP's tests/test_mock.c:1061-1204, one sweep across all ten endpoint types, 3 discovery-claim tests ported from tests/test_mock.c:1917-1980 (adapted to DiscoveryClaim's steady_clock-based API). include/rcp/mock.hpp: 1387 -> 1759 lines. tests/test_mock.cpp: 1285 -> 1561 lines, 60 -> 71 TEST_CASEs (583 assertions). include/rcp/ discovery.hpp: 818 -> 837 lines. Full tree rebuilds clean (0 errors, 0 warnings), 58/58 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…Tracker, RxWatchdog (#148) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 4 batch C of a four-batch mock.hpp port, built on batch A (admission) and batch B (response suppression + regmap/discovery wiring). This closes the exact gap flagged during Phase 2 (PR #134, e2e.hpp's own header comment): RxSequenceGuard, StreamFaultTracker, and RxWatchdog were all fully implemented in e2e.hpp but never called from any dispatch path anywhere in cpp-RCP -- e2e.hpp's own comment explicitly deferred this wiring to "Phase 4 (server/dispatch) scope, matching c-RCP's own mock.c wiring (frame_seq_gate_admits(), dispatch_frame_e2e())." That deferred work lands here. ## Ported (c-RCP src/mock.c -> Server) - rcp_mock_server_dispatch_e2e() (src/mock.c:1892-2038, REQ-E2E-021/ 031/041, REQ-WDG-010) -> new Server::dispatch_e2e(). - frame_seq_gate_admits() (src/mock.c:3038-3063, REQ-E2E-028/029) -> private Server::seq_gate_admits(), folded into single-member dispatch per this batch's scope (multi-member AVTPDU frame splitting is batch D's job; this batch's guard logic is built at the single-member level so batch D can reuse the same guard instance). - rcp_mock_server_check_watchdog() (src/mock.c:991-1021, REQ-E2E-046) -> public Server::check_watchdog_overflow(). - rcp_mock_server_stream_status_rx_blocked() (src/mock.c:981-989) -> Server::stream_rx_blocked(). - Watchdog-kick call sites (REQ-WDG-010) -> unconditional rx_watchdog_kick() at the top of dispatch_e2e(). ## Shape of the new E2E dispatch surface One public Server::dispatch_e2e() entry point, not ten dispatch_<type>_e2e() siblings. ep_req_crc_enable (TC18 Sec12.7.1) is a per-endpoint, not per-endpoint-type, toggle, and c-RCP's own rcp_mock_server_dispatch_e2e() is likewise a single function parameterized by byte_bus_id, not duplicated per handler -- mirrors dispatch()'s own established single-entry-point convention. All 10 operational endpoint types get E2E support uniformly via regs_.generic_configs[].ep_req_crc_enable (the real register). EP0 gets none -- it has no generic_configs[] row, matching c-RCP's own EP0 exclusion from admission entirely. ## New Server state e2e::StreamFaultTracker stream_fault_tracker_; std::array<e2e::RxSequenceGuard/RxWatchdog/StreamStatus, kMaxEntries=64> keyed by resolved request_streams[] index (mirrors c-RCP's srv->seq_tracker[]/srv->stream_status[]) -- all owned directly, matching mock.hpp's existing convention over c-RCP's pointer/setter style. ## Deliberate scope boundaries (documented in-file, not TODO-stubbed -- none block correctness) - REQ-E2E-045/029's full stream-wide "broadcast safe state to every bound endpoint" actuation is not implemented (no SequencerTable- driving actuator exists in this file yet) -- only the observable StreamStatus latch is. Pre-existing gap class shared with batch A/B's own admission-reject overflow-safestate consequence, not new here. - check_watchdog_overflow()'s purge targets server::Endpoint:: watchdog_purge() (the pending/conditional store) rather than rcp::watchdog::Manager (RequestLedger-keyed, architecturally mismatched to this mock's admission storage). - No TSCF variant of dispatch_e2e (c-RCP's dispatch_e2e_tscf, REQ- TIMED-012/013) -- out of scope since plain dispatch() has no TSCF timing gate either yet. No content bugs found versus c-RCP. One test-fixture-only issue found while writing coverage: GPIO's apply_gpio_write() masks writes by pin direction (default all-input, TC18 Sec13.7.4.3) -- tests needed a configure_gpio_all_outputs() fixture helper before asserting OR-write effects; not a library bug. ## Verification New Server::dispatch_e2e() + 6 new TEST_CASEs covering replay detection, stream-fault short-circuit, and watchdog kick/overflow, integration-tested via mock.hpp's dispatch (not re-testing e2e.hpp's own already-unit-tested internals). include/rcp/mock.hpp: 1759 -> 2162 lines. tests/test_mock.cpp: 1561 -> 1832 lines, 71 -> 77 TEST_CASEs (632 assertions). include/rcp/e2e.hpp: header comment updated to record the closed deferral (primitives unchanged). Full tree rebuilds clean (0 errors, 0 warnings, verified with -Wall -Wextra), 58/58 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…for E2E fragmented dispatch (#149) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. First of two sub-batches (D1/D2) making up Phase 4's fourth and final batch. Built on batches A (admission), B (response suppression + regmap/discovery), and C (single-member E2E dispatch with RxSequenceGuard/StreamFaultTracker/RxWatchdog). fragment.hpp and respqueue.hpp were both fully ported in Phase 1 (fixed-capacity, zero heap allocation, with the c-RCP issue #614/#616 oversized-reassembly error-handling lesson baked in from day one) but neither was ever wired into any dispatch path. This closes that gap. ## Ported (c-RCP src/mock.c:2175-2504, rcp_mock_server_dispatch_e2e_fragment) REQ-E2E-038/039/046, REQ-ISELED-025, REQ-FRAG-*. Confirmed by direct source read that c-RCP's own fragment-dispatch function has no sequence-gate call and no TSCF variant reachable from this rewrite's scope -- mirroring batch C's own established "NTSCF-only, no per-frame seq gate" simplification for dispatch_e2e(), neither dispatch_e2e_fragment_tscf nor a sequence-number gate were ported here; both are legitimately deferred to D2's future frame-level dispatch_frame_e2e(). ## New public surface on mock::Server - dispatch_e2e_fragment(client, stream_id, fragment, out_resp, out_resp_payload) -- feeds fragments into a per-request-stream fragment::Reassembler (keyed by regmap::request_stream_cfg::resolve_index(), same indexing as seq_trackers_/rx_watchdogs_/stream_status_). On completion validates e2e::compute_fragmented_crc() (over the first fragment's header + reassembled payload, deliberately ignoring unwrap()'s own wrong single-frame verdict, matching c-RCP's own comment), then delegates into the existing single-member decode_and_dispatch()/dispatch() path -- no duplicated CRC-unwrap/admission logic. - maybe_fragment_response(...) -- new (c-RCP's own equivalent, rcp_mock_server_dispatch_multi_response(), is a per-endpoint registered-handler pattern this rewrite's hardcoded ten-dispatch_*()-method architecture has no analog for). Slices an oversized genuine response via fragment::plan() and pushes each fragment onto a respqueue::RespQueue keyed by rx_resp_stream_index (1-based, resolved against regs_.response_streams -- multiple request streams can share one response queue). - Accessors: fragment_reassembler(stream_id), resp_queue_for_stream( stream_id) -- nullable pointers mirroring c-RCP's own rcp_mock_server_fragment_reassembler() precedent. - Three new DispatchErrc values: fragment_pending, fragment_rejected (both no-wire-response, mirroring c-RCP's identical dispositions), response_fragmented (real multi-frame response now on the RespQueue). ## Oversized-reassembly error path (c-RCP issue #614/#616) -- verified real, not silent After CRC validation, the reassembled total is checked against the ACF wire ceiling before re-encoding; on overflow, set_error_response() produces a genuine RequestRejected ErrorResponse. Verified by test with a payload under the Reassembler's own capacity but over the ACF wire ceiling, asserting response_kind_of(resp) == ErrorResponse -- distinct from a separate test proving the Reassembler's own internal kErrTooLarge bound produces fragment_rejected with no wire response, matching c-RCP's own RCP_MOCK_DISPATCH_REJECTED precedent. Symmetric coverage on the response side: an unconfigured response-stream also produces a genuine RequestRejected ErrorResponse rather than dropping silently. ## Bug found and fixed acf::decode_acf_abb()/decode_acf_gbb() are deliberately lenient (a documented Phase-1 TODO) and don't trim wire pad octets off the decoded payload -- left as-is this would have corrupted both the reassembled payload and the fragmented-CRC comparison whenever pad != 0. Added a local trim_wire_pad() helper applied to both intermediate- and final-fragment decodes, scoped to this batch rather than touching the shared decoder (out of D1's fragmentation-only scope). ## Build-clean fix std::array<fragment::Reassembler, N>/std::array<respqueue::RespQueue, N> member declarations with a trailing {} triggered a "converting from initializer list would use explicit constructor" warning (both types have explicit sole constructors) -- fixed by dropping the {}. ## Deferred (doc-only TODO update) apply_cancellation()'s ClearSingle TODO relabeled from TODO(phase4-batch-d) to TODO(phase4-batch-d2) now that D is split; D1's own dispatch_e2e_fragment() still routes everything through admit_and_classify(), so AdmitOutcome::Cancellation remains as unreachable as it was after batch A -- no behavior touched. ## Verification 13 new TEST_CASEs: plain-command-mode passthrough, unresolvable-stream fallback, never-fragmented fallback, genuine 2-fragment reassembly, out-of-order rejection + recovery, fragmented-CRC mismatch + stream- fault latch, both oversized paths, three response-fragmentation scenarios (successful multi-entry split with byte-exact reassembly, unconfigured-queue rejection, max_avtpdu_size-driven tighter splitting), accessor nullptr behavior. include/rcp/mock.hpp: 2162 -> 2723 lines. tests/test_mock.cpp: 1832 -> 2349 lines, 77 -> 89 TEST_CASEs (764 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 58/58 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ses out Phase 4 (#150) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Second of two sub-batches (D1/D2) making up Phase 4's fourth and FINAL batch. This completes mock.hpp -- the last module of Phase 4 -- and closes out the server/dispatch layer in full. Built on batches A (admission), B (response suppression + regmap/discovery), C (single-member E2E dispatch), and D1 (E2E fragmented dispatch). ## Ported (c-RCP src/mock.c/src/scheduler.c/src/request.c, REQ-MOCK-019/ 020/029, REQ-E2E-033, REQ-CANCEL-012) - split_frame_members() -- ported from rcp_sched_split_frame_members() (scheduler.c:63-106), a new namespace-scope helper (request.hpp's own header explicitly notes this splitting logic lives nowhere else in the rewrite -- c-RCP keeps it mock.c-local too). - FrameMemberResult + Server::dispatch_frame()/dispatch_frame_e2e() -- ported from rcp_mock_server_dispatch_frame()/_dispatch_frame_e2e() (mock.c:3068-3382). Each returns per-member results (mirrors c-RCP's own result-array shape -- c-RCP does not aggregate members' responses into one outgoing wire frame either, confirmed by source read). - Chain-group/position bookkeeping (REQ-CANCEL-012, ported from rcp_chained_advance/is_chained_member/last_pending_index, mock.c:2950-3231, request.c:673-694) -- via a new shared dispatch_frame_impl() template, using request::should_execute_chained() for the cs-polarity predicate and a new last_pending_index() helper. - apply_cancellation()'s ClearSingle TODO (open since batch A) filled in: decodes request::decode_clear_single() from the raw frame, captures the target's chain_group/chain_position before removing it, cascades via cancel_chain_from(). ## Bug found and fixed (beyond the literal ask) admit_and_classify() always re-encoded ABB from req/payload, silently stripping any genuine GBB conditional/cancellation opcode's semantics -- meaning ClearSingle/Chained frames were never actually reachable before this batch, regardless of how they'd been decoded. Refactored to take the raw frame directly (mechanical edit across its 10 call sites, zero behavior change for them) and added decode_and_dispatch()'s own conditional-opcode-aware routing branch, so dispatch_frame()/dispatch_frame_e2e() -- and every existing dispatch_e2e()/dispatch_e2e_fragment() caller -- can now genuinely admit Chained/Compound/Triggered/Timed/Cancellation GBB members. Also extracted dispatch_e2e_core()/dispatch_e2e_fragment_core() with an apply_seq_gate flag so the frame-level sequence gate runs exactly once per frame (matching the pre-existing doc comment's own stated intent), not once per member. ## Delta from c-RCP (documented in-file) dispatch_frame_e2e() routes every member through dispatch_e2e_fragment_core() rather than plain dispatch_e2e_core() -- c-RCP has no frame-level fragment-aware dispatch at all; this is a safe superset since the fragment-core function falls back identically for non-fragmented members. ## Test-writing findings (documented, not library bugs) split_frame_members() requires each member's declared acf_msg_length*4 to exactly match its real encoded byte count, but acf::encode_acf_abb()/e2e::wrap_framed() never auto-pad payloads to quadlet boundaries (documented as caller-owned in acf.hpp) -- never mattered for single-member dispatch before; all new multi-member test frames use quadlet-aligned payloads with an explanatory comment. Confirmed DispatchErrc::unknown_bus is unreachable in this codebase (acf.hpp's decode is deliberately lenient vs c-RCP's strict decode) -- documented as defensive-only with a test pinning the actual lenient behavior. ## Final Phase-4 sweep Grepped the whole file for TODO(phase4-batch-*) markers: only apply_cancellation()'s ClearSingle stub existed, now resolved. No dangling phase-4 markers remain anywhere in mock.hpp. ## Verification New split_frame_members()/dispatch_frame()/dispatch_frame_e2e() + ClearSingle cascade, tested end-to-end via multi-member AVTPDU frames. include/rcp/mock.hpp: 2723 -> 3413 lines. tests/test_mock.cpp: 2349 -> 2775 lines, test_mock: 101 TEST_CASEs (863 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 58/58 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. ## Phase 4 complete mock.hpp grew from its pre-Phase-4 baseline of 917 lines to 3413 lines across four batches: A added server::Endpoint admission wiring, B added Table 24 response suppression plus regmap/discovery storage, C wired RxSequenceGuard/StreamFaultTracker/RxWatchdog into single-member E2E dispatch (closing a gap flagged since PR #134), D1 wired fragment.hpp/respqueue.hpp for E2E fragmented dispatch (closing a gap open since Phase 1), and this D2 batch added AVTPDU frame-level multi-member splitting/routing/chain-cascade cancellation. Combined with regmap (batches A/B), the new server.hpp, discovery, and adapt -- all merged earlier in Phase 4 -- the server/dispatch layer is now complete with no phase-4-scoped work left deferred. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…l dispatch (#151) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 5 (transport layer), Wave 1. ## The gap udp::Server::Handler was shaped to match mock::Server::dispatch()'s single-already-isolated-message contract, so Server::serve() decoded each datagram into individual ACF messages and called Handler once per message. A caller wiring that Handler straight to mock::Server:: dispatch() -- the obvious, natural thing to do -- never reached mock.hpp's Phase 4 batch D2 frame-level entry points (dispatch_frame()/ dispatch_frame_e2e()), silently losing Table 24 response suppression, conditional/cancellation-opcode routing, and E2E CRC verification/ fragment reassembly for every request that goes over UDP. Note: c-RCP's current src/udp.c/include/rcp/udp.h have been re-architected into a pure raw-AVTPDU transport (send/recv only) with no dispatch wiring at all -- that concern is deliberately deferred to a future layer per udp.h's own comment. This gap is purely internal to cpp-RCP's own design (where udp::Server bakes in decode+dispatch+ encode), not a delta versus a current c-RCP dispatch implementation. ## Fix Widened Server::Handler to a frame-level contract: using Handler = std::function<size_t(size_t client, avtp::StreamId stream_id, uint8_t sequence_num, const std::vector<uint8_t>& acf_frame, std::vector<FrameResponse>& out_responses)>; acf_frame is the raw ACF-region bytes -- exactly the shape mock:: Server::dispatch_frame()/dispatch_frame_e2e() accept. New FrameResponse type mirrors the response half of mock::FrameMemberResult without udp.hpp needing to #include mock.hpp. serve() now hands raw bytes straight through and treats FrameResponse::info.rsp == false as "no wire message for this member" (Table 24 suppression/queued/pending/ etc.), skipping it from the reply MultiFrame; if every member was suppressed, no reply datagram is sent at all (previously a reply was always sent). Chose widening Handler's own signature over keeping the old single-message shape as a parallel "compatibility path" -- two wiring surfaces would keep inviting the exact mistake this gap describes; one correct surface is safer. Both the real POSIX Server and the Windows stub Server were updated identically. ## Call sites tests/test_udp.cpp: every existing handler lambda ported to the new signature. include/rcp/tls.hpp needed no change (SecureServer::Handler is just `using Handler = udp::Server::Handler;`, follows automatically -- confirmed via rcp_tls ctest pass). ## Other findings No other behavioral deltas found versus c-RCP's current udp.c/udp.h -- its Annex J wrap/unwrap and control port already match cpp-RCP's existing encode_annexj_datagram/decode_annexj_datagram/ kAnnexJControlPort, left untouched. Documented (via a new test) that regmap::RequestStreamConfig::rx_resp_stream_index defaults to 1, not 0 (intentional "answer discovery before configuration" power-on default) -- the suppression test sets it explicitly to 0 to actually exercise suppression. ## Verification 4 new end-to-end tests wiring a real mock::Server through UDP: multi-member dispatch_frame, Table 24 suppression (client now genuinely times out instead of getting a default response), a Triggered conditional-opcode request (routed to Pending admission, not mistaken for Standard), and an E2E-CRC-protected request via dispatch_frame_e2e. include/rcp/udp.hpp: 747 -> 846 lines. tests/test_udp.cpp: 599 -> 923 lines, test_udp: 24 TEST_CASEs (151 assertions). Full tree rebuilds clean (0 errors, 0 warnings), 58/58 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…el dispatch (#152) * rewrite(phase5): l2.hpp — add FrameHandler wired to Phase 4 frame-level dispatch Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 5 (transport layer), Wave 1 (parallel with udp.hpp's equivalent fix in #151). ## The gap Server::serve() decoded every inbound frame straight into a MultiFrame (parsed acf::AcfEntry list) and called Handler once per already-decoded member -- the same shape as mock::Server::dispatch(), which never receives a stream_id at all. A caller wiring Handler to mock::Server::dispatch() can never reach Table 24 suppression (only reachable via the ten dispatch_<type>() wrappers' stream_id parameter), never reaches peek_conditional_request_type()/decode_and_dispatch() so conditional/cancellation-opcode requests get misread as ordinary Standard requests and executed immediately instead of queued, and never reaches dispatch_frame()/dispatch_frame_e2e() so E2E/fragment dispatch is unreachable too. ## Fix Added a namespace-scope FrameMemberResult (deliberately a parallel type to mock::Server::FrameMemberResult, not a shared one -- matching this file's own documented "parallel concrete types, no shared interface" convention) and a FrameHandler type receiving one whole frame's raw, unparsed ACF bytes plus its stream_id/sequence_num -- the exact shape mock::Server::dispatch_frame()/dispatch_frame_e2e() expect. Two new pure, socket-free helpers, decode_avtp_frame_header()/ decode_l2_frame_header(), split the AVTP envelope off without parsing the ACF payload, so serve() can hand raw bytes to a FrameHandler instead of re-encoding an already-parsed MultiFrame. set_frame_handler() takes priority over the pre-existing Handler when both are set; a member whose response.rsp == false (Table 24 suppression, a Queued/ Pending outcome, or a frame-level sequence-gate rejection) is dropped from the outgoing frame, and if every member was dropped, nothing is sent at all. The old Handler path is untouched byte-for-byte for backward compatibility with tests/l2_veth_roundtrip.cpp (a real-frames-over- real-veth-interfaces CI gate) -- this was a deliberate additive choice rather than replacing Handler's signature (the sibling udp.hpp fix in #151 widened Handler directly instead; both are valid shapes for the same underlying gap, no reconciliation needed since they're independent files). ## Bug found and fixed (c-RCP delta) rcp_l2_mac_is_unicast() (c-RCP src/l2.c:79-82, include/rcp/l2.h:121-137, REQ-L2-011) had no cpp-RCP equivalent anywhere -- added is_unicast_mac(). Confirmed via repo-wide search that regmap::writer_ctx()'s via_unicast parameter has no production caller yet (lifecycle.hpp's REQ-LIFECYCLE-027 unicast gate is data-model-only today) -- a real content gap worth closing now, but wiring it into a live dispatch path is a separate, broader gap, documented as out-of-scope for this batch. ## Verification Verified in a Docker/Ubuntu 24.04 container with CAP_NET_RAW/ CAP_NET_ADMIN and a real veth pair, replicating ci.yml's exact l2-veth job flags: clean build (0 errors, 0 warnings), full ctest 58/58, and l2_veth_roundtrip actually EXECUTED (not just compiled) against real AF_PACKET sockets, both before and after the change. A scratch program exercising the new FrameHandler path over the same real veth pair (multi-member dispatch plus a suppressed member producing no wire response) also passed. macOS build clean, 0/0 warnings, 58/58 ctest (l2_veth_roundtrip is Linux-only, gated separately -- confirmed absent from the macOS ctest registration as expected, re-confirmed by the "l2 veth round trip" CI job itself on this PR). include/rcp/l2.hpp: 808 -> 1069 lines. tests/test_l2.cpp: 244 -> 596 lines, 11 -> 21 TEST_CASEs (7 new for FrameHandler/Table-24/ conditional-opcode/multi-member/E2E wiring, 3 new for is_unicast_mac). .fusa-reqs.json: +2 entries (REQ-L2-009, REQ-L2-010). tests/l2_veth_roundtrip.cpp: unchanged by design (still uses Handler, exercising raw-socket round-trip fidelity, not dispatch semantics). No production call site wires l2::Server anywhere else in the tree (repo-wide grep confirmed), so no other call sites needed updating. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * fix(l2): cast AvtpFrameHeader::sequence_num (uint16_t) to uint8_t for FrameHandler calls in tests Fixes an MSVC /W4 /WX narrowing-conversion error (C2220/C4244) at two new test call sites (test_l2.cpp:525,572) that call FrameHandler's uint8_t sequence_num parameter directly with hdr.sequence_num (uint16_t) -- not caught by clang/gcc's warning sets on the other CI platforms. Test-only, no production code affected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --------- Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…te-level buffer (#153) * rewrite(phase5): shmem.hpp — rebuild Channel around a real bounded byte-level buffer Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 5 (transport layer), Wave 2. A dedicated investigation (this session) found cpp-RCP's shmem.hpp was a decoded-object pass-through with no byte encoding at all and no capacity bound -- Channel::request() called a std::function handler directly and synchronously in the same process. c-RCP's rcp_shmem_avtp_pair_new() is a real byte-level bounded ring-buffer transport, real memcpy framing, RCP_ERR_BUSY backpressure when full -- but is ALSO in-process only under the hood (plain heap, non-pshared mutex/condvar). Neither codebase has ever had genuine cross-process shared memory; "shmem" means "a fast paired in-process queue" in both trees. Decision: rebuild Channel around a real byte-level bounded buffer (closing the two real gaps -- no capacity bound, codec never exercised) while keeping the existing decoded-object Handler signature at the boundary (matching udp::Server's own internal-codec/external- decoded-callback shape), so admin.hpp/config.hpp need zero changes. ## Ported (c-RCP src/shmem.c 266 lines / include/rcp/shmem.h 99 lines) - shmem.c:68-71 (RCP_ERR_BUSY on full queue) -> Channel's new detail::FrameSlots<kMaxQueueCapacity> bounded pool + ErrBusy return, REQ-SHMEM-006. - shmem.c:64-67/109-113 (closed-side send/recv RCP_ERR_CLOSED) -> Channel::request()'s existing closed_ check, now cited REQ-SHMEM-005/010. - The codec-never-exercised gap: Channel::request() now genuinely calls acf::encode_acf_abb/encode_acf_gbb and decode_acf_abb/ decode_acf_gbb (selection mirrors udp.hpp's encode_frame/decode_frame) on both the request and response legs before/after the Handler runs. - shmem.h:91-93's runtime queue_capacity parameter (clamped to min 1, shmem.c:204) -> Channel(stream_key, queue_capacity = kDefaultQueueCapacity), clamped into [1, kMaxQueueCapacity]. ## New shape Channel keeps its old Handler signature and synchronous, no-thread- of-its-own dispatch model exactly. Internally, request() now: encodes -> acquires a fixed-capacity slot (ErrBusy if none free) -> decodes back out of that slot -> calls Handler with decoded objects (handler copied out from under a separate mutex and invoked UNLOCKED, so concurrent callers' handlers genuinely run in parallel) -> encodes/ decodes the response -> releases the slot via RAII. Registry's public surface is byte-for-byte unchanged -- confirmed by reading all of admin.hpp/config.hpp/test_admin.cpp/test_config.cpp in full: they only use Registry::add_channel/deregister/lookup/channels/close and Channel::stream_key()/new_channel(), never Channel::request()'s internals. No changes needed to any of the four. ## Bug found and fixed mid-task (real concurrency bug) The original code held the handler mutex ACROSS the handler call itself, which would have serialized every concurrent request and deadlocked under the new capacity tests -- fixed by copying the handler out under the lock, then invoking it unlocked. ## Architecture note (documented in the header) Unlike c-RCP's genuinely two-party, strict-FIFO ring (two independent threads holding *out_a/*out_b), Channel::request() is both producer and consumer in one synchronous call, so the bounded buffer is a per-caller indexed slot pool, not a literal head/tail FIFO -- same capacity bound and BUSY-without-enqueuing contract, no independent- reader ordering to preserve since there's only ever one reader. ## REQ-SHMEM mapping / c-RCP citation drift found (not propagated) .fusa-reqs.json was split 2026-08-18 (issue #533) into REQ-SHMEM-001.. 010 (not 001..009). Found three req-id mis-citations in c-RCP's own src/shmem.c inline comments (shmem_side_send citing 007 instead of 006; shmem_side_recv wrongly also citing 006; shmem_side_destroy citing 007 instead of 008) -- c-RCP's own test_shmem.c citations are correct, only the .c file's comments drifted; not propagated here. REQ-SHMEM-004 (recv timeout) and REQ-SHMEM-007 (recv buffer-too-small) have no analog in the new cpp shape, documented as such rather than force-fit. ## Verification include/rcp/shmem.hpp: 173 -> 458 lines. tests/test_shmem.cpp: 170 -> 526 lines, test_shmem: 20 TEST_CASEs (68 assertions), re-run 5x back-to-back with no flakiness in the new thread/condition-variable- based backpressure tests. Full tree rebuilds clean (0 errors, 0 warnings), 58/58 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * fix(shmem): correct REQ-SHMEM-004/007 test coverage — numbering mix-up, not a real gap The prior commit's header comment claimed REQ-SHMEM-004/007 had 'no analog' in the new architecture, based on c-RCP's own shmem.h numbering (where those ids mean recv() timeout / recv() destination-buffer-too- small). That was a numbering mix-up, not an actual gap: THIS project's .fusa-reqs.json assigns REQ-SHMEM-004 to 'Registry::lookup finds a channel registered via add_channel' and REQ-SHMEM-007 to 'the caller-supplied client id is forwarded to the handler unchanged' -- both ordinary, already-implemented, directly testable behaviors. - REQ-SHMEM-004: the existing 'Registry::lookup finds a channel registered via add_channel' test already covered this exact behavior -- it was just missing the requirement tag ([registry] only). Added the tag. - REQ-SHMEM-007: no test existed for client-id forwarding at all. Added one, asserting the handler sees exactly the client id passed to request() across two different values (not cached/defaulted from a prior call). Fixes the cpfusa trace (100%% coverage) gate failure on PR #153 (tested=410/total=412 -- these were the two untested entries). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --------- Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…, port deadlock fix (#154) * rewrite(phase5): shmem.hpp — rebuild Channel around a real bounded byte-level buffer Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 5 (transport layer), Wave 2. A dedicated investigation (this session) found cpp-RCP's shmem.hpp was a decoded-object pass-through with no byte encoding at all and no capacity bound -- Channel::request() called a std::function handler directly and synchronously in the same process. c-RCP's rcp_shmem_avtp_pair_new() is a real byte-level bounded ring-buffer transport, real memcpy framing, RCP_ERR_BUSY backpressure when full -- but is ALSO in-process only under the hood (plain heap, non-pshared mutex/condvar). Neither codebase has ever had genuine cross-process shared memory; "shmem" means "a fast paired in-process queue" in both trees. Decision: rebuild Channel around a real byte-level bounded buffer (closing the two real gaps -- no capacity bound, codec never exercised) while keeping the existing decoded-object Handler signature at the boundary (matching udp::Server's own internal-codec/external- decoded-callback shape), so admin.hpp/config.hpp need zero changes. ## Ported (c-RCP src/shmem.c 266 lines / include/rcp/shmem.h 99 lines) - shmem.c:68-71 (RCP_ERR_BUSY on full queue) -> Channel's new detail::FrameSlots<kMaxQueueCapacity> bounded pool + ErrBusy return, REQ-SHMEM-006. - shmem.c:64-67/109-113 (closed-side send/recv RCP_ERR_CLOSED) -> Channel::request()'s existing closed_ check, now cited REQ-SHMEM-005/010. - The codec-never-exercised gap: Channel::request() now genuinely calls acf::encode_acf_abb/encode_acf_gbb and decode_acf_abb/ decode_acf_gbb (selection mirrors udp.hpp's encode_frame/decode_frame) on both the request and response legs before/after the Handler runs. - shmem.h:91-93's runtime queue_capacity parameter (clamped to min 1, shmem.c:204) -> Channel(stream_key, queue_capacity = kDefaultQueueCapacity), clamped into [1, kMaxQueueCapacity]. ## New shape Channel keeps its old Handler signature and synchronous, no-thread- of-its-own dispatch model exactly. Internally, request() now: encodes -> acquires a fixed-capacity slot (ErrBusy if none free) -> decodes back out of that slot -> calls Handler with decoded objects (handler copied out from under a separate mutex and invoked UNLOCKED, so concurrent callers' handlers genuinely run in parallel) -> encodes/ decodes the response -> releases the slot via RAII. Registry's public surface is byte-for-byte unchanged -- confirmed by reading all of admin.hpp/config.hpp/test_admin.cpp/test_config.cpp in full: they only use Registry::add_channel/deregister/lookup/channels/close and Channel::stream_key()/new_channel(), never Channel::request()'s internals. No changes needed to any of the four. ## Bug found and fixed mid-task (real concurrency bug) The original code held the handler mutex ACROSS the handler call itself, which would have serialized every concurrent request and deadlocked under the new capacity tests -- fixed by copying the handler out under the lock, then invoking it unlocked. ## Architecture note (documented in the header) Unlike c-RCP's genuinely two-party, strict-FIFO ring (two independent threads holding *out_a/*out_b), Channel::request() is both producer and consumer in one synchronous call, so the bounded buffer is a per-caller indexed slot pool, not a literal head/tail FIFO -- same capacity bound and BUSY-without-enqueuing contract, no independent- reader ordering to preserve since there's only ever one reader. ## REQ-SHMEM mapping / c-RCP citation drift found (not propagated) .fusa-reqs.json was split 2026-08-18 (issue #533) into REQ-SHMEM-001.. 010 (not 001..009). Found three req-id mis-citations in c-RCP's own src/shmem.c inline comments (shmem_side_send citing 007 instead of 006; shmem_side_recv wrongly also citing 006; shmem_side_destroy citing 007 instead of 008) -- c-RCP's own test_shmem.c citations are correct, only the .c file's comments drifted; not propagated here. REQ-SHMEM-004 (recv timeout) and REQ-SHMEM-007 (recv buffer-too-small) have no analog in the new cpp shape, documented as such rather than force-fit. ## Verification include/rcp/shmem.hpp: 173 -> 458 lines. tests/test_shmem.cpp: 170 -> 526 lines, test_shmem: 20 TEST_CASEs (68 assertions), re-run 5x back-to-back with no flakiness in the new thread/condition-variable- based backpressure tests. Full tree rebuilds clean (0 errors, 0 warnings), 58/58 ctest suites pass (100%) -- independently re-verified from a fresh build, not just the porting agent's own report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * rewrite(phase5): admin.hpp — fixed-capacity subscriber/counter bounds, port deadlock fix Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 5 (transport layer), final module. ## Ported (c-RCP src/admin.c 247 lines / include/rcp/admin.h 177 lines) - admin.c:109-110 (RCP_ADMIN_MAX_SUBSCRIBERS=16, RCP_ADMIN_MAX_COUNTERS=256) -> std::array<..., kMaxSubscribers>/std::array<..., kMaxCounters> bounds on Server's subscriber list and counter table, replacing cpp-RCP's unbounded std::vector/std::map. Matches this rewrite's fixed-capacity, no-dynamic-allocation-on-safety-relevant-paths architecture (already applied to server.hpp's admission queue and shmem.hpp's FrameSlots). - admin.c:133-147 (rcp_admin_server_subscribe, capacity check before add) -> Server::subscribe() returns AdminErrc::subscriber_capacity_exceeded once kMaxSubscribers is reached, matching c-RCP's fail-closed "cb not added" behavior (admin.h:145) rather than growing unbounded. - admin.c:173-198 (rcp_admin_server_record_counter, capacity check on a genuinely new counter name+labels pair) -> counter_capacity_exceeded once kMaxCounters distinct counters exist (admin.h:158), existing counters still update past the bound. - Ported c-RCP's two boundary tests (subscribe-at-capacity, record_counter-at-capacity) into Catch2 TEST_CASEs. ## Bug found and fixed (real concurrency bug, same class as shmem.hpp/#153) emit() (ported from rcp_admin_server_emit(), admin.c:149-171) held the subscriber-list mutex across every subscriber callback invocation -- a subscriber callback that itself calls back into Server (a realistic pattern for a metrics/event sink) would deadlock. Fixed by copying the subscriber list out under the lock, then invoking callbacks unlocked -- same fix shape as shmem.hpp's Channel::request() handler-invocation fix in #153. ## Deliberate non-port, documented in-file c-RCP's admin.c is caller-driven: rcp_registry_t was retired project- wide (per c-RCP's own history) and every admin_server_t call site now passes state in directly rather than looking it up from a live registry. cpp-RCP's admin.hpp instead holds a reference to shmem::Registry and looks up endpoint/channel state through it live. NOT migrated to c-RCP's caller-driven pattern: c-RCP's rationale for going caller-driven was retiring rcp_registry_t entirely, which doesn't apply here -- shmem::Registry is live, current, and still the single source of truth for channel state in this rewrite (see shmem.hpp, #153). Reasoning recorded in admin.hpp's own header comment, not just here. ## Verification include/rcp/admin.hpp: 128 -> 338 lines. tests/test_admin.cpp: 123 -> 200 lines. Full tree rebuilds clean (0 errors, 0 warnings) from a from-scratch rebuild against the fully-merged rewrite/v3-from-c-rcp base (picked up #151/#152/#153 via merge-forward first), 58/58 ctest suites pass (100%) -- independently re-verified, not just the porting agent's own report. ## Phase 5 status udp (#151), l2 (#152), shmem (#153) already merged; this closes out admin, the fourth and final "port from c-RCP" module in Phase 5's scope. tls/capi remain as re-verify-only per ROADMAP.md Phase 17 — confirmed clean against the new core, no content changes needed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --------- Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 1 of 13. ## Background cpp-RCP's .fusa-reqs.json (412 entries) is missing hundreds of requirement entries the code was already ported to implement in Phases 1-5, and cpp-RCP's code already references 1015 distinct REQ-* ids via //fusa:req/[REQ-XXX] tags against only 412 defined entries -- 603 orphaned citations left behind by porting agents as unresolved "ported from c-RCP REQ-XXX, see file.c:NNN" provenance notes. Phase 6 walks these existing citations prefix-by-prefix and turns each into a real catalog entry (or a documented gap), rather than re-deriving requirements from scratch. ## Prefix-scheme decision Kept REQ-ACF-*/REQ-AVTP-*/REQ-WIREERR-* as their own prefixes reusing c-RCP's exact id numbers 1:1, rather than folding into cpp-RCP's existing REQ-WIRE-*/REQ-ACF-*/REQ-ERR-* buckets -- the code already had orphaned citations using c-RCP's exact numbering (REQ-ACF-044, REQ-AVTP-014/021/022/023), signaling that was the intended scheme all along. Existing REQ-WIRE-*/REQ-ACF-*/REQ-ERR-* entries are untouched; duplicate-coverage mappings recorded below for anyone reconciling the two schemes later. ## New catalog entries (36) - REQ-ACF-{001,004,012,013,014,017,019,021,023,024,025-031,032,033, 038,044,047-053} (28 entries) - REQ-AVTP-{013,014,015,021,022,023,031} (7 entries) - REQ-WIREERR-001 (WireErrorCode enum matches TC18 Table 27/30 numbering) Each has a real //fusa:req tag in acf.hpp/avtp.hpp and //fusa:test + Catch2 bracket tag coverage in test_acf.cpp/test_avtp.cpp. 3 new tests added where coverage was genuinely missing: acf_category()/ avtp_category() message-uniqueness (REQ-ACF-001, REQ-AVTP-015), encoder-forces-correct-type-despite-wrong-caller-header (REQ-ACF-004/ 038), WireErrorCode numeric-value pinning (REQ-WIREERR-001). Also fixed a mislabeled inline comment in avtp.hpp that cited REQ-AVTP-022 for behavior that's actually REQ-AVTP-031's (tscf_reserved_all_zero). ## Genuine gaps found (9, filed to .fusa-reqs-pending.json, not the catalog) - REQ-ACF-006/040: encode_acf_abb/_gbb honor a caller-supplied nonzero acf_msg_length instead of always recomputing -- a documented, deliberate divergence needed by e2e.hpp's CRC-trailer accounting, flagged here for an explicit accept/reject call rather than silently carried forward. - REQ-ACF-009/041: decoders never validate declared acf_msg_length against buffer size -- matches the code's own pre-existing TODO(phase1-followup) comment. - REQ-ACF-011: encode_acf_gbb doesn't zero message_timestamp when mtv=false. - REQ-ACF-018: no read_size_or_segment_num kind-selector; existing doc comment contradicts TC18's op-based rule. - REQ-AVTP-012: no combined stream_id+byte_bus_id address-equality construct anywhere. - REQ-WIREERR-002: no WireErrorCode-to-string function. - REQ-WIREERR-005: SequencerNotKnown enumerator has zero consumers (owner-tracking not yet built). ## Judgment calls, not filed as gaps (documented here, not in the pending file) - REQ-ACF-037: cpp's op is a 2-state bool, not c-RCP's 3-state enum -- the OP_NONE fallback case is structurally inapplicable, not missing. - REQ-AVTP-016/027 (transport retain/release refcounting): cpp-RCP uses RAII via l2.hpp/udp.hpp/shmem.hpp instead of a manual-refcount transport abstraction -- an architecture divergence, and neither has a TC18 citation in c-RCP's own catalog, so they don't fit the pending file's TC18-scope contract. ## Existing-duplicate mappings (no action; c-RCP id -> cpp-RCP id already covering it) ACF: 002/022/034/035/036/037->WIRE-010, 005/039->WIRE-013, 007/008-> WIRE-014, 010->WIRE-004, 015/016/046->WIRE-006, 020->WIRE-004, 042/043/045->WIRE-005. AVTP: 001/003->WIRE-001, 002/004->WIRE-002, 005/006->WIRE-013, 007/008->WIRE-003, 010/011/025->WIRE-007. ## Out of scope (implemented, but in other files -- noted only, not this batch's concern) AVTP-009/017-020/024/028/029/030/032-034 (l2.hpp, udp.hpp, shmem.hpp, lifecycle.hpp, mock.hpp, server.hpp); WIREERR-003/004/006/007 (e2e.hpp, lifecycle.hpp, request.hpp, pwm.hpp) -- confirmed genuinely implemented there, will get their own tags when those prefixes' batches run. ## Verification .fusa-reqs.json: 412 -> 448 entries (+36, 3672 -> 4092 lines). .fusa-reqs-pending.json: 3 -> 12 entries (+9, 35 -> 123 lines). include/rcp/acf.hpp: +28 lines. include/rcp/avtp.hpp: +14 lines net. tests/test_acf.cpp: +84 net. tests/test_avtp.cpp: +22 net. Independently re-verified (not just the porting agent's own report): confirmed programmatically that zero of the 412 pre-existing catalog entries were modified or deleted (semantic id-keyed diff against the pre-batch base, not just a raw line diff -- the raw diff shows churn in unrelated CLI/RELAY/Discovery entries that's pure JSON re-serialization noise, not content change), and that zero orphaned REQ-ACF-*/REQ-AVTP-*/REQ-WIREERR-* tags remain anywhere in acf.hpp/ avtp.hpp/test_acf.cpp/test_avtp.cpp. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). ## CI gate fix found while watching this PR's checks `cpfusa trace`'s sec-tested gate failed: it was a 17% floor (64/376 at the time it was written) against `secTestedRequirements/totalRequirements`, but its own comment already stated the real intent was "a regression floor against the exact, CYBERSECURITY.md-enumerated subset" losing test coverage -- not a claim about what fraction of the codebase is security-relevant. A percentage-of-total is the wrong shape for that intent once the total catalog is deliberately growing (exactly what Phase 6 does): this batch's 36 new, correctly non-cybersecurity REQ-ACF-*/REQ-AVTP-*/REQ-WIREERR-* entries dropped the ratio from 72/412=17.5% to 72/448=16.1%, failing a floor that was never meant to track total-catalog size. Converted ci.yml's "Enforce security-test coverage" step to an absolute-count floor (>= 72, the current CYBERSECURITY.md-enumerated total) instead -- this still catches the real regression the step exists for (an enumerated id losing its test tag, or being dropped from severity:cybersecurity) without being diluted by legitimate, intentional catalog growth elsewhere. Without this fix, every subsequent Phase 6 batch would hit the same false failure as the denominator keeps growing toward c-RCP's 1282-entry scale. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ation (#156) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 2 of 13. ## Scope fragment.hpp's REQ-FRAG-* prefix (18 c-RCP entries, all already orphan-cited in cpp-RCP's own code), the 7-entry REQ-RMAP-* slice that respqueue.hpp's code already cites (059/061-065/085 -- a real subset of c-RCP's combined register-map+response-queue module, specific to response-queue behavior; the rest of REQ-RMAP-*/REQ-REGMAP-* is deliberately deferred to batch 11's regmap.hpp rename), and loan.hpp's REQ-LOAN-* prefix (9 c-RCP entries). ## New catalog entries (26) - REQ-FRAG-{001-015,017,018} (17) -- fragment.hpp's plan_count()/ plan()/Reassembler, all verified against real code. REQ-FRAG-010 needed one new test (destruction-safety, RAII) that didn't exist yet. - REQ-LOAN-{008,009} (2) -- BufferPool destructor free-list release, new_buffer_pool(). LOAN-001-007 needed no action: already had correct catalog entries and code tags from prior work (LOAN-007's tag lives in rcp.hpp/test_rcp.cpp, not loan.hpp -- confirmed found-and-covered, not a gap). - REQ-RMAP-{059,061,062,063,064,065,085} (7) -- respqueue.hpp's RespQueue class. REQ-RMAP-065 copied honestly as c-RCP has it: scope="tc18-gap", status="partial" -- c-RCP's own text states the heartbeat-composition primitives are all present and proven composable, but driving them against a real clock/transport is left to the integrator; cpp-RCP is at the identical boundary. ## Judgment call, not filed as a gap (documented in-file, not the pending file) REQ-FRAG-016 (rcp_fragment_reasm_result_t's RCP_FRAGMENT_REASM_ERR_ALLOC, a distinct allocation-failure return value): no C++ analog -- Reassembler uses a fixed std::array member with no heap growth and therefore no allocation-failure path to distinguish. c-RCP's own REQ-FRAG-016 also carries no TC18 citation, matching the same "architecture divergence, no spec basis to force a gap entry" precedent as batch 1's REQ-ACF-037/REQ-AVTP-016/027. The orphaned tag lines were removed from fragment.hpp/test_fragment.cpp and replaced with an explanatory comment (prose mentions of the id remain for context, not real //fusa:req/test tags -- confirmed these don't register as references to cpfusa's trace tool). ## No gaps filed this batch Every c-RCP requirement in scope was either genuinely implemented, already covered by a pre-existing entry, or the one documented judgment call above -- .fusa-reqs-pending.json is unchanged. ## Verification Independently re-verified (not just the porting agent's own report): semantic id-keyed diff confirms zero pre-existing catalog entries modified/deleted, exactly the 26 new ids added. Zero real orphaned FRAG/LOAN/RMAP tags remain in the six files in scope (the one apparent match, REQ-FRAG-016, is plain prose in a regular comment, not a //fusa:req/test tag -- confirmed by direct grep). Confirmed no new entry carries "severity": "cybersecurity" (would have diluted the sec-tested absolute-count floor fixed in batch 1/#155) -- sec count holds exactly at 72/474, matching the floor. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). .fusa-reqs.json: 448 -> 474 entries (+26, 4092 -> 4378 lines). include/rcp/fragment.hpp, tests/test_fragment.cpp: net changes for the REQ-FRAG-016 tag removal/explanatory-comment swap and the new REQ-FRAG-010 test. respqueue.hpp/loan.hpp and their test files: unchanged (already correctly tagged and tested). Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ivation (#157) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 3 of 13 -- the largest single-concern batch so far. ## Scope request.hpp's conditional-request cluster: Compound (REQ-CMP-*, 29 c-RCP entries), Triggered (REQ-TRIG-*, 13), Chained (REQ-CHAIN-*, 12), Timed (REQ-TIMED-*, 13), Cancellation (REQ-CANCEL-*, 15), and the scheduler (REQ-SCHED-*, 8) -- 90 c-RCP entries total, all already orphan-cited across request.hpp/test_request.cpp. c-RCP splits this across three files (request.c/request_sequencer.c/scheduler.c); cpp-RCP's rewrite deliberately keeps its own prior unification into one file (documented cpp-RCP design choice, not re-split), while keeping c-RCP's id-prefix scheme per RELAY docs/RCP-ARCHITECTURE.md's canonical choice #7. ## New catalog entries (70) - REQ-CMP-{001-003,010-029} (23) - REQ-TRIG-{001,004-013} (11) - REQ-CHAIN-{002,004-007,010-012} (8) - REQ-TIMED-{002-011} (10) - REQ-CANCEL-{002-015} (14) - REQ-SCHED-{002,003,007,008} (4) Each verified against real request.hpp code/test_request.cpp tests, not rubber-stamped from the pre-existing orphan-tag manifest. Two new tests added (REQ-CHAIN-005, REQ-TIMED-004) that had no dedicated test before. ## Genuine gaps found (7 new, filed to .fusa-reqs-pending.json, total now 19) - REQ-CMP-008/009, REQ-TRIG-003, REQ-CHAIN-003: encode_compound_ request()/encode_triggered_request()/encode_chained_member() never validate their type/payload-size before encoding -- they forward straight to acf::encode_acf_gbb(), which by design never rejects -- and their plain std::vector<uint8_t> return type has no channel to signal rejection at all, unlike encode_timed_request()'s own std::optional<...> (confirmed: request.hpp:569/745/970 return bare std::vector, request.hpp:1077 returns std::optional -- the exact asymmetry this finding describes). No test exercised these paths. Removed the false orphan tags claiming coverage, documented the finding in-file (request.hpp:242-249). - REQ-CHAIN-008: RequestLedger::submit() never rejects a Chained request with no chained_predecessor; no CHAIN_ERROR-equivalent exists (c-RCP's rcp_chained_advance() returns RCP_CHAINED_MEMBER_CHAIN_ERROR for exactly this case, TC18 §11.2.2.6). - REQ-TIMED-012/013: no dispatch/admission entry point anywhere in cpp-RCP threads a real TSCF avtp_timestamp into timed_admit()/ timed_due() -- the primitives exist, the loop doesn't. c-RCP's own entries describe a real dispatch-layer TSCF-threading feature (rcp_mock_server_dispatch_tscf() and its E2E-aware siblings); cpp-RCP's mock.hpp has no equivalent entry point yet. Matches this file's own documented "no scheduler thread" scope -- a real, TC18-cited gap, not force-fit as implemented. ## Duplicate-coverage mappings (no new entry, no gap) CMP-004, TRIG-002, CHAIN-001, TIMED-001, CANCEL-001 -> pre-existing REQ-SEQ-009 ("RequestErrc is a distinct error category with non-empty, distinct messages") -- cpp-RCP unified all per-kind strerror requirements into one shared error category. SCHED-001 -> pre-existing REQ-SEQ-002 (category_of() already documents this classification). Note: cpp-RCP's REQ-SEQ-* is its own, pre-existing, differently-scoped prefix about this same file -- unrelated to c-RCP's request_sequencer.h REQ-SEQ-*, left untouched, out of scope. ## Judgment calls, already documented in-file (no new action) CMP-005/006/007 (peek_request_type) and SCHED-004/005/006 (frame-splitting) -- both already explicitly documented in request.hpp's own header as deliberate non-ports, composable via other already-covered functions. CHAIN-009 -- covered by RequestLedger's graph-based cascade_cancel()/propagate_chain_completion(), already documented as strictly more capable than c-RCP's positional model. ## REQ-SCHED-* prefix decision Code's own orphan tags already used REQ-SCHED-* verbatim, matching c-RCP -- kept as-is, no rename needed. ## Stale pre-existing gap entries found, not touched (out of this batch's scope) .fusa-reqs-pending.json's pre-existing REQ-SEQ-013/014/015 (filed 2026-08-07, describing missing Compound exec_delay/repetitions, Triggered trigger fields, Timed presentation_time) appear stale -- current request.hpp code genuinely implements all three (CompoundStep, TriggeredStep, TimedRequest + timed_admit()/timed_due()). Left as-is since fixing them means touching the REQ-SEQ-* prefix; flagging here for whoever picks up REQ-SEQ-* in a later batch. ## Verification Independently re-verified (not just the porting agent's own report): semantic id-keyed diff confirms zero pre-existing catalog entries modified/deleted, exactly the 70 new ids added matching the report's own breakdown per prefix exactly. Confirmed no new entry carries "severity": "cybersecurity" -- sec-tested count holds exactly at 72/544, matching the absolute-count floor fixed in batch 1. Spot- verified the encode_*_request asymmetry claim directly against request.hpp's real function signatures, and REQ-CHAIN-008/REQ-TIMED- 012/013's c-RCP source citations directly against c-RCP's own .fusa-reqs.json entries -- both check out. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%), test_request alone: 137 TEST_CASEs, 615 assertions. .fusa-reqs.json: 474 -> 544 entries (+70, 4378 -> 5203 lines). .fusa-reqs-pending.json: 12 -> 19 entries (+7, 123 -> 192 lines). include/rcp/request.hpp: 1508 -> 1537 lines. tests/test_request.cpp: 1685 -> 1717 lines. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…atalog change (#158) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 5 of 13. ## Scope watchdog.hpp's REQ-WDG-* prefix. c-RCP has REQ-WDG-001..012 (12 entries); cpp-RCP's catalog already had REQ-WDG-001..008 (8 entries, prior work); watchdog.hpp/test_watchdog.cpp reference all 12 ids, so 009-012 looked orphaned going in. Investigation found the premise only half held. ## No new catalog entries this batch REQ-WDG-009 (synchronous initial verdict), REQ-WDG-011 (default_config poll interval), REQ-WDG-012 (destroy() null-safety): never tagged anywhere in cpp-RCP -- they only appear in watchdog.hpp's own header-comment prose explaining why they don't transfer, and carry no c-RCP TC18 citation. They're c-RCP-specific API-shape artifacts (background-thread poll config, cached-state staleness, manual destroy()) structurally moot in cpp-RCP's RAII/live-compute design. Confirmed this reasoning is sound; left as documented non-transfers, no catalog entry, no pending-file gap (nothing is actually missing). ## One genuine finding: REQ-WDG-010 is implemented and tested, but has zero formal tags anywhere Filed to .fusa-reqs-pending.json as a traceability-tag gap, NOT a behavioral gap. The behavior itself is real and correctly wired: mock.hpp's dispatch_e2e_core() calls rx_watchdog_kick() unconditionally at both its call sites (mock.hpp:1147, mock.hpp:1343), forwarding to watchdog::Manager::on_request_received(); test_mock.cpp has a dedicated end-to-end test ("dispatch_e2e kicks the per-stream RxWatchdog (REQ-WDG-010) on every call...", test_mock.cpp:1739+); sim.hpp's Simulator independently wires the same hook. The blocker is purely mechanical: cpp-FuSa's trace tool only counts a requirement as traced/tested when a literal `// fusa:req`/`// fusa:test REQ-WDG-010` comment exists somewhere -- mock.hpp's existing prose mentions and test_mock.cpp's `[REQ-WDG-010]` Catch2 tag string don't match that regex, so this id has zero formal annotations anywhere despite being genuinely covered. Adding it to .fusa-reqs.json today (with no real tag anywhere) would fail CI's hard `cpfusa trace --req-coverage 100` /tested-coverage gates. Fixing this needs two tags added to include/rcp/mock.hpp/tests/test_mock.cpp -- both out of this batch's file-scope (batch 5 is restricted to watchdog.hpp/test_watchdog.cpp). Filed here so batch 13 (MOCK/PWRMODE, the batch that touches mock.hpp) can add those two tags and migrate this entry from pending into the real catalog in the same change. ## REQ-WDG-001..008 re-verified, untouched Existing tags checked against real code/tests and found accurate -- no placement fix needed, no catalog change. ## Verification .fusa-reqs.json: unchanged, byte-identical (544 entries, sec-tested count still exactly 72). .fusa-reqs-pending.json: 19 -> 20 entries (+1, REQ-WDG-010). include/rcp/watchdog.hpp: expanded its existing REQ-WDG-010 header-comment bullet with the mock.hpp citation and the pending-migration note (+21/-3 net). tests/test_watchdog.cpp: unchanged. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 6 of 13 -- both safety-mechanism files (E2E CRC/sequence guard, LIFECYCLE state machine/access control). ## Scope e2e.hpp's REQ-E2E-* (c-RCP has 47 entries) and lifecycle.hpp's REQ-LIFECYCLE-* (c-RCP has 39 entries), 86 c-RCP entries total. ## Major finding: a pre-existing id-numbering collision, unique to this batch Unlike every prior batch, cpp-RCP's pre-existing REQ-E2E-001..014 and REQ-LIFECYCLE-001..007 entries were numbered independently (sequential-by-file-order) before this rewrite's later convention of reusing c-RCP's exact ids took hold -- they collide head-on with c-RCP's own ids 001-014/001-007, describing entirely different requirements (verified directly: cpp's REQ-E2E-004 is "verify_crc detects a mismatched or corrupted CRC"; c-RCP's REQ-E2E-004 is "rcp_e2e_length_with_crc() applies the length-accounting pre-adjustment" -- unrelated content, same id). Not renamed here (out of scope, no established rename plan for this specific collision unlike REQ-REGMAP-*/REQ-RMAP-*'s already-decided rename). Six c-RCP ids that are genuinely implemented but whose natural id is already taken (REQ-E2E-004-009) are filed to .fusa-reqs-pending.json as id-collision gaps -- a new gap category, distinct from a missing-tag or missing-behavior gap -- for a future renumbering-cleanup batch. ## New catalog entries (39) - E2E (13): 021, 028, 029, 030, 032, 035, 036, 037, 038, 040, 042, 045, 046. None carry severity:cybersecurity -- c-RCP's own catalog has zero severity fields anywhere (confirmed via grep of the whole file); cpp-RCP's existing 14 cybersecurity-marked E2E entries are this rewrite's own earlier independent judgment, not a c-RCP citation, so nothing new was copied. sec-tested count unchanged at exactly 72/583 -- no CI floor change needed. - LIFECYCLE (26): 013-022, 024-039 (023 excluded, see below). Two entries (022, 034) copied honestly as scope="tc18-gap"/ status="partial", matching c-RCP's own tracked findings and mirrored by cpp-RCP's own pre-existing LifecycleErrc::eps_not_idle doc comment. ## Real bug found and fixed: REQ-LIFECYCLE-023 tag was mislabeled The pre-existing orphan tag for REQ-LIFECYCLE-023 was attached to field_writable()'s GENERAL per-FieldKind tests (which actually match c-RCP's REQ-LIFECYCLE-018/019/020), not to c-RCP-023's actual narrow claim (EP_GEN_CFG/QUEUE_CFG mapped to HwGeneric's lock rule, per Figure 17). Retagged the four field_writable() tests to their correct ids (018/019/020, plus their real secondary associations 026/027/030/035/036/037). REQ-LIFECYCLE-023's actual claim confirmed NOT implemented anywhere -- regmap.hpp's own header comment explicitly defers the "EP0 address-routed dispatcher" carrying this decision -- filed to .fusa-reqs-pending.json as a genuine behavioral gap, not a tag issue. Independently spot-verified: confirmed cpp's REQ-E2E-004 content mismatch and the REQ-LIFECYCLE-023 tag removal directly against the diff. ## Other gaps filed (5 more, total 11 new pending entries this batch) REQ-E2E-033/039/041/047: genuinely implemented+tested in mock.hpp/ test_mock.cpp (out-of-scope files) with zero formal tags -- same REQ-WDG-010 pattern from batch 5, verified by reading the actual dispatch code. Filed for a future mock.hpp-scoped batch (13) to tag and migrate. ## New test added LifecycleErrc category/message distinctness (REQ-LIFECYCLE-021) -- was implemented but had zero test coverage. ## Duplicate/consolidation mappings (no action, largest area of the batch) E2E: c-RCP 001-020, 022-027, 031, 034, 043, 044 map onto existing/new cpp entries (043/044 subsumed into new StreamFaultTracker entry 021; 026/035 and 030/036 are literal duplicate pairs c-RCP itself acknowledges, both kept separately per c-RCP's own practice). LIFECYCLE: c-RCP 001-012 map onto cpp's existing 001-007 (e.g. 010+011 both subsumed by cpp-006's single unconditional-deconfigure statement). ## Out-of-scope finding, flagged only Pre-existing orphan tags REQ-RMAP-049/REQ-RMAP-055 in lifecycle.hpp's own manifest, untouched -- for whoever picks up the RMAP prefix (batch 11). ## Verification Independently re-verified (not just the porting agent's own report): semantic id-keyed diff confirms zero pre-existing catalog entries modified/deleted, exactly the 39 new ids added matching the report's per-prefix breakdown exactly. Confirmed no new entry carries severity:cybersecurity -- sec-tested count holds exactly at 72/583. Directly spot-checked the id-collision claim (cpp vs c-RCP REQ-E2E-004 content) and the REQ-LIFECYCLE-023 mislabel-and-retag against the real diff -- both check out. Confirmed e2e.hpp/lifecycle.hpp diffs are PURE tag-manifest additions/corrections with zero behavioral code changes -- appropriate caution for safety-mechanism files. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). .fusa-reqs.json: 544 -> 583 entries (+39, 5203 -> 5663 lines). .fusa-reqs-pending.json: 20 -> 31 entries (+11, 202 -> 310 lines). include/rcp/e2e.hpp: +6 lines (tags only). tests/test_e2e.cpp: +8 lines net. include/rcp/lifecycle.hpp: +17 lines (tags only). tests/test_lifecycle.cpp: +52 lines net (retags + 1 new test). Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…160) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 7 of 13. ## Scope can.hpp's REQ-CANEP-* (c-RCP: 42 entries), lin.hpp's REQ-LINEP-* (34), iseled.hpp's REQ-ISELED-* (42) -- 118 c-RCP entries total. Base bus/ frame-layer REQ-CAN-*/REQ-LIN-* prefixes explicitly out of scope. ## Major finding: can.hpp/lin.hpp/iseled.hpp were already far ahead of the catalog An earlier, untracked rewrite pass had already brought these three files close to c-RCP's real design -- most of the 118 requirements were already genuinely implemented and tested; the gap was almost entirely in the catalog/tags, not the code. iseled.hpp/test_iseled.cpp already carried complete file-level tags for essentially the whole c-RCP id range -- zero changes needed to either file. can.hpp/lin.hpp only had tags for their old, pre-rewrite low-numbered ids and needed 33/33 new tags respectively. ## Id-collision pattern (same class as batch 6/#159) cpp's pre-existing low ids (CANEP 001-009, LINEP 006, ISELED 001-007) were numbered independently before this rewrite's "reuse c-RCP's exact id" convention took hold, and collide with c-RCP's own same-numbered but content-unrelated requirements (verified directly: cpp's REQ-CANEP-001 is "CAN's ep_type id is 0x0B"; c-RCP's is "rcp_ep_can_frame_format_valid() accepts exactly the six FrameFormat values Table 57 defines" -- unrelated content, same id). Filed as 17 id-collision gaps to .fusa-reqs-pending.json (CANEP-001..009 x9, LINEP-006 x1, ISELED-001..007 x7) rather than corrupting catalog id uniqueness, for a future renumbering-cleanup batch. ## New catalog entries (98) - CANEP: 33 (010-028, 030-042; 029 included as an honest status:not-implemented spec-gap entry, matching c-RCP's own tracked defect -- not force-fit as implemented) - LINEP: 31 (007-039, excluding collision 006 and retired 023/026) - ISELED: 34 (008-027, 029-042, excluding collisions 001-007 and retired 028) ## Retired, no action (3) REQ-LINEP-023/026, REQ-ISELED-028 -- c-RCP itself retired these as near-duplicates; correctly not ported. ## Real test-coverage gaps found and closed (new TEST_CASEs added) can.hpp: CanErrc message distinctness; CanFunctionalConfig zero-init; full reject-taxonomy completions for decode_frame_request/ decode_frame_response/decode_frame_response_fragment/ decode_reassembled_frame_response (bad_msg_type/bad_frame_format/ bad_arbitration_id were previously never exercised, only wrong_bus/ short_frame/wrong_op/bad_evt were); a dedicated regression test for the ep_clear_req_storage wire-bit-4 fix (a wrong-but-self-consistent bit that round-trip tests alone can't catch); explicit byte-level right-alignment assertion; remote-frame-absence documentation test. lin.hpp: LinErrc message distinctness; decode_command_request bad_evt/bad_msg_type paths that were named in an existing test's title but never actually exercised; LinReconfigErrc message non-emptiness. Two initially-incorrect test constructions were caught and fixed before finalizing (a structurally-unreachable bad_msg_type-via-ACF_GBB path, a byte-index mislabel in the right-alignment test). ## Verification Independently re-verified (not just the porting agent's own report, which itself ran the real cpfusa v0.18.0 trace tool and reported Total 681/Annotated 681/Tested 681, 100% both metrics, secTested holding at 72): semantic id-keyed diff confirms zero pre-existing catalog entries modified/deleted, exactly 98 new ids matching the report's per-prefix breakdown exactly (CANEP 33, LINEP 31, ISELED 34), zero duplicate ids anywhere in the catalog. Confirmed no new entry carries severity:cybersecurity -- sec-tested count holds exactly at 72/681. Directly spot-checked the CANEP-001 id-collision claim against both catalogs' actual content -- confirmed real. Confirmed can.hpp/lin.hpp diffs are pure additions (zero deleted lines across all six touched files) -- tags and new tests only, no existing behavior touched. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). .fusa-reqs.json: 583 -> 681 entries (+98, 5663 -> 6806 lines). .fusa-reqs-pending.json: 31 -> 48 entries (+17, 310 -> 517 lines). include/rcp/can.hpp: +33 lines (tags only). include/rcp/lin.hpp: +33 lines (tags only). tests/test_can.cpp: +335 lines. tests/test_lin.cpp: +101 lines. include/rcp/iseled.hpp, tests/test_iseled.cpp: unchanged. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* rewrite(phase6): batch 8 — I2C/ADC/GPIO catalog re-derivation Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 8 of 13. ## Scope i2c.hpp's REQ-I2C-* (c-RCP: 26 entries), adc.hpp's REQ-ADC-* (55), gpio.hpp's REQ-GPIO-* (46) -- 127 c-RCP entries total. ## Major finding: same pattern as batch 7 -- code was already far ahead of the catalog All three modules' code was already re-implemented against c-RCP's real design in an earlier, untracked pass, with top-of-file manifests already covering nearly the full c-RCP id range. The gap was almost entirely in the catalog and the 9 pre-existing low-numbered ids per module, not missing implementation. GPIO needed zero code/test edits at all; ADC's module was fully redesigned (compute_average/ AdcEndpoint::request_reading/handle_request/encode_adc_value no longer exist), making its 9 old-id collisions the cleanest case of the three -- single-occupant stale content with zero remaining tags pointing to them. ## New catalog entries (101) - I2C: 18 (REQ-I2C-008-018, 020-026) - ADC: 46 (REQ-ADC-010-055) - GPIO: 37 (REQ-GPIO-010-046) ## Id-collision gaps filed (21, same class as batches 6/7) - I2C: 7 (001-007) -- old entries describe now-removed pre-rewrite functions (i2c_mode_of 2-way decode, compound_wait_matches_bits) or content that migrated to other ids; real c-RCP content genuinely implemented+tested, often via legitimate dual-tagging with the old entry's own still-valid test. - ADC: 9 (001-009) -- cleanest collision case: verified directly, cpp's REQ-ADC-008 ("AdcEndpoint::handle_request applies Table 33 Row 2...", carries severity:cybersecurity, left untouched) describes a function (AdcEndpoint::handle_request) confirmed to no longer exist anywhere in the codebase; c-RCP's real REQ-ADC-008 ("collect_ response_values reports a short count...") is unrelated content, same numeral. - GPIO: 5 (001, 006-009) -- old entries still valid for their own content, real c-RCP content at the same numerals implemented+tested elsewhere via dual-tags. ## Genuine behavioral gaps filed (4, GPIO only) REQ-GPIO-002/003/004/005 -- c-RCP's pin_index_valid()/pin_mask()/ pin_get()/write_semantics_valid() have no standalone cpp equivalents; bounds/mask/get logic is inlined ad hoc at call sites rather than exposed as independently-tested primitives (confirmed via zero matching bracket tags anywhere). ## Retired, no action REQ-I2C-019 only -- c-RCP itself retired it, superseded by 001/021/022. No retired ids in ADC or GPIO's ranges. ## Real bug fixed: mislabeled tag tests/test_i2c.cpp's config_write_not_supported TEST_CASE was mistagged [REQ-I2C-012] (that id's real meaning is "decode_transfer_ request rejects a malformed frame" -- unrelated); retargeted to [REQ-I2C-007], restoring a legitimate dual-tag with the old catalog entry it actually matches. ## New test added ADC's collect_response_values leading-count truncation (REQ-ADC-007) had no test exercising the avg_count > value_count case -- added. Plus two manifest-line/assertion fixes (REQ-ADC-035/041 tags added to the header manifest; an out-of-range-code assertion strengthened in the existing AdcErrc message test). ## Verification Independently re-verified (the porting agent itself also ran the real cpfusa trace tool: 782/782 annotated, 780/782 tested by design -- the 2 untested, REQ-ADC-032/034, mirror c-RCP's own test_ep_adc.c convention where "testedness" lives in other modules' suites, secTested holding at 72): semantic id-keyed diff confirms zero pre-existing catalog entries modified/deleted, exactly 101 new ids matching the report's per-prefix breakdown exactly (I2C 18, ADC 46, GPIO 37), zero duplicate ids anywhere. Confirmed no new entry carries severity:cybersecurity -- sec-tested count holds exactly at 72/782. Directly spot-checked the REQ-ADC-008 id-collision claim against both catalogs' actual content -- confirmed real. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). .fusa-reqs.json: 681 -> 782 entries (+101, 6806 -> 8003 lines). .fusa-reqs-pending.json: 48 -> 73 entries (+25, 517 -> 783 lines). include/rcp/i2c.hpp, include/rcp/gpio.hpp: unchanged. tests/test_i2c.cpp: net 0 lines (1 line content-changed). tests/test_adc.cpp: +22 lines. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * fixup: close the tested-coverage gap CI found (REQ-ADC-032/034) Real CI (not just the porting agent's local check) caught a genuine gap this batch's own local verification missed: REQ-ADC-032/034 were traced (had //fusa:req tags) but had zero //fusa:test tags or bracket tags anywhere in the tree -- cpfusa's tested-coverage gate requires tested == total exactly, with no tolerance for "req-only by design" entries, unlike the porting agent's own local read of the situation. REQ-ADC-034 ("ADC sampling occurs only while a request executes, and a compound wait compares the last acquired average without sampling"): the catalog entry's own text already identifies the real mechanism as acf.hpp's compound_wait_match() -- a shared, endpoint-agnostic comparator, not ADC-specific code. Dual-tagged the existing "compound_wait_match exact-match mode" TEST_CASE in test_acf.cpp with [REQ-ADC-034] (bracket tag) and added the matching //fusa:test REQ-ADC-034 manifest line, following this rewrite's established cross-file dual-tagging convention for shared mechanisms. REQ-ADC-032 ("An ADC endpoint serves exactly one channel, is limited to 16-bit resolution, and requires a selected analog input pin"): no existing test pinned the 16-bit wire-format constant directly (every use of kAdcValueLen was incidental, inside unrelated codec tests). Added a real, dedicated TEST_CASE asserting kAdcValueLen == sizeof(uint16_t) == 2, plus the matching manifest line. ## Verification Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass. Independently re-derived cpfusa's own req/test coverage logic in Python against the real .fusa-reqs.json + all include/rcp/ *.hpp + tests/*.cpp files: 782/782 requirements now have both a //fusa:req reference and a //fusa:test-or-bracket reference, zero gaps -- confirming the real CI gate will pass on the next run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --------- Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 9 of 13. ## Scope mdio.hpp's REQ-MDIO-* (c-RCP: 28 entries), pwm.hpp's REQ-PWM-* (75, the largest single per-endpoint prefix in the whole catalog) -- 103 c-RCP entries total. ## Major finding: same pattern as batches 7/8 Both modules' code and tests were already ported ahead of the catalog in an earlier untracked pass -- mdio.hpp/test_mdio.cpp needed zero edits at all (top-of-file manifests already covered the full 28-id c-RCP range). The gap was almost entirely catalog-side, plus 14 pre-existing low-numbered id collisions. ## New catalog entries (87) - MDIO: 21 (REQ-MDIO-008-028) - PWM: 66 (REQ-PWM-010-026, 060-075 minus already-covered/collision ids) ## Id-collision gaps filed (14) - MDIO: 7 (001-007) -- old entries describe unrelated pre-rewrite content (ep_type id, payload width validation); real c-RCP content (addr_valid, burst_next_regad, word_encode/decode, pack_len/words, word_count_of) genuinely implemented+tested under the same bracket numerals -- a legitimate dual-tag overlap, not a gap. - PWM: 7 (001, 004-009) -- verified content collisions directly, e.g. cpp's REQ-PWM-008 carries severity:cybersecurity and describes handle_request Row-2 classification, while c-RCP's real REQ-PWM-008 (RESERVED4 no-op) is unrelated and genuinely tested elsewhere. REQ-PWM-001 is an honest architecture divergence, not force-fit as a collision or a gap: cpp has no validate-and-reject write_semantics_valid(); endpoint::write_semantics_of() masks to 3 bits instead, making every input constructively valid -- same safety property, different shape, cited to test_endpoint.cpp's REQ-ENDPOINT-002 tests. ## Already-covered, no action (2) REQ-PWM-002 (exact 1:1 match), REQ-PWM-003 (cpp's own entry bundles Or/And/Xor/Add/Subtract under one id; OR is a literal subset). ## Real tested-coverage gaps found and closed (2, following batch 8's lesson) Grepped the whole include/+tests/ tree for every id before adding it (per the explicit instruction added to this batch's dispatch after batch 8's CI-caught miss): found REQ-PWM-041 (PWM_IN strerror uniqueness -- cpp shares one PwmErrc/pwm_category() across PWM_OUT/ PWM_IN, unlike c-RCP's two separate functions, so no dedicated test existed) and REQ-PWM-061 (decode_read_request rejects a non-ACF_ABB frame -- no test covered bad_msg_type for PWM_OUT's read-request decode at all). Strengthened the existing REQ-PWM-024 message test into an exhaustive non-empty/distinct-message-per-code loop (dual- tagged [REQ-PWM-024][REQ-PWM-041]), and added a new TEST_CASE for REQ-PWM-061 mirroring test_mdio.cpp's existing pattern for the same check. ## Verification Independently re-verified (the porting agent's own final step re-ran a whole-catalog tag-coverage script covering all 869 entries, not just this batch's 103 -- confirming no regression to batches 1-8): semantic id-keyed diff confirms zero pre-existing entries modified/ deleted, exactly 87 new ids matching the report's per-prefix breakdown exactly (MDIO 21, PWM 66), zero duplicate ids. Independently re-ran my own from-scratch Python tag-coverage check against the merged worktree (not trusting the agent's script output alone): confirmed 869/869 entries have both a real //fusa:req and a real //fusa:test-or-bracket tag somewhere in the tree, zero gaps. Confirmed no new entry carries severity:cybersecurity -- sec-tested count holds exactly at 72/869. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). .fusa-reqs.json: 782 -> 869 entries (+87, 8003 -> 9026 lines). .fusa-reqs-pending.json: 73 -> 87 entries (+14, 783 -> 961 lines). tests/test_pwm.cpp: +42 lines. include/rcp/mdio.hpp, include/rcp/pwm.hpp, tests/test_mdio.cpp: unchanged. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 10 of 13. ## Scope spi.hpp's REQ-SPI-* (c-RCP: 42 entries), uart.hpp's REQ-UART-* (49), wakeup.hpp's REQ-WAKEUP-* (36) -- 127 c-RCP entries total. ## Major finding: mixed -- spi/wakeup already ahead, uart needed real work spi.hpp/test_spi.cpp and wakeup.hpp/test_wakeup.cpp were already fully ported ahead of the catalog in an earlier untracked pass -- zero code edits needed in either. uart.hpp needed genuine work: its req-manifest only covered 9 of 49 ids despite most of the underlying code/tests already existing, plus two genuine tested-coverage gaps this batch's mandatory whole-tree grep caught before they reached CI. ## New catalog entries (106) - SPI: 36 (REQ-SPI-006-030, 033-036, 038-044) - UART: 40 (REQ-UART-010-049, including 048 at c-RCP's own honest status:partial -- not force-fit as fully implemented) - WAKEUP: 30 (REQ-WAKEUP-006-025, 027-036) -- REQ-WAKEUP-020's real implementation lives in regmap.hpp/test_regmap.cpp (not wakeup.hpp), already fully tagged/tested there from an earlier pass; added as a cross-module mapping with no code touch needed. ## Id-collision gaps filed (19) SPI 001-005, UART 001-009, WAKEUP 001-005 -- real behavior implemented and tested under a shared numeral, but the id is already taken by unrelated pre-rewrite catalog content, matching the established pattern from batches 6-9. ## Other gaps filed (2) - REQ-SPI-037 (SPI error-state EP-enable-reset behavior): matches c-RCP's own not-implemented status -- a genuine TC18 §13.7.3.3 spec silence, not force-fit as done. - REQ-WAKEUP-026 (regmap.hpp's ep_id_map::apply_reconfig() never enforces WakeUp's fixed-EP_Nr invariant at write time): the read-only diagnostic half (REQ-WAKEUP-020) exists and is tested; the write-time-denial half does not. Out of this batch's scope since regmap.hpp isn't one of the three in-scope modules -- flagged for whoever picks up REQ-RMAP-*/regmap.hpp (batch 11). ## Real tested-coverage gaps found and closed (3, per batch 8's now-mandatory whole-tree check) - REQ-UART-032 (ep_status register round-trip): added a new apply_reconfig-side test, dual-tagged with the existing render-side test. - REQ-UART-035 (compound-wait bounded by RX FIFO): added a new UART-flavored test exercising acf::compound_wait_match() directly, mirroring SPI's own analogous test. - REQ-UART-048 (partial status, separate-units fields): dual-tagged onto the existing Table 51 register-round-trip test. ## Verification Independently re-verified (the porting agent's own final step re-ran a whole-catalog tag-coverage script covering all 975 entries): semantic id-keyed diff confirms zero pre-existing entries modified/ deleted, exactly 106 new ids matching the report's per-prefix breakdown exactly (SPI 36, UART 40, WAKEUP 30), zero duplicate ids. Independently re-ran my own from-scratch Python tag-coverage check against the merged worktree (not trusting the agent's script alone): confirmed 975/975 entries have both a real //fusa:req and a real //fusa:test-or-bracket tag somewhere in the tree, zero gaps. Confirmed no new entry carries severity:cybersecurity -- sec-tested count holds exactly at 72/975. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). .fusa-reqs.json: 869 -> 975 entries (+106, 9026 -> 10288 lines). .fusa-reqs-pending.json: 87 -> 108 entries (+21, 961 -> 1226 lines). include/rcp/uart.hpp: +58 lines. tests/test_uart.cpp: +103 lines net. include/rcp/spi.hpp, tests/test_spi.cpp, include/rcp/wakeup.hpp, tests/test_wakeup.cpp, include/rcp/regmap.hpp, tests/test_regmap.cpp: unchanged. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
… catalog (#164) * rewrite(phase6): batch 11 — REQ-REGMAP-*→REQ-RMAP-* rename + RMAP/SRV catalog Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 11 of 13 -- the largest single batch so far, combining a real id-field rename with two full-prefix derivations. ## Part A — REQ-REGMAP-* → REQ-RMAP-* rename Pure id-field rename per ROADMAP.md Phase 17's already-established canonical naming (RELAY's docs/RCP-ARCHITECTURE.md cites REQ-RMAP-* as canonical): all 15 REQ-REGMAP-001..015 catalog entries renamed to REQ-RMAP-001..015 (id field only, no other field changed), plus all 15 code tags in include/rcp/regmap.hpp and 36 test tags in tests/test_regmap.cpp. severity:cybersecurity preserved on all 15 (these are part of the enumerated 72-id cybersecurity subset per ci.yml's own comment) -- sec-tested count holds at exactly 72. Zero REQ-REGMAP-* remnants anywhere in .fusa-reqs.json, .fusa-reqs-pending.json, or the include/+tests/ tree (one pre-existing entry's own prose text still literally says "REQ-REGMAP-005" referring to another entry by name -- left untouched, it's descriptive text, not an id). Stale REQ-REGMAP-* mentions remain in CYBERSECURITY.md/TARA-ANALYSIS.md/ ROADMAP.md/ci.yml's comment -- outside this batch's scope, left for a docs pass; the CI gate itself reads the severity field dynamically, not a hardcoded id list, so it's unaffected. ## Part B — REQ-RMAP-* full derivation (87 c-RCP entries) - 59 new catalog entries (016-045, 047-058, 060, 066-067, 070-071, 073-079, 082-084, 086-087), including 055 (already fully tagged in lifecycle.hpp/test_lifecycle.cpp from an earlier batch -- cross-module mapping, no code touch) and two status:partial entries (023, 067) honestly preserved from c-RCP. - 15 id-collision/gap entries filed to .fusa-reqs-pending.json: 001/002/003/009/010/011/012/013/014/015 -- the rename claimed these numerals first for cpp's own pre-existing content; c-RCP's real content at the same numbers (is_ep0(), writer_ctx() grant/withhold, hw_pin bit-layout distinctness, named_signal_string()) is separately implemented+tested, filed as collisions rather than corrupting id uniqueness -- matching the established pattern from batches 6-10. 068/069/072/080: real gaps matching regmap.hpp's own explicit "EP0 address-routed dispatcher NOT ported in this batch" scope note. 081: genuine TC18 spec-defect gap (EP_RESP_ON_ERROR, a Table 31 prose reference with no defined bit/field) -- matches c-RCP's own not-implemented disposition verbatim; cpp additionally has a defensive test proving no bit is invented for it. - 6 retired ids (004-008, 046) -- c-RCP itself retired these (superseded by 030/039); no entry or gap filed, since there's no real c-RCP content for cpp to collide with or gap against. - 15 renamed + 7 pre-existing respqueue-specific (untouched) + 59 new + 15 pending + 6 retired = all 87 c-RCP ids accounted for. ## Part C — REQ-SRV-* full derivation (42 c-RCP entries, all new) include/rcp/server.hpp already carried all 42 fusa:req tags from an earlier untracked pass -- zero code edits needed there (0 line delta). tests/test_server.cpp only had 24 of 42 tested; added 14 new TEST_CASEs plus several dual-tags to close the remaining 18 (019, 020, 022, 024-034, 037-039, 042), including genuinely new coverage for per-kind select_due() gating (027-032), CompoundWait's independent per-slot evt/payload evaluation and wait_tick() application (020, 033, 042), no-sequencer-advance-for-Triggered/Timed/Chained plus always-removed (034, 037), and chain-timer-not-restarted (038). All 42 SRV catalog entries added. No new entry (RMAP or SRV) carries severity:cybersecurity -- confirmed neither c-RCP's real 87 RMAP nor 42 SRV entries mark any as security-relevant; sec-tested count unchanged at exactly 72. ## Verification Independently re-verified (not just the porting agent's own report, which itself ran a whole-catalog tag-coverage script twice, catching and fixing one gap where REQ-RMAP-082 had a test tag but no fusa:req tag before finalizing): semantic id-keyed diff confirms zero pre-existing entries modified, exactly 116 new/renamed ids (15 renamed + 59 new RMAP + 42 new SRV) matching the report's breakdown exactly, zero duplicate ids anywhere (catalog or pending). Independently re-derived the whole-tree tag-coverage check from scratch in Python against the merged worktree (not trusting the agent's script alone): confirmed all 1076 entries have both a real fusa:req and a real fusa:test-or-bracket tag somewhere in include/+tests/, zero gaps. Confirmed severity:cybersecurity was preserved on exactly the 15 renamed entries and granted to zero genuinely-new entries -- sec-tested count holds at exactly 72/1076. Directly spot-checked the REQ-RMAP-009 writer_ctx tag placement, the REQ-RMAP-081 spec-defect gap text, and the new REQ-SRV-027/028/033 select_due()-gating tests against the real diff -- all confirmed accurate. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). .fusa-reqs.json: 975 -> 1076 entries (+116, well over +101 in raw additions once the 15 rename removals are netted out; 10288 -> 11498 lines). .fusa-reqs-pending.json: 108 -> 123 entries (+15, 1226 -> 1420 lines). include/rcp/regmap.hpp: +11 lines net (rename tags only). tests/test_regmap.cpp: +23 lines net (rename tags + dual-tag additions). include/rcp/server.hpp: unchanged (0 line delta). tests/test_server.cpp: +339 lines net (14 new TEST_CASEs + dual-tags). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * fixup: batch 11 -- add missing //fusa:test manifest tags cpfusa's real gate needs Real CI failure on PR #164's cpfusa-trace job: tested=1051 total=1076 (gate requires exact equality). Root cause, diagnosed against the real pinned cpfusa v0.18.0 binary (built locally, not guessed at): cpfusa's trace tool scans for a specific `// fusa:test REQ-ID` comment-line form (src/trace/trace.cpp's test_tag_re) -- it does NOT recognize Catch2 `[REQ-ID]` bracket tags as evidence of testedness at all. My own verification script for this batch (and the porting agent's) checked for either form, which is why both reported false 100% clean. Same lesson as the batch-8 CI incident: verify against the real gate tool, not an approximation of it. 24 REQ-RMAP-* ids + REQ-SRV-017 had real, correct bracket-tagged TEST_CASEs (or, for REQ-SRV-017, real testable content in an adjacent module) but no matching //fusa:test manifest line anywhere in the tree: - 22 ids (018-023, 026-029, 031-033, 035-036, 038-039, 043, 073-075, 082) already had real TEST_CASEs with correct bracket tags -- pure missing-manifest-line fix, no test content changed. - REQ-RMAP-034/037 had zero bracket-tag occurrences anywhere: found the existing "GeneralMap default-constructs zeroed" TEST_CASE already REQUIREs their exact fields (svr_request_stream_cfg_capacity/ svr_response_stream_cfg_capacity for 034; svr_ep_bytebus_id_map_ptr/ _capacity for 037) as part of its zero-init sweep -- matching the exact same depth of coverage already accepted for their sibling Table-20 pointer/capacity fields (026-029/031-033/035-036/038, all covered by this same test). Added the two missing bracket tags to that TEST_CASE's existing tag list; no new assertions needed, the content was already there. - REQ-SRV-017 (status:partial, cross-module by its own catalog text -- server.hpp's part is content-modeling/admission only, not response- queue transmission): dual-tagged onto test_respqueue.cpp's existing "should_flush_by_time is independent of queue state" TEST_CASE (REQ- RMAP-065), which already asserts the exact claim (an empty queue still fires the Flush_time trigger) before any push() -- the real, and only, test target for this id's actual behavior. Re-verified against the real pinned cpfusa binary (built locally from the same v0.18.0 tag CI uses, not approximated): tested=1076 total=1076, cpfusa trace --gaps returns zero requirement ids. Dangling- tag warning count unchanged at 26 (all pre-existing, out-of-scope DISC/SHMEM ids -- batch 12's territory, not touched here). Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). tests/test_regmap.cpp: +30 lines (manifest tags + 2 bracket tags on one existing TEST_CASE). tests/test_respqueue.cpp: +7 lines (manifest tag + dual-tag + explanatory comment on one existing TEST_CASE). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --------- Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 12 of 13. ## Scope discovery.hpp's REQ-DISC-* (c-RCP: 30 entries), adapt.hpp's REQ-RELAY-* (c-RCP: 17 entries) -- 47 c-RCP entries total. ## Major finding: DISC already far ahead of the catalog, RELAY needed real work discovery.hpp/test_discovery.cpp already carried real fusa:req/bracket-tag citations for nearly the full 001-030 range from an earlier untracked pass (only 5 missing //fusa:req manifest lines for 025-028/030) -- almost entirely a catalog-side derivation. adapt.hpp/test_adapt.cpp only covered cpp's own pre-rewrite REQ-RELAY-001-005 numbering; the real c-RCP 006-017 range needed genuine investigation against c-RCP's per-op field-table design vs cpp's simpler generic-RequestFn shape. ## Id-collision pattern (same class as batches 6-11) Both prefixes' pre-existing low-numbered ids were assigned before this rewrite's "reuse c-RCP's exact id" convention took hold and collide with unrelated, real c-RCP content at the same numerals (verified directly, e.g. cpp's REQ-DISC-001 "Discovery request framing" vs c-RCP's real REQ-DISC-001 "should_drop flags non-NTSCF subtype" -- unrelated). Filed as 14 id-collision gaps (DISC 001-009 x9, RELAY 001-005 x5) to .fusa-reqs-pending.json -- in every DISC case the real c-RCP behavior turned out to already be correctly implemented+tested under the matching numeral, only the catalog entry was stale/collided. ## New catalog entries (29) - DISC: 21 (010-030) -- genuinely implemented+tested already; 5 needed their missing //fusa:req manifest tag added (025-028, 030). - RELAY: 8 (006, 008, 009, 010, 012, 014, 016, 017) -- genuinely implemented+tested, with text adapted to document where cpp's generic-RequestFn design diverges from c-RCP's per-op field table (no failure path on 006, no bound-kind check on 008/009, no transport/kind params on 012). 014/016/017 actually live in relay.hpp/rcp.hpp rather than adapt.hpp -- tagged in adapt.hpp per this repo's existing convention (001-005 already do the same), with test tags placed in test_relay.cpp where the real tests live. Added 2 small missing tests (Channel::is_closed(), to_string(Protocol) uniqueness) to genuinely earn 002's and 016's claims. ## Architecture-divergence gaps filed (4, RELAY only, deliberate not oversights) REQ-RELAY-007 (no per-op opcode enum -- explicitly documented in test_adapt.cpp's own header comment as intentionally unported), REQ-RELAY-011 (no adapt-specific error category/strerror -- std::errc reused instead), REQ-RELAY-013 (no RCP-namespaced SpecVersion alias -- single-namespace architecture has nothing to duplicate, c-RCP's two- package split doesn't apply here), REQ-RELAY-015 (no manual caller retain/refcount -- superseded by std::unique_ptr RAII, also explicitly documented as unported in test_adapt.cpp). No new entry (DISC or RELAY) carries severity:cybersecurity -- confirmed via ci.yml/CYBERSECURITY.md that only cpp's existing (untouched) REQ-DISC-001..009 are in the enumerated 72-id cybersecurity subset; REQ-RELAY-* never appears there. sec-tested count unchanged at exactly 72. ## Verification Independently re-verified (the porting agent's own final step re-ran the real pinned cpfusa v0.18.0 binary, not an approximating script -- the batch-11 lesson applied from the start this time): semantic id-keyed diff confirms zero pre-existing entries modified/deleted, exactly 29 new ids matching the report's breakdown exactly (DISC 21, RELAY 8), zero duplicate ids. Independently re-ran the real cpfusa binary myself against the merged worktree: testedRequirements == totalRequirements == 1105, cpfusa trace --gaps returns zero ids. Confirmed sec-tested count holds at exactly 72/1105. Directly spot-checked the REQ-DISC-001 collision claim and the REQ-RELAY-013 architecture-divergence gap text against the real diff -- both accurate. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). .fusa-reqs.json: 1076 -> 1105 entries (+29, 11498 -> 11836 lines). .fusa-reqs-pending.json: 123 -> 141 entries (+18, 1420 -> 1600 lines). include/rcp/adapt.hpp: +19 lines (tags only). include/rcp/discovery.hpp: +5 lines (tags only). tests/test_adapt.cpp: +5 lines (tags only). tests/test_relay.cpp: +36 lines (tags + 2 new tests). Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
… 6 complete) (#166) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 13 of 13 -- the FINAL batch, covering the two largest files in the codebase (mock.hpp/test_mock.cpp) plus genuinely new PWRMODE porting work. ## Scope mock.hpp's REQ-MOCK-* (c-RCP: 33 entries), powerstate.hpp's REQ-PWRMODE-* (c-RCP: 28 entries, split across c-RCP's power.c+powerstate.c, folded into cpp-RCP's single powerstate.hpp per this rewrite's earlier design choice) -- 61 c-RCP entries total. Plus mandatory pickup of 5 mock.hpp-scoped deferred gaps from batches 5 and 6. ## Part C — deferred-gap pickup (mandatory, done first) REQ-WDG-010 (batch 5), REQ-E2E-033/039/041/047 (batch 6): all confirmed still genuinely implemented+tested in mock.hpp/test_mock.cpp exactly as found originally -- promoted from .fusa-reqs-pending.json straight into .fusa-reqs.json with real fusa:req/fusa:test manifest tags added (they'd only ever had prose/bracket-tag mentions, never a real manifest tag). ## REQ-MOCK-* (33 c-RCP entries) 3 new catalog entries (027 pending_count, 028 REQUEST_NOT_FOUND, 029 CHAIN_ERROR/ABORTED -- free ids with real pre-existing tests, now manifest-tagged). All 26 of cpp's pre-existing REQ-MOCK-001..026 are confirmed id-collisions (same pattern as every batch since 6 -- cpp's own entries describe unrelated Table-33 evt-classification content numbered before this rewrite's id-reuse convention). Filed to pending with per-id verification: 15 collision ids are separately implemented +tested elsewhere in cpp (cross-referenced to REQ-LIFECYCLE-*/REQ-SRV-*/ REQ-CANCEL-*), 1 not-applicable (C++ RAII supersedes a manual-refcount requirement), 10 genuine gaps (mostly cpp's fixed-10-endpoint architecture lacking c-RCP's dynamic add/remove-endpoint API). Free ids 030-033 (stream-scoped lookup family): genuine architectural gaps, filed honestly rather than force-fit. ## REQ-PWRMODE-* (28 c-RCP entries, genuinely new porting work) Unlike every other batch this phase, powerstate.hpp had ZERO existing REQ-PWRMODE-* citations to walk -- this required reading c-RCP's power.c+powerstate.c source directly against cpp's powerstate.hpp. 20 new catalog entries (19 investigated + 028, which had code tags but no catalog entry). Implemented 3 small, safe fixes with new tests: cold_start_lifecycle_target() (closes 003+014), and a backward- compatible network_available parameter on begin_wake_from_sleep() (closes 016). REQ-PWRMODE-004 was initially assessed as a small fix but reclassified to a genuine architectural gap on closer inspection: the literal transition-table fix would contradict an already-tested, protected REQ-PWR-002 assertion -- filed to pending rather than forcing a design decision that isn't this batch's to make. 8 total architectural gaps filed (004, 012, 017, 019, 021, 022, 026, 027), most notably 026 (mode commits before the sleep/standby response is confirmed transmitted -- a real safety-relevant ordering gap, not cosmetic). No new entry (MOCK or PWRMODE) carries severity:cybersecurity -- verified against CYBERSECURITY.md/ci.yml and c-RCP's own severity fields. sec- tested count unchanged at exactly 72. ## Verification Independently re-verified (the porting agent's own final step re-ran the real pinned cpfusa v0.18.0 binary against the WHOLE catalog, not just this batch, to catch any regression across all 12 prior batches): semantic id-keyed diff confirms zero pre-existing entries modified/ deleted, exactly 28 new ids matching the report's breakdown exactly (MOCK 3, PWRMODE 20, promoted-deferred 5), zero duplicate ids in either the catalog or the pending file. Independently re-ran the real cpfusa binary myself against the merged worktree: testedRequirements == totalRequirements == 1133, cpfusa trace --gaps returns zero ids. Confirmed sec-tested count holds at exactly 72/1133. Directly spot-checked the REQ-PWRMODE-004 reclassification and REQ-WDG-010's promotion against the real diff -- both accurate, and the 004 reclassification shows genuinely careful judgment (catching a real conflict rather than force-fitting a fix). Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). ## Phase 6 complete All 13 batches merged. Final catalog: .fusa-reqs.json 1105 -> 1133 entries (+28, all 100% traced and tested, zero regressions across all 13 batches, independently re-verified via a fresh whole-tree cpfusa trace). Final pending/gap catalog: .fusa-reqs-pending.json 141 -> 174 entries (+38 added, -5 promoted = net +33), honestly documenting every id-collision and architectural gap found across the whole phase. The whole-catalog traceability gate (testedRequirements == totalRequirements) is clean. A detailed Phase 6 completion summary will be posted to issue #129 separately. include/rcp/mock.hpp: +8 lines (tags only). include/rcp/powerstate.hpp: +73 lines (2 new functions + tags). include/rcp/wakeup.hpp: +1 line (tag only). tests/test_mock.cpp: +8 lines (tags only). tests/test_powerstate.cpp: +76 lines (2 new test blocks). tests/test_wakeup.cpp: +1 line (tag only). Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
…A+ specs, add TLC CI job (#167) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 7 (formal verification + MC/DC + release-pipeline hardening), batch 1 of several: TLA+ formal verification only. ## Scope cpp-RCP had 3 TLA+ specs (WatchdogSafeState, RxSequenceGuard, CrcSafeStateLatch) but was missing c-RCP's LifecycleStateMachine and E2ESafePoint specs -- and, a real pre-existing gap discovered during scoping, none of the 3 existing specs were ever actually run in CI. ## Part A -- ported specs (both pass TLC clean) tla/LifecycleStateMachine.tla (+229) + .cfg (+10): models rcp::lifecycle::ServerLifecycle::transition()/check_hw_cfg()/ check_rcp_cfg(). TLC: 12 distinct states, depth 5, no error. tla/E2ESafePoint.tla (+215) + .cfg (+17): models rcp::e2e::RxWatchdog/ apply_watchdog_overflow()/endpoint_in_configured_safe_state()/ may_execute_now(). TLC: 256 distinct states, depth 7, no error. Both cross-referenced directly against cpp-RCP's include/rcp/lifecycle.hpp and include/rcp/e2e.hpp against c-RCP's src/lifecycle.c/src/e2e.c -- confirmed field-for-field ports from Phase 2's content-parity pass, so both are faithful direct ports, not mechanical transliterations. No implementation bugs found; no C++ source touched. One known abstraction gap documented (not fixed, correctly out of this batch's scope): cpp's field_writable() computes FunctionalWStar locking as a pure function of current ServerState, and transition() supports an additional RcpConfigured->HwConfigured partial demotion that neither this spec nor c-RCP's original models. c-RCP's own spec already abstracts this away (sticky field_lock variable, omits the demotion transition); this port preserves that same abstraction and flags it in a "Known abstraction gap" comment block for future re-derivation. Independently spot-checked against RxSequenceGuard.tla's own FORMAL_VERIFICATION.md: a separate, genuinely pre-existing gap there (the spec still models pre-Phase-2 non-wraparound sequence comparison, not the real RFC 1982 forward-window rule evaluate() implements today) is confirmed untouched by this batch (byte-identical diff against base) -- correctly out of scope, a distinct re-derivation task. ## Part B -- TLC CI automation (new gap closed for all 5 specs, not just 2) New `formal-verification` job in .github/workflows/ci.yml (+44 lines), following c-RCP's own ci.yml job as a structural reference but written in cpp-RCP's own job-naming/style conventions: downloads tla2tools.jar once, then runs TLC against all 5 specs in tla/ (the pre-existing 3 plus the 2 new ports), each as its own step so a single spec's failure is individually attributable in the Actions UI. No `needs:` -- TLC checking is independent of the C++ build, matching c-RCP's own pattern. ## Verification Independently re-verified: rebuilt tla2tools.jar locally and re-ran TLC against all 5 specs myself (not trusting the agent's own run) -- all 5 report zero errors/violations/deadlocks (WatchdogSafeState 104 states, RxSequenceGuard 32, CrcSafeStateLatch 4, LifecycleStateMachine 12, E2ESafePoint 256). Validated the new ci.yml job's YAML parses cleanly and has the expected 7-step structure. Directly spot-checked the "Known abstraction gap" comment's honesty against real lifecycle.hpp behavior, and confirmed the RxSequenceGuard pre-existing-gap claim via a byte-identical diff of FORMAL_VERIFICATION.md against the pre-batch base. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%) -- unaffected, since no C++ source was touched. tla/LifecycleStateMachine.tla: new, +229. tla/LifecycleStateMachine.cfg: new, +10. tla/E2ESafePoint.tla: new, +215. tla/E2ESafePoint.cfg: new, +17. .github/workflows/ci.yml: +44 (765 -> 809 lines). Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
#168) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 7 (formal verification + MC/DC + release-pipeline hardening), batch 2: real MC/DC (Modified Condition/ Decision Coverage) evidence in CI. cpp-RCP had zero MC/DC infrastructure before this batch (verified: no mcdc/MCDC/coverage-mcdc references anywhere in .github/workflows/ci.yml). ## What this adds Ported c-RCP's existing `mcdc:` CI job (LLVM 18, real llvm-cov/llvm-profdata condition/decision instrumentation, ratchet-gated -- not a 100% gate), adapted for cpp-RCP's header-only C++ architecture: - CMAKE_C_FLAGS -> CMAKE_CXX_FLAGS, CXX: clang++-18 (not CC:). - -sources scoped to include/rcp/*.hpp (cpp-RCP has no src/ dir -- verified; all real implementation logic lives in headers, not the test files), matching c-RCP's own intent of measuring implementation code, not test code. - `cpfusa coverage --mcdc-file` confirmed to have the same class of bug as c-RCP's `cfusa` equivalent: it parses `covered_true_count`/ `covered_false_count` object keys that real `llvm-cov export -format=text` output never produces (verified directly against both cpp-FuSa's source and real export output -- the schema is a positional array, no such keys anywhere). Followed c-RCP's precedent: the job reads llvm-cov export's own totals.mcdc block directly. ## Floor: 60%, set from a real freshly-measured baseline Not copied from c-RCP's own 93% (that number reflects c-RCP's C codebase's own current state, meaningless for cpp-RCP's). Measured locally end-to-end (instrumented build -> 58/58 tests passing under instrumentation -> profile merge -> llvm-cov export) on Homebrew LLVM 18.1.8 (matching the CI job's clang-18 major version): 313/466 = 67.17%. Cross-checked on LLVM 22.1.8: 426/629 = 67.73% -- close agreement despite a 4-major-version gap, evidence the number reflects the test suite, not toolchain noise. Floor set with ~7-point margin below the clang-18-matched measurement, wider than c-RCP's own 2.3-point margin because this measurement also crosses macOS/arm64 (local) vs Ubuntu 22.04/x86_64 (CI) and Homebrew-built vs apt.llvm.org-built clang-18 -- either could plausibly shift condition-pair counts on a header-only codebase (many decisions live in platform-conditional code, e.g. rcp/l2.hpp's raw-socket paths) more than a same-OS compiler bump would. Provenance written into the gate step's own comment and AUDIT_PACK.md §3/§5. ## Verification Independently re-verified (not just the porting agent's own measurement): reproduced the entire instrumented pipeline myself from scratch on the same Homebrew LLVM 18.1.8 toolchain -- 58/58 tests pass under instrumentation, real llvm-cov export produces a non-empty totals.mcdc block: 315/468 = 67.31%, matching the report's 313/466 = 67.17% almost exactly (trivial build-to-build variance, well within the ~7-point floor margin either way). Directly confirmed the cpfusa --mcdc-file schema mismatch by inspecting both cpp-FuSa's parser source and a real llvm-cov export's actual JSON structure side by side. Validated the new/modified ci.yml job's YAML parses cleanly. Full non-instrumented tree rebuilt from scratch separately: 0 errors, 0 warnings, 58/58 ctest suites pass (100%) -- confirming this batch doesn't affect the normal build path at all. .github/workflows/ci.yml: +192 lines (809 -> 1001). AUDIT_PACK.md: +18 lines (140 -> 158, real-MC/DC provenance paragraph in §3 + new row in the §5 CI Gate Summary table). Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
…CHANGELOG (#169) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 7 (formal verification + MC/DC + release-pipeline hardening), batch 3 -- closes the remaining Phase 7 scope: release-pipeline hardening, .fusa-dispositions.json, CHANGELOG.md. ## version-sources-agree (new CI job) Ported from c-RCP's own equivalent, adapted for cpp-RCP's real version.hpp syntax (a constexpr std::string_view, not a #define macro like c-RCP's). While building this, found and fixed a real bug: .fusa.json had no real project.version field at all -- cpfusa's config loader was silently defaulting to "0.1.0" for every artifact it has ever generated for this repo. Added "version": "2.26.0" to .fusa.json's project object. ## cpfusa cyber (renamed from its stale "--strict" name) The job's name claimed --strict but its run: line never passed it -- --strict is a real flag, but cpp-RCP does not cleanly pass under it today (838 WARNING-severity findings, overwhelmingly the expected shape of a wire-protocol library's byte-packing/masking idioms and test-suite loopback addresses, not defects -- but also not several hundred call sites to responsibly mass-review or mass-waive in one batch). Renamed the job to match its real behavior rather than either rushing the findings or quietly keeping a misleading name. Also confirmed .fusa-dispositions.json structurally cannot fix this either way: cyber's --strict check never consults dispositions, unlike check/lint/analyze's gated_exit_code(). ## cpfusa analyze (real gate now, || true removed) Root-caused rather than just removing the escape hatch: the job's own build step left Catch2's vendored FetchContent source (including Catch2's own SelfTest suite) inside build/, which cppcheck then misreported as first-party ERROR findings on Catch2's own BDD macros -- that's what was actually keeping the job red, not a cpp-RCP defect. Separately, compile_commands.json was left inside build/ while analyze looks for it at the project root, so clang-tidy had never actually run in this job at all. Fixed by copying compile_commands.json to the root then deleting build/ before invoking analyze, so the scan only sees first-party sources. With both fixed: 0 errors, real hard gate. ## .fusa-dispositions.json (new, legitimately empty) Neither cpfusa lint (0 errors/138 warnings) nor the now-correctly-scoped cpfusa analyze (0 errors/269 warnings/161 info) has any ERROR-severity finding needing a waiver -- {"dispositions": []} is the honest state, not a placeholder. Confirmed the mechanism itself (rule-ID-only matching, a blanket per-rule waiver, not per-fingerprint) is reserved for genuinely rule-wide architectural situations like c-RCP's own alloc.c precedent -- no such situation exists in cpp-RCP's current ERROR-level findings. ## release.yml: two new steps IEC 62443 gap report (SL-2, artifact-only, mirrors the existing ISO26262/IEC61508/DO-178C siblings) via cpfusa iec62443 --sl SL-2. Shipped-artifact version verification: adapted from c-RCP's step, not copy-pasted -- cpp-FuSa v0.18.0's release/qualify commands hardcode their own module name in sbom.json/provenance.json/etc. regardless of target project (confirmed by running cpfusa release and inspecting output), so c-RCP's literal field-path doesn't apply. Uses report.html's real "cpp-RCP v{version}" field (sourced from .fusa.json) plus a direct version.hpp/.fusa.json re-comparison, since version-sources-agree doesn't run on tag pushes. ## CHANGELOG.md (new) Keep a Changelog format (no prior convention existed). Single [Unreleased] -> v3.0.0 section grouped by Phase 1-7, one line per real merged PR pulled from rewrite/v3-from-c-rcp's actual git history -- no fabricated pre-rewrite history. ## Verification Independently re-verified: both workflow YAML files parse cleanly. Reproduced the version-sources-agree grep logic myself against the real files (2.26.0/2.26.0/2.26.0, match) and against a deliberately-mutated copy (correctly detects mismatch). Reproduced cpfusa cyber's real exit codes myself: 838 warnings, exit 0 without --strict, exit 1 with --strict -- matching the report exactly. Reproduced the fixed cpfusa analyze pipeline myself from a clean build (compile_commands.json copied to root, build/ deleted before scanning): 0 errors, 269 warnings, 161 info -- matching the report's 0/269/160 within a single info-finding's worth of trivial build-path variance. Confirmed .fusa-dispositions.json's empty state against both tools' real current output. Spot-checked CHANGELOG.md's entries against real git history -- verbatim PR titles, not fabricated. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). .fusa.json: +1 line. .github/workflows/ci.yml: +86/-2 (1001 -> 1083 lines ... wait, actual diff: +84 net). .github/workflows/release.yml: +59 lines (248 -> 307). .fusa-dispositions.json: new, 3 lines. CHANGELOG.md: new, 113 lines. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
…inject/ratelimit/mdns/authz/relay/bridges) (#170) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 8 (remaining modules), batch B of 2: tsn, powerstate, faultinject, ratelimit, mdns, authz, relay/relay.hpp, restbridge/grpcbridge -- all already fully implemented+tested from an earlier untracked pass; this batch is a content-parity audit against c-RCP, not a rewrite. ## Findings, per module - tsn.hpp: already equivalent within a documented architectural divergence (c-RCP wraps a full AVTP transport + raw-frame priority classifier; cpp-RCP exposes a primitive apply_priority() called with an already-known RequestCategory) -- pre-documented in-header, no action. Catalog observation only (c-RCP's REQ-TSN-007/008 have no cpp-RCP analog, by design) -- left untouched, Phase 6 is closed. - powerstate.hpp: no new findings beyond the 8 already-filed pending gaps from Phase 6 batch 13 (REQ-PWRMODE-004/012/017/019/021/022/ 026/027) -- confirmed NOT re-litigated. Confirmed wakeup.hpp (not powerstate.hpp) carries the actual SleepCMD/WakeUp wire codec equivalent to c-RCP's ep_wakeup.h -- a layering difference, not a missing capability. No changes. - faultinject.hpp: capability-equivalent (Drop/Slow/Error/Timeout, count-limited rules). Notably its pick_rule() already fixes a real use-after-free/data-race an earlier revision had, documented in-file as cpp-RCP-D6 -- arguably more correct than c-RCP's own mechanism. No action. - ratelimit.hpp: capability-equivalent (token bucket, safety-tag exemption, per-domain keying). Bootstrap/refill semantics match exactly. No action. - mdns.hpp: capability-equivalent (Discoverer/Announcer, StaticDiscoverer, make_instance_name). One cosmetic-only divergence (hex case, service type string) -- not a wire-interop requirement since mDNS records are self-contained. No action. - authz.hpp: genuine capability-granularity gap found, documented in-file rather than ported -- c-RCP keys policy entries on an opaque request_type byte (letting a policy distinguish read vs write within one request kind); cpp-RCP's RequestCategory taxonomy is coarser and cannot express that distinction. Pre-existing since v2.11.0, already justified at length in the header's own comment, and changing it would break the already-tested PolicyEntry/permit() contract -- out of this batch's authority. Documented as a known granularity gap for a possible future targeted fix, not force-fit here. - relay/relay.hpp: confirmed structurally distinct from adapt.hpp (relay.hpp is the protocol-agnostic Channel<T>/Context/Node abstraction; adapt.hpp is the RCP-specific wiring on top, already covered by Phase 6 batch 12). c-RCP's own relay.h header states outright it is "a pure-C port of the same subset cpp-RCP's include/relay/relay.hpp exposes... not a full RELAY binding" -- cpp-RCP's version is the reference here, confirmed full parity (superset, even -- templated Channel<T> vs one concrete type). No action. - restbridge.hpp/grpcbridge.hpp: both compile-time interface stubs on both sides (no backend linked, every call returns not-supported). c-RCP's own header comments confirm they mirror cpp-RCP's stub behavior. Config shapes and defaults match exactly. No action. ## Verification Independently re-verified: confirmed the c-RCP relay.h "pure-C port of cpp-RCP's own relay.hpp" claim, the restbridge.h "mirrors cpp-RCP's own stub" claim, and the faultinject.hpp cpp-RCP-D6 use-after-free-fix comment all directly against source -- all accurate. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). include/rcp/authz.hpp: +17 lines (comment only, zero behavior change). No other files touched. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
…g/cli + dyndata/redundancy/sim re-verify) (#171) * rewrite(phase8): batch A — content-parity audit (record/observe/config/cli + dyndata/redundancy/sim re-verify) Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 8 (remaining modules), batch A of 2: record, observe, config, cli -- all already implemented+tested from an earlier untracked pass; audited against c-RCP for genuine capability gaps. Plus dyndata/redundancy/sim (no c-RCP equivalent) re-verified for integration/staleness against the now much more mature Phase 5-7 core. ## record.hpp vs c-RCP recorder.c/recorder.h No functional gaps -- both sides deliberately diverge to different capture granularities (c-RCP: raw ACF/AVTPDU frames; cpp-RCP: RC-Client request/response pairs), already documented on both sides. Closed 5 real test gaps instead: write_binary()/read_binary() failure paths, mutation-immunity of stored entries, Playback::run_all()'s actual-sleep branch (previously only exercised at speed_factor=0.0), PlaybackConfig{} default. ## observe.hpp: genuine gaps ported - New standalone observe::record() free function (mirrors c-RCP's rcp_observe_record()) -- lets a caller record a span/counters directly without an ObservingClient wrapper; ObservingClient::request() now delegates to it. - Span::stream_key added (previously byte_bus_id only, unlike c-RCP's full addr). - Metric/MetricsSink::record_counter gained byte_bus_id (previously stream_key only, losing per-endpoint attribution within a stream). 7 new tests added. ## config.hpp: largest gap, includes a real latent bug fix c-RCP's config.c had a later rework (server/hw_pin_map schema feeding mock.c) cpp-RCP never picked up. Ported the two pieces reachable without touching mock.hpp (out of scope): ServerManifest + apply_to_mock() with c-RCP's exact merge semantics (magic overwritten only if nonzero, options OR'd not overwritten), HwPinManifestEntry + capacity-checked application to RegisterMap::hw_pin_map, load_to_mock() combinator. NOT ported (documented, deliberate): c-RCP's dynamic endpoints/streams sections -- mock::Server has no dynamic endpoint-registration API, fixed at compile time. Real bug fixed: the endpoint-entry parser only routed an object to parse_endpoint_entry if BOTH stream_key and byte_bus_id keys were present in the raw scan -- an entry with only one was silently DROPPED rather than rejected. The same bug class c-RCP's own config.c independently found and fixed (per its own 2026-08-13/#338 comment). Fixed by routing on EITHER key, with parse_endpoint_entry itself now the one place that validates both are genuinely present (throwing ParseError if not) -- a malformed entry is now loudly rejected, not silently dropped. 15 new tests added. ## cli.hpp: genuine gaps ported - capabilities_json()'s features array now reports the 5 REQ-RMAP-030 option names (time_sync/enhanced_cancel/trigger/chained/ compound_bundles) -- cpp-RCP has real underlying implementations for all 5 but never advertised them. - transports now includes "tsn" (a real, non-stub wrapper), matching c-RCP's own equivalent module. 5 new tests added (previously-untested help/--help/-h dispatch, dangling --format value, status default text, new capabilities fields). ## dyndata.hpp / redundancy.hpp / sim.hpp (no c-RCP equivalent) Confirmed clean, no changes: every call site verified to match current lifecycle.hpp/e2e.hpp/regmap.hpp/watchdog.hpp/adapt.hpp/mock.hpp shapes exactly; test coverage confirmed real (genuine construction, real state-transition assertions, meaningful concurrency stress, real mock::Server/Adapt() integration), not vestigial. ## Catalog flags, correctly left untouched (Phase 6 is closed) - REQ-REC-006/007, REQ-OBS-009..022, REQ-CFG-007..020, REQ-CLI-005/006: various no-cpp-counterpart or cross-language content-mismatch ids. - REQ-RED-001/002/003: catalog titles still say "RedundantController" (the retired class name) though text/code/tests correctly say RedundantRequestFn -- a catalog-only staleness artifact. ## Verification Independently re-verified: confirmed the config.hpp routing fix directly against source -- the "else if" now reads `obj.find("stream_key") != npos || obj.find("byte_bus_id") != npos` (was implicitly AND-gated before), and parse_endpoint_entry itself throws ParseError if byte_bus_id is missing -- exactly the claimed fix. Confirmed the REQ-RED-* title-staleness claim directly against .fusa-reqs.json (titles say "RedundantController", code/tests say "RedundantRequestFn" 12/13 times respectively). Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). include/rcp/cli.hpp: +28 (619->647). include/rcp/config.hpp: +267 (172->439). include/rcp/observe.hpp: +43 (168->211). include/rcp/ record.hpp, dyndata.hpp, redundancy.hpp, sim.hpp: unchanged. tests/test_cli.cpp: +37. tests/test_config.cpp: +156. tests/test_observe.cpp: +144. tests/test_record.cpp: +85. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> * fixup: batch A — fix MSVC /W4 narrowing warning in test_observe.cpp PR #171's windows-2022/msvc/C++17 job failed: a braced-init-list {3, 1, 4, 1, 5} deduces std::initializer_list<int>, and binding each element to a range-for variable of avtp::ByteBusId (uint16_t) is a narrowing conversion MSVC's /W4 /WX flags as error C4244 (clang/gcc did not flag this, or flagged it differently, so it wasn't caught locally on macOS/Linux toolchains). Fixed by making each list element's type explicit (avtp::ByteBusId{3} etc.), eliminating the narrowing. Verified: rebuilt test_observe standalone locally, 54/54 assertions pass. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --------- Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
SoundMatt
added a commit
that referenced
this pull request
Aug 22, 2026
Final step of the v3.0.0 cutover (cpp-RCP issue #129, ROADMAP.md Phase 17). CMakeLists.txt, include/rcp/version.hpp, and .fusa.json all still declared 2.26.0 after merging the rewrite branch (#172) -- bumped all three together (verified they agree, matching the version-sources-agree CI gate's own check) so the v3.0.0 git tag doesn't ship a mismatched version declaration. Also promotes CHANGELOG.md's [Unreleased] section to a real [3.0.0] release entry. Verified: fresh rebuild, 0 errors/0 warnings, 58/58 ctest suites pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
10 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v3.0.0: full ground-up rewrite, ported from c-RCP
Final cutover for the cpp-RCP v3.0.0 ground-up rewrite (issue #129, ROADMAP.md Phase 17: "TC18 RCP — General Availability, first release where cpp-RCP is the OPEN Alliance TC18 Remote Control Protocol"). This merges the long-lived
rewrite/v3-from-c-rcpbranch, where the entire codebase was rebuilt against c-RCP's current RC5-conformant, atomicity-audited, ASIL-D-hardened implementation as the content source of truth, re-expressed in idiomatic modern C++.What this covers (all phases of the original rewrite plan, now complete)
.fusa-reqs.jsonrequirement catalog re-derivation from c-RCP's current catalog. 412 → 1133 entries, 174 genuine gaps/id-collisions honestly documented in.fusa-reqs-pending.jsonrather than force-fit, zero tag-coverage gaps (confirmed against the real pinnedcpfusabinary).version-sources-agreegate, turned two silently-softened safety gates into real ones,.fusa-dispositions.json,CHANGELOG.md).record/observe/config/cli/tsn/powerstate/faultinject/ratelimit/mdns/authz/relay.hpp/live bridges/dyndata/redundancy/sim) audited for content parity against c-RCP; found and fixed a real latent bug inconfig.hpp's manifest parser (a malformed entry was silently dropped instead of rejected).Verification (this cutover, in addition to every individual batch's own independent re-verification before merge)
ctest: 58/58 suites passing (100%).cpfusa check: 0 errors (1 pre-existing, out-of-scope warning already present onmainbefore this rewrite — a project-ASIL-vs-hazard-ASIL declaration mismatch, unrelated to this rewrite's content).cpfusa trace: 1133/1133 requirements traced and tested, 0 tag-coverage gaps.WatchdogSafeState,RxSequenceGuard,CrcSafeStateLatch,LifecycleStateMachine,E2ESafePoint): TLC reports zero errors/violations/deadlocks.After merge
Tag
v3.0.0and update RELAY'sdocs/RCP-ARCHITECTURE.md.