Skip to content

Commit dba8cea

Browse files
committed
Document the updated protections and fault contract
1 parent b9445da commit dba8cea

1 file changed

Lines changed: 93 additions & 4 deletions

File tree

docs/protections-and-diagnostics.md

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ flowchart TD
4242
D --> G["Diagnostics::Hub::flush()"]
4343
```
4444

45+
The application integration contract is:
46+
47+
```cpp
48+
Board<FaultPolicyT, dev0, dev1, ...>
49+
```
50+
51+
Where:
52+
53+
- `FaultPolicyT` is mandatory and is always the first template argument
54+
- `dev0, dev1, ...` are the board declarations to inscribe into the domains
55+
- the framework always owns the top-level runtime machine
56+
- the application may optionally provide a nested operational machine and/or a `FAULT` entry callback
57+
4558
### 1.2 Registering Protections
4659

4760
Protections are created through `ProtectionEngine::create_protection(...)`.
@@ -66,6 +79,33 @@ Available rule factories:
6679
Both `create_protection(...)` and `add_rule(...)` return `std::expected`, so configuration errors
6780
must be handled explicitly.
6881

82+
Current rule signatures are:
83+
84+
```cpp
85+
Rules::below(fault_threshold)
86+
Rules::below(fault_threshold, warning_threshold)
87+
88+
Rules::above(fault_threshold)
89+
Rules::above(fault_threshold, warning_threshold)
90+
91+
Rules::range(low_fault, high_fault)
92+
Rules::range(low_fault, high_fault, low_warning, high_warning)
93+
94+
Rules::equals(value)
95+
Rules::not_equals(value)
96+
97+
Rules::time_accumulation(fault_threshold, window_seconds)
98+
Rules::time_accumulation(fault_threshold, warning_threshold, window_seconds)
99+
```
100+
101+
`Rules::time_accumulation(...)` has these semantics:
102+
103+
- it is intended for floating-point samples
104+
- it evaluates `abs(sample)`
105+
- it measures continuous active time, not an integral over samples
106+
- it resets the accumulated active time when the triggering condition clears
107+
- it uses `Scheduler::get_global_tick()`, so it does not depend on the `while (1)` iteration rate
108+
69109
### 1.3 When to Register Protections
70110
71111
Register protections before `Board::init()`.
@@ -78,6 +118,10 @@ The intended lifecycle is:
78118
79119
After `Board::init()`, the protection registry is locked.
80120
121+
If registration code reports a fatal condition before `Board::init()`, that fatal request is still
122+
preserved across the first fault-runtime installation and its diagnostic record remains eligible for
123+
later delivery once sinks are installed.
124+
81125
### 1.4 Typical Protection Example
82126
83127
```cpp
@@ -135,6 +179,7 @@ Typical choices are:
135179

136180
- `Board<DefaultFaultPolicy, ...>` when no extra fault callback is needed
137181
- `Board<FaultPolicyNoMachine<on_fault_enter>, ...>` when only `FAULT` entry actions are needed
182+
- `Board<FaultPolicy<app_machine, on_fault_enter>, ...>` when both a nested operational machine and `FAULT` entry actions are needed
138183

139184
If the application does use a functional state machine, it can be nested inside `OPERATIONAL`
140185
through a `FaultPolicy`.
@@ -176,6 +221,21 @@ Important rules:
176221
the child machine directly
177222
- `Board` takes the fault policy type as its first template argument
178223

224+
`on_fault_enter` semantics:
225+
226+
- it is an optional callback owned by the global fault runtime
227+
- it runs when the global runtime enters `FAULT`
228+
- it is the right place to perform application fault-entry actions such as disabling power stages,
229+
opening contactors, or setting status LEDs
230+
- it does not replace the fault transition itself; it is an enter action attached to the global
231+
`FAULT` state
232+
233+
If the application needs neither a nested machine nor a `FAULT` entry action, use:
234+
235+
```cpp
236+
using MainBoard = Board<DefaultFaultPolicy, led>;
237+
```
238+
179239
### 1.6 Runtime Diagnostics API
180240

181241
The runtime diagnostic façade is:
@@ -203,10 +263,19 @@ Internally, protections and fatal runtime reporters converge on:
203263
FaultController::request_fault(cause);
204264
```
205265
206-
This primitive is not intended to be the normal user-facing API.
266+
This primitive is not part of the normal user-facing API.
267+
In the current implementation it is an internal `FaultController` entry point, not a public
268+
application hook.
269+
207270
User code should prefer `FAULT(...)` or `PANIC(...)` so the library captures consistent source
208271
metadata and preserves the public runtime contract.
209272
273+
In practice:
274+
275+
- protections use `FaultController::request_fault(...)` internally
276+
- `PANIC(...)` and `FAULT(...)` use that same path internally
277+
- user application code should not call `request_fault(...)` directly
278+
210279
### 1.8 Transmission Semantics
211280
212281
All external reporting goes through `Diagnostics`.
@@ -226,6 +295,22 @@ Default sinks are installed during `Board::init()`:
226295
227296
If a transport is not compiled in, it is simply not installed.
228297
298+
### 1.9 Migration From the Legacy Model
299+
300+
If you are migrating from the previous architecture:
301+
302+
- stop using `ProtectionManager`
303+
- stop using the low/high protection split
304+
- stop using `Boundary` / `BoundaryInterface` as the protection integration model
305+
- stop depending on `FaultRuntime`
306+
- stop treating `STLIB::start()`, `STLIB::update()`, `STLIB_LOW::start()`, or `STLIB_HIGH::start()`
307+
as the real bootstrap path
308+
- move bootstrap to `Board::init()`
309+
- declare `Board<fault_policy, ...>` explicitly
310+
- move operational user behavior into `FaultPolicy<app_machine, on_fault_enter>` when needed
311+
- stop programming transitions to the global `FAULT`
312+
- replace legacy reporting paths with `PANIC(...)`, `FAULT(...)`, `WARNING(...)`, and `INFO(...)`
313+
229314
## 2. Internal Development
230315
231316
### 2.1 Architectural Overview
@@ -317,20 +402,24 @@ Important invariant:
317402

318403
The fault path is valid during `Board::init()`.
319404

320-
That is why `Board::init()` installs:
405+
That is why `Board::init()` installs, as early as possible in the bootstrap path:
321406

322407
- default diagnostic sinks
323408
- the global fault runtime
324409

325-
before subsystem initialization that may trigger `PANIC(...)`.
410+
before clock/peripheral setup and before subsystem initialization that may trigger `PANIC(...)`.
326411

327412
If a fatal request arrives before the global runtime has been started:
328413

329414
- the cause is latched
330415
- the runtime is rebuilt so that it starts directly in `FAULT`
331416
- the urgent fault diagnostic is still published through `Diagnostics`
332417

333-
This avoids losing early boot faults.
418+
If the diagnostic record is produced before any sink exists, it is still retained in local history.
419+
When the first sink is installed, the retained history is replayed into the pending queue so the
420+
record can still be delivered later.
421+
422+
This avoids losing early boot faults and other pre-transport diagnostics.
334423

335424
### 2.5 FaultCause and Diagnostic Mapping
336425

0 commit comments

Comments
 (0)