TLA+ specifications are located in tla/. They cover the three
independent primitives rcp/e2e.hpp and rcp/watchdog.hpp provide for the
safety-critical subsystems identified in the HARA (see HARA.md),
re-derived at the Certification Refresh milestone (v2.18.0) against the
actual v2.6.0/v2.10.0 mechanism rather than the pre-replacement,
Zone/Command-shaped model the previous revision of this document described.
| Spec | Module | Safety Property |
|---|---|---|
RxSequenceGuard.tla |
e2e::RxSequenceGuard monotonic sequence check |
SP1: high-water mark never decreases; SP2: no stale acceptance |
CrcSafeStateLatch.tla |
e2e::RxStreamGuard E2E CRC drop-vs-latch rule |
SP1: no latch without rx_enforce_e2e; SP2: latch only cleared by an explicit reset |
WatchdogSafeState.tla |
e2e::RxWatchdog/watchdog::StreamWatchdog timeout + safe-state latch |
SP1: no latch without rx_wd_safestate_enable; SP2: latch only cleared by an explicit reset |
tla/AntiReplayGuard.tla, tla/HealthStateMachine.tla, and
tla/WatchdogProtocol.tla are retired as of this milestone: the first
modelled the pre-replacement 32-entry sliding-window bitmap guard removed
at v2.6.0, and the latter two both modelled the same three-state
Healthy/Degraded/Faulted watchdog machine, driven by a client-side
periodic CommandType::Watchdog kick, discarded outright at v2.10.0. The
current watchdog mechanism has no Degraded intermediate state and is kicked
by any accepted inbound request, not a dedicated command.
Specs are verified with the TLC model checker (tla2tools.jar, TLA+
Toolbox ≥ 1.7). Each spec's like-named .cfg file in tla/ supplies the
constants, INIT/NEXT, and the invariants/properties TLC checks — TLC
loads it automatically when invoked without -config:
java -jar tla2tools.jar -workers 4 tla/RxSequenceGuard.tla
java -jar tla2tools.jar -workers 4 tla/CrcSafeStateLatch.tla
java -jar tla2tools.jar -workers 4 tla/WatchdogSafeState.tlaExpected output: Model checking completed. No error has been found.
CrcSafeStateLatch.cfg and WatchdogSafeState.cfg each check their
respective spec's affirmative latch behavior under
EnforceLatch/SafestateEnabled = TRUE; the complementary
NoLatchWithoutEnforce/NoLatchWithoutEnable property is non-vacuous only
under the FALSE configuration — flip the relevant CONSTANT line and
re-run to check it directly (see each .cfg file's own comment).
A stream's high-water mark (last_seq) never decreases, and a sequence
number no greater than the current high-water mark is never subsequently
accepted. Unlike the pre-replacement sliding-window bitmap this spec
supersedes, there is no window to exhaust — acceptance is a single
comparison against the high-water mark.
STATUS CORRECTED 2026-08-21 (cpp-RCP issue #129 / RELAY Phase 17 Phase 2 pass) — two distinct corrections, both documentation-only:
- Wiring gap, now closed for this codebase's own reference dispatch.
This section previously stated that
RxSequenceGuardwas never instantiated anywhere outside its own unit test — not inmock::Server's dispatch, and not in any transportServer. That was accurate as of the 2026-08-21 pass that added this note, but is superseded by Phase 4 batch C, "mock.hpp batch C — wire RxSequenceGuard, StreamFaultTracker, RxWatchdog" (CHANGELOG.md, cpp-RCP issue #129, PR #148):rcp/mock.hpp'sServernow holds astd::array<e2e::RxSequenceGuard, ...> seq_trackers_member, and bothServer::dispatch_e2e()andServer::dispatch_frame_e2e()call it (via a sharedseq_gate_admits()helper) on every dispatched request/frame before CRC unwrap — integration-tested intests/test_mock.cpp("dispatch_e2e's sequence gate (REQ-E2E-028/029) rejects a non-increasing sequence_num"), not merely the standalone primitive's owntests/test_e2e.cppunit test. SeeHARA.md's own corrected H-004 section andinclude/rcp/e2e.hpp's file header ("UPDATE (Phase 4/Phase 17 batch C...)") for the full account (mirroring c-RCP's own earlier resolution of the identical ambiguity, issues #601/#606). SP1/SP2 as verified here are therefore properties of a primitive that is now wired into this codebase's own reference dispatch — with the same "an integrator bypassingmock::Server's own dispatch is on their own" caveat every primitive in this codebase carries (seeHARA.md's H-004 Residual Risks entry). - This spec models the pre-Phase-2-pass algorithm. The same Phase 2
pass that produced this correction also content-corrected
e2e::RxSequenceGuard's actual comparison rule against c-RCP'srcp_e2e_seq_evaluate(): acceptance is now an RFC 1982 forward-window comparison over the 8-bit AVTPDUsequence_numspace (forward distance in[1, 127]), not the plain non-wrappingn > last_seqthis file'stla/RxSequenceGuard.tlastill models (its ownAccept(n)action). A non-wrapping model is a real behavioral divergence from the corrected C++ implementation, not merely an abstraction choice the "Assumptions and Abstractions" section below already accounts for (that section's "unsigned 32-bit wrap-around... is not modelled directly" note predates and does not cover this). Re-derivingRxSequenceGuard.tla(and its.cfg) against the RFC 1982 rule — including the independentrx_seq_safestate_enable-gated discontinuity signal this pass also added — is not undertaken in this pass (a distinct formal-modeling task, not a documentation fix); tracked as a follow-up. Until then, SP1/SP2 as stated here should be read as verified properties of the prior algorithm, not the current one.
ASIL tracing: H-004 (request replay/out-of-order delivery), SG-004, REQ-E2E-007.
A stream configured with rx_enforce_e2e = FALSE never latches on a CRC
failure — only that request is rejected. A stream configured with
rx_enforce_e2e = TRUE latches on its first CRC failure, and — once
latched — only an explicit reset_latch() step (identifiable in the model
by every other tracked state component staying unchanged) ever clears it.
ASIL tracing: H-011 (E2E CRC integrity — the most safety-relevant new surface introduced across this roadmap), SG-011, REQ-E2E-004, REQ-E2E-006.
A stream configured with rx_wd_safestate_enable = FALSE never latches on
a watchdog timeout — only the overflow itself is reported. A stream
configured with rx_wd_safestate_enable = TRUE latches on the first
observed timeout, and only an explicit clear_safe_state() step ever
clears it. kick() alone never causes a timeout, since overflow detection
requires an elapsed-time step (Tick) past rx_wd_timeout_interval.
ASIL tracing: H-003 (watchdog not kicked), H-009 (RC Server not woken), SG-003, SG-008, SG-010, REQ-WDG-001, REQ-WDG-003, REQ-WDG-004.
- The TLA+ models use natural numbers as simulated clocks; unsigned 32-bit
wrap-around (the C++ implementation's actual sequence-number and
watchdog-timer representation) is not modelled directly, since TLC model
checking already bounds the state space independently via each spec's
.cfgconstants/constraints. RxSequenceGuard.tlaandCrcSafeStateLatch.tla/WatchdogSafeState.tlaare modelled independently, matchingrcp/e2e.hpp's own separation ofRxSequenceGuard,RxStreamGuard, andRxWatchdoginto three independently-configurable primitives — a stream can enable any combination ofrx_enforce_seq,rx_enforce_e2e, andrx_wd_safestate_enable(or none) per the register map.WatchdogSafeState.tlaassumes a synchronousTickaction for the elapsed-time check; the C++ implementation instead compares caller-supplied wall-clock milliseconds on eachcheck()/poll()call, with no timer thread of its own (RxWatchdog/StreamWatchdogare primitives, not a scheduler — seercp/watchdog.hpp's own header comment).
| TLA+ Spec | TLA+ Variable | C++ Location |
|---|---|---|
RxSequenceGuard |
last_seq, has_last |
e2e::RxSequenceGuard::last_seq_, has_last_ |
CrcSafeStateLatch |
latched |
e2e::RxStreamGuard::latched_ |
WatchdogSafeState |
kicked, last_kick, latched |
e2e::RxWatchdog::kicked_, last_kick_ms_, in_safe_state_ |