|
Safe API Framework 0.1.0 build 384e6ae
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
|
Accepted (2026-10-02); steps 1 to 4 done; step 5 in progress (5a, 5b-1, 5b-2 and 5b-3a done, 5b-3b and 5c open).** How the service behaves as built is in the section "Service behaviour as built" below; the end-to-end system view (both sites, A and B, cross-compare, checkpoint) is RBC_GP's Site redundancy and A/B agreement. Plan for gap G1 of the root docs/architecture/CAPELLA_ARCHITECTURE_BASELINE.dox. Decisions 1 and 2 below were taken by the product owner on 2026-10-02 (the model overrides A3; drive points stay in the application cycle). Step 1 moved the pure policy (rte_site_role_policy, REQ-SITEROLE-001..004). Step 2 moved the mechanism: rte_site_role owns the inter-site link, the startup negotiation, frame exchange, partner supervision and teardown, the takeover confirmation, the warm-standby sync and the FAULTED flush (REQ-SITEROLE-010..013, tests/site_role/test_rte_site_role.c). RBC_GP's ab_gp_channel_negotiate.c is now the Site Role Client: state codec (sessions, db), what a promotion takes over (transfer policy), local facts. Wire layout and log text unchanged. Two seams are still the application's: the pause between startup connect attempts (delay callback - the OAL has no sleep service) and the console line sink (trace callback). Step 3 (2026-10-03) moved the A/B role agreement rule and its 3-strike count: rte_site_role_check_sibling() (REQ-SITEROLE-014) logs the mismatch lines and the NEGOTIATION_MISMATCH event and returns a verdict; GP forwards the sibling's site-state frame and keeps the fault reaction (safe-state reboot). Step 4a (2026-10-03): the token check and the challenge are in rte_site_role (REQ-SITEROLE-015, decision 4 below). Steps 4b-4e (2026-10-03) are done with no design change: RBC_GP wires the key and the token builder, RBC_Maintenance_Web is the interim issuer, RBC_Test_Env covers the takeover and its negative cases, and the documentation is updated. Step 5a (2026-10-06, RBC_GP only): A and B of a site agree their startup timestamp over the A/B peer link (PEER_MSG_KIND_STARTUP_TS); the shared /tmp file is gone. Step 5b-1 (2026-10-06): the sibling-state source seam (REQ-SITEROLE-016). Step 5b-2 (2026-10-06, RBC_GP): GP stores the sibling's raw site-state frame and hands it to the service through the source; the service derives the sibling facts and runs the A/B check, GP only reacts to rte_site_role_sibling_verdict(). Step 5b-3a (2026-10-06): a channel starting next to a running A/B sibling resumes the sibling's role (REQ-SITEROLE-017); GP does not deliver the frame at start-up yet (5b-3b), so in RBC_GP the start-up still runs the timestamp tie-break. Step 5c (site and peer links on rte_flow, GP copies deleted, rte_site_role_check_sibling() removed from the API) is open.
The Capella model (RBC-GP-ARCH-CAP 0.1, 2026-10-02) puts site-role negotiation, partner supervision, verification of the dispatcher's takeover confirmation and state transfer on promotion in RTE Redundancy Services ("[SF] Negotiate Site Role", "[SF] Transfer State on Promotion"). The application keeps only a declarative Site Role Client (IF-05): it receives the role, supplies readiness and the state to replicate, and passes the confirmation on. Role state is exchanged independently by channels A and B and the role decision must agree in both (2oo2); two ONLINE sites are excluded.
On 2026-10-02 all of it was in RBC_GP (AB/GP/com/); the table is kept as the starting point of the move:
| Piece | Where | Generic (to RTE) or application (stays) |
|---|---|---|
| West-East link: raw rte_netlink, fixed A/B ports, reconnect bounded to 50 ms | ab_gp_channel_negotiate.c | generic |
| Startup tie-break (older timestamp wins), wrapping rte_dual_channel + rte_dual_negotiator | same | generic (already partly RTE) |
| Extra payload framing faulted | single_mode | cycle | sessions | db | valid | same, common_config.h | framing generic; sessions/db codecs application | | Decision table, rules 1-7 + readiness gate, actions NONE/PROMOTE/DEMOTE/RENEGOTIATE/ALARM/WAIT | ab_gp_site_role.c | generic (readiness input is application) | | Standby / takeover / tie-break timing predicates | ab_gp_standby_policy.c | generic, except session_adopt_allowed | | Promote / demote / renegotiate, apply state transfer, cycle restart policy | ab_gp_channel_negotiate.c | trigger generic; what is applied is application | | Warm standby sync, 10 ms link service tick | same, called from ab_gp_channel.c, ab_gp_channel_checkpoint.c, post_execute.c | generic | | Takeover confirmation (flag + cycle only, not verified) | same, decoded in ab_gp_channel.c | verification generic | | Fault flush before reboot | same, from ab_gp_channel_crosscompare.c | generic | | A/B role agreement (sibling {online, single} compared, 3 mismatches -> reboot) | ab_gp_channel_crosscompare.c | generic | | Readiness (relay frame within 10 cycles), session adoption from sibling, relay gating on STANDBY | ab_gp_site_status.c, pre_execute.c | application |
What RTE has: rte_dual_negotiator (startup tie-break only, no FAULTED state, no promote/demote input, no takeover, no payload; GP is its only caller), rte_dual_channel (transport), rte_state_transfer (codec registry, no transport), rte_election (N-way, unused), rte_redundancy_config (standby_mode). There is no site-role API.
This reverses RBC_GP's decision of 2026-09-23 (review finding A3, "keep both"): under the model, the policy applied to the negotiator's verdict is platform mechanism, not application concern.
A new RTE module redundancy/site_role/ owns the mechanism; GP becomes its client. The API sketched on 2026-10-02 is below; the built API differs in names and shape (callbacks in rte_site_role_config_t instead of register calls) and is include/rte/redundancy/site_role/rte_site_role.h, summarised in "Service behaviour as built". Sketch:
Source: include/rte/redundancy/site_role/rte_site_role.h, rte_site_role_policy.h, src/redundancy/site_role/. The rule list in rte_site_role_decide()'s header comment is authoritative. Tests: tests/site_role/test_rte_site_role.c, tests/site_role/test_rte_site_role_policy.c.
| Call | When the application calls it | What it does |
|---|---|---|
| rte_site_role_init() | setup | validates and copies the configuration; opens nothing |
| rte_site_role_start() | setup, after init | start-up role (below); blocking, bounded by startup_timeout_ms |
| rte_site_role_execute(cycle, local) | once per cycle (RBC_GP: pre_execute) | one site-role step (below) |
| rte_site_role_service_link(cycle) | fast tick (RBC_GP: every 10 ms of the tick wait, and inside the checkpoint poll) | drains up to 4 frames, so acknowledgements do not depend on the other site's cycle phase |
| rte_site_role_sync_standby(cycle, local) | before the output commit | warm/hot: ONLINE sends this cycle's snapshot and tracks the acknowledgement |
| rte_site_role_on_takeover_token() / _on_takeover_confirm() | when the dispatcher's confirmation arrives | token check (REQ-SITEROLE-015) / unauthenticated interim path |
| rte_site_role_note_lost_contact() | the application stalled while ONLINE | sets the lost-contact history (rule 5) |
| rte_site_role_flush_faulted() | right before a fault reboot | best-effort final frame with faulted = 1 |
| rte_site_role_sibling_verdict() | after execute | the A/B verdict of that cycle (REQ-SITEROLE-016) |
| rte_site_role_is_online(), _get_status(), _takeover_challenge(), _get_negotiator_states() | any time | role, status snapshot (site_wait / site_why in RBC_GP), current challenge, negotiator diagnostic |
The application supplies: the state codec (encode_state, receive_state), the change callback (on_change, with apply_state on a promotion), the local facts per call (faulted, single_mode, own_ready; the sibling_* fields only without a sibling source), the sibling source, the MAC verifier, and the trace / delay seams.
First match wins (REQ-SITEROLE-001). "Answering" = a counterpart frame within the last 2 cycles.
| # | Condition | Action |
|---|---|---|
| 1 | own and counterpart FAULTED | RENEGOTIATE: WEST ONLINE, EAST STANDBY |
| 2 | STANDBY, not FAULTED, counterpart FAULTED | PROMOTE |
| 3 | own FAULTED, counterpart healthy, valid counterpart snapshot held | DEMOTE |
| 4 | STANDBY, not FAULTED, counterpart unresponsive, dispatcher confirmation at most RTE_SITE_ROLE_TAKEOVER_CONFIRM_VALID_CYCLES (120) old | PROMOTE |
| 5 | ONLINE, nobody FAULTED, counterpart ONLINE now and answering | DEMOTE if lost-contact history (yield), else ALARM (dual ONLINE, not resolved) |
| 6 | STANDBY, link up, counterpart answering, responsive, not ONLINE, not FAULTED, for RTE_SITE_ROLE_BOTH_STANDBY_CYCLES (6) consecutive cycles on WEST, or 1 cycle when the A/B sibling is ONLINE | PROMOTE |
| 7 | STANDBY, A/B sibling ONLINE, counterpart not FAULTED and not (ONLINE now and answering), for 2 consecutive cycles | PROMOTE (follow the sibling) |
Readiness gate (REQ-SITEROLE-002): a PROMOTE becomes WAIT unless own_ready and the sibling is ONLINE, ready or not reachable. Silence alone never promotes (REQ-SITEROLE-003). A restarted cycle counter makes a confirmation expired and a snapshot stale (REQ-SITEROLE-004).
With verify_mac set: a 24-byte token (16-byte message version | site id | channel id | 0 | challenge | issuer sequence | 0, 8-byte MAC) is accepted only for this channel, with a MAC the verifier accepts and the current challenge. The challenge is drawn with rte_random, replaced after each accepted token and after challenge_lifetime_cycles (0 = 240); no challenge, no token. While ONLINE a token is ignored. The unauthenticated rte_site_role_on_takeover_confirm() is refused when a verifier is configured. An accepted confirmation is valid for 120 cycles and only acts through rule 4.
ISS-036 (the negotiator and the dual channel ignore a failed rte_timer_now(); the watchdog part is fixed), ISS-038 (received frame length not checked), ISS-042 (start-up bound), ISS-043 (dual channel acknowledges then overwrites a DATA frame; benign for the one-link site role). All in the root ISSUES.dox.
Each step keeps behaviour identical and is verified by the existing unit tests, the Docker 2oo2_tests and fault_injection suites, and smoke.sh before the next one starts.