|
Safe API Framework 0.1.0 build 384e6ae
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
|
Status: Accepted Date: 2026-08-20 Applies to: src/timer/rte_timer.c, src/ipc/rte_ipc.c, src/task/rte_task.c, src/netlink/rte_netlink.c, src/nvm/rte_nvm.c, src/log/rte_log.c, src/clocksync/rte_clocksync.c, src/reboot/rte_reboot.c, src/memory/rte_memory.c, src/safestate/rte_safestate.c, src/ipc/rte_ipc_pubsub.c, src/ipc/rte_ipc_request_reply.c.
A downstream integrator (RBC_GP) asked this framework to review itself end to end for exactly the class of gap ADR-026 introduced rte_lifecycle_check_setup_allowed() to close: "can a resource with process-lifetime scope still be created/registered after the application has started running, when it should only ever be creatable during setup." ADR-026 §2.2 enumerates the constructors it gated as a closed list (rte_timer_create, rte_channel_init, rte_voter_init/ _register_channel, rte_cross_comparator_init/_register_channel, rte_watchdog_create) and a separate, deliberate exclusion list (rte_netlink_open, rte_dual_channel_init, rte_dual_negotiator_init
This matters specifically for the _register_backend() family: a backend vtable is process-lifetime, integrator-supplied wiring - exactly ADR-026's own "resource an application's own design did not already account for" concern (§2.2). Before this ADR, nothing stopped execute()/pre_execute() from swapping a running application's memory- pool or NVM backend mid-run, which is a considerably sharper hazard than adding a new timer would be (it can silently redirect already-in-flight state to a different backend implementation).
Extend ADR-026 §2.2's gated list with the same guard, in the same place (after NULL/param validation, before any real work), returning the same RTE_STATUS_INVALID_STATE on a locked setup phase:
rte_nvm_open() and rte_log_init() were evaluated and NOT gated - see the "Deferred" note below.
No new malloc-based default backend was added anywhere in the framework as part of this ADR.** This was raised as a candidate (a pluggable allocator, falling back to malloc() if no backend is registered) but rejected once a repository search found RBC_GP/src/posix_osadapter/rte_posix_osadapter_memory.c already implements a complete, working rte_osadapter_memory_t using a static compile-time-sized arena (g_arena[RTE_POSIX_MEM_ARENA_SIZE]) with a bump allocator and intrusive free list - zero malloc()/free() calls. Adding a malloc-backed default to the framework itself would reintroduce dynamic allocation exactly where this framework's own CLAUDE.md ("No dynamic memory: malloc, free, and realloc are strictly banned") and ADR-005's "backend is integrator-supplied" philosophy both already keep it out - a real regression in rigor for a convenience an already- compliant integrator does not need. CLAUDE.md's existing rule required no amendment: it remains categorically true (zero malloc/free/ realloc call sites anywhere in include//src/) rather than needing a new stated exception.
Both are structurally the initialization half of a _register_backend() pair, but neither has a settled answer to "is a post-lock re-open ever legitimate" the way rte_netlink_open() does (ADR-026 §2.2's own reconnect rationale, backed by a real downstream consumer that does it). Gating them speculatively risks the same kind of break ADR-026 §2.2 already had to reason carefully about for netlink/dual - left ungated until a real use case (or its absence) is confirmed, rather than guessed at here.