# How rules work

URL: /rules

Rules turn raw logs into findings an engineer can check line by line. Each rule is deterministic and versioned, and its fixed wording observes what a source shows without drawing the conclusion.



A rule reads one kind of evidence and flags one kind of event: a unit leaving the active state, a receive gap, a failsafe. The console calls them deterministic detections. The same files and the same rule versions give the same findings, so a reviewer who reruns them next month gets the same answer.

Rules run as the last step of every import, after Foxborne has validated, hashed, parsed and aligned the files. On the run page, **Import job** opens those steps with their timings. A finding therefore sits on the same aligned timeline as the events around it, with its source's error bound.

## What a rule reads [#what-a-rule-reads]

Each rule reads one source, except `clock_uncertain`, which checks them all.

| Source                | Rules that read it                                                                                                    |
| --------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Flight log (PX4 ULog) | `vibration_high`, `failsafe_entered`, `topic_stall`, `gnss_jamming`, `gps_quality_drop`, `log_dropout`, `battery_sag` |
| Companion journal     | `process_exit`, `service_restart`, `device_disconnect`                                                                |
| Process samples       | `memory_pressure`                                                                                                     |
| Ground receive log    | `telemetry_gap`                                                                                                       |
| Every source          | `clock_uncertain`                                                                                                     |

A rule with nothing to read produces nothing, so a missing finding is not evidence that nothing happened. The run page marks a run that lacks a source as **Partial**. Its note reads "Findings that depend on the missing source are not produced, and reports list the gap under Limitations."

## What a finding records [#what-a-finding-records]

| Part             | What it holds                                       | Example from INC-0142                                          |
| ---------------- | --------------------------------------------------- | -------------------------------------------------------------- |
| Rule and version | The rule ID and the version that ran                | `telemetry_gap 1.3`                                            |
| Threshold        | The threshold text of that version                  | More than 3 s without a HEARTBEAT                              |
| Events read      | The file, the location in it and the file's SHA-256 | `rc1-receive.jsonl`, lines 1,118 and 1,119                     |
| Values           | The measurements behind the finding                 | Last received 14:32:03.912, next received 14:32:10.874         |
| Wording          | The fixed report wording of the rule                | Observed: telemetry receive gap, not necessarily radio failure |

The console shows a finding in four places:

* the timeline's **Findings** row, subtitled **Rule results**, as a flag with a short name;
* the **Events in view** table, in bold, with the rule in the **Rule** column;
* the evidence inspector's **Rule** block, with **Rule**, **Detects**, **Threshold** and **Wording**;
* the event page's **Highlights**, with **Rule** and **Finding**.

## Rules observe, engineers conclude [#rules-observe-engineers-conclude]

<Callout type="note" title="Rules observe. They never conclude.">
  Their wording starts with Observed or Warning, and a report keeps rule output apart from what an engineer writes. Changing a threshold creates a new rule version, and results already produced keep the version that made them.
</Callout>

This note sits under the table on the console's **Rules** page. Twelve rules start their wording with &#x2A;*Observed:**, and `clock_uncertain` starts with &#x2A;*Warning:**. The wording is fixed per rule, "so every report describes the same detection the same way", as the rule editor puts it.

A rule's output sits on the first rung of the [evidence levels](/concepts/evidence-levels): observed. Linking two findings, proposing a cause or declaring a question unknown is the engineer's work.

The report draws that line in writing. Under the engineer's finding it states "This paragraph is the engineer's. Everything below it is extracted from the sources."

## Versions [#versions]

A rule's version shows under its name in the **Rules** table, for example `process_exit 1.2`. Saving any change to a rule creates the next version, and the rule editor names it before you save: **Saves as** `process_exit 1.3`.

The number after the dot goes up by one, so 1.2 becomes 1.3 and 0.9 becomes 0.10.

Findings already produced keep the version and the threshold that made them. The editor's hint says so: "Results already produced keep version 1.2 and its threshold." A report can cite a finding made under a threshold that has since changed, and the version tells the reviewer which one applied.

The example dataset lists `telemetry_gap 1.3`, `process_exit 1.2`, `memory_pressure 1.1` and `battery_sag 0.9`, and 1.0 for the other nine rules. Your deployment may show different versions after its own threshold changes. [Rule versions](/release-notes/rules) records rule version changes across releases.

## Scopes and overrides [#scopes-and-overrides]

**Applies to** sets which vehicles or sources a rule covers. It is free text, and the example dataset uses **All vehicles**, **Q4 vehicles**, **T4 vehicles** and **All sources**. The **Rules** table shows the scope under each threshold.

An **Override** gives part of the fleet its own value. The example dataset has one: `memory_pressure` applies 6,144 MiB to Q4 vehicles and carries the override **T4 vehicles: 24,576 MiB**. `vibration_high` keeps its per-platform values inside the threshold text instead: &#x2A;*Above 24 m/s² for 1 s (T4), 12 m/s² (Q4)**.

## The locked rule [#the-locked-rule]

`clock_uncertain` is always on, and its drawer has no **Edit**. Its **Enabled** switch is disabled too, with the tooltip "Always on. Every ordering claim depends on it."

A before or after claim between two sources is only as good as the clocks this rule checks.

## Switching rules on and off [#switching-rules-on-and-off]

The **Enabled** switch takes effect at once, with no confirmation. The toast states when the change applies, for example &#x2A;*Battery sag enabled. Applies to the next import.**

The audit log records **Enabled rule** or **Disabled rule** with the rule's version, which does not change.

`battery_sag` is the only rule that ships switched off in the example dataset.

## Running rules again [#running-rules-again]

A run page carries a **Run rules again** button. It reruns the rules on the run's files, and the toast reports the outcome: &#x2A;*Rules ran again. Results are identical to the previous run.**

The audit log records **Ran rules again** against the run ID, under **Imports**.

Identical results are the point, and the run page's **Import job** drawer states the same guarantee: "Deterministic. The same files and parser versions give the same event IDs." A reviewer who doubts a finding can rerun it and see the same event, threshold and values come back.

## Dismissing a finding [#dismissing-a-finding]

An engineer can dismiss a finding that does not apply, for example a restart during a scheduled bench test. In the evidence inspector's **Rule** block, select **Dismiss this finding**.

The dialog states what happens: "The raw evidence is not changed. The dismissal and its reason are kept in the audit log and shown in the report." A **Reason** is required.

Select **Dismiss finding**, and the toast reads &#x2A;*Finding dismissed. The evidence is unchanged.**

The flag leaves the **Findings** row, and the inspector shows **Dismissed** with the name and the reason. The audit log records **Dismissed finding** with the rule and the reason, under **Evidence**.

## All rules [#all-rules]

<RuleTable />

## Next [#next]

* [Thresholds, scopes and overrides](/rules/configure) walks through editing a rule and switching it on or off.
* [Rules and findings](/concepts/rules-and-findings) places rules in the whole reconstruction.
* [Evidence levels](/concepts/evidence-levels) explains observed, correlated, hypothesis and unknown.
* [Telemetry gap](/rules/telemetry-gap) shows one rule end to end, from the records it reads to an example finding.
