Safety Analysis
Note
A complete working example covering failure_modes, fault_trees, safety_analysis and dependability_analysis is
available in
bazel/rules/rules_score/examples/seooc/safety_analysis/.
The dependability_analysis rule summarizes all the dependability analyses
(Safety / Security) for a dependable element. A single element may have
multiple dependability analyses.
Overview
Why safety analysis?
A safety analysis shall support the process to systematically identify failures which could violate safety goals or safety requirements. It shall also help to identify their root causes and design appropriate countermeasures.
Safety analyses are typically performed with two complementary methods:
FMEA (Failure Mode and Effects Analysis) is an inductive, bottom-up method: it examines individual components mainly at their interfaces, how they can fail, and what effect those failures have on the overall system.
FTA (Fault Tree Analysis) is a deductive, top-down method: depending on the context it starts from a safety goal / safety requirement / failure (mode) and traces back to the potential causes that could lead to it.
How safety analyses are used in this context
SEooCs are designed to be included in a system (e.g. platform). This means that from a system point of view the SEooCs are the leaves of its FMEA. Thus failure modes of the SEooC can be identified by applying the FMEA methodology (structured fault models) to its interface (aka public API).
In a second step the root causes of the identified failure modes need to be analyzed within the SEooC. This can be achieved by performing a Fault Tree Analysis (FTA). Each identified root cause then needs to be treated with appropriate safety measures to guarantee that a safety-related failure mode cannot occur in the first place.
So the single steps are:
Identify failure modes — apply structured fault models (see Fault models) to each public interface to derive what can cause a violation of an overarching safety goal.
Analyze effects and causes — decompose the identified failure modes into their root causes using a Fault Tree Analysis (FTA).
Define countermeasures — for every root cause, close it with a
Mitigation, aCompReq, or push the obligation outward with anAoU, and trace it back through the FTA to the root cause.Wire and validate — bundle the resulting artifacts in Bazel and let the traceability check verify that they are consistently linked.
Each step is described in detail in Performing the Analysis.
Fault models
The failure modes to consider are defined by the SCORE process:
The fault models cover three categories: messages (send/receive behaviour),
time constraints (too early / too late), and execution (wrong result,
loss, delay, corruption, non-determinism). The Guideword enum in the
ScoreReq model maps each category to a structured label used in the
FailureMode records.
Artifacts and traceability
As mentioned above, the safety analysis method used by dependability_analysis is a combination of
both an FMEA and a FTA. The failure_modes, fault_trees and safety_analysis targets together bundle four types of
artifacts that must be linked together:
Artifact |
Format |
What it represents |
Created in |
|---|---|---|---|
Public API Interfaces |
PlantUML (from |
Interfaces where failures can manifest; referenced by |
|
Failure Modes |
TRLC ( |
Effects identified in the FMEA: what can go wrong and its impact |
Step 1 |
FTA Diagrams |
PlantUML ( |
Fault Tree Analysis: structural decomposition of each failure mode into root causes |
Step 2 |
Safety Measures |
TRLC ( |
Countermeasures that address the root causes identified in the FTA |
Step 3 |
The artifacts are linked as follows:
FailureMode.interfacereferences an element of thepublic_apiof thearchitectural_designtarget. This connects the architectural view to the safety analysis.Every
$FailureModenode in an FTA diagram names the TRLC fully-qualified record name(s) (Package.RecordName) of theFailureModerecord(s) it covers.Every
$RootCauseleaf in an FTA diagram gets a plain-identifier alias. A dedicated tool generates aRootCausestub for each such alias; the stub lists theFailureModerecord(s) of its fault tree infailure_modes. AMitigation/CompReq/AoUthen references the stub explicitly from itsroot_causes/derived_fromfield.
The figure shows all record links; arrows point from the referencing record to the referenced one:
The traceability check verifies these links automatically (see Traceability Validation).
Performing the Analysis
The Bazel rule and traceability check only verify that the artifacts are linked and traced — they cannot tell you whether the analysis is complete or correct. Identifying failure modes, reasoning about causes, and choosing countermeasures is a safety-engineering activity.
The following steps walk through this activity. Each step first explains the
safety reasoning and then shows how its result is recorded as an artifact. All
snippets are taken from the SEooC example in
bazel/rules/rules_score/examples/seooc.
Step 1 — Identify failure modes per interface
Go through the public_api method by method. For each method, walk the
applicable fault models and ask “can this occur, and would it violate a safety
goal or safety requirement?”
The Guideword enum labels the fault-model category on each FailureMode:
Fault-model category |
Example fault models |
|
|---|---|---|
Message (send/receive) |
not sent / not received, corrupted, lost, unintended |
|
Timing / duration constraint |
too late / too early, boundary violated |
|
Execution |
wrong result, loss of execution, arbitrary/incomplete |
|
Recording failure modes (TRLC)
Each identified failure mode is recorded as a ScoreReq.FailureMode record
in a .trlc file (here sample_safety_analysis_failure_modes.trlc). The
TRLC package name (SampleLibrary below) is chosen freely by the author —
it is not derived from the Bazel target name and is not enforced by the rule
(only the generated <fta_package> package, see Modeling the fault tree
(PlantUML), is fixed):
package SampleLibrary
import ScoreReq
ScoreReq.FailureMode SampleFailureMode{
guidewords = [ScoreReq.Guideword.LossOfFunction]
description = "SampleFailureMode takes over the world"
failureeffect = "The world as we know it will end"
version = 1
safety = ScoreReq.Asil.B
interface = "safety_software_seooc_example.SampleLibraryAPI.GetNumber"
}
Attribute |
Meaning |
|---|---|
|
One or more |
|
What goes wrong. |
|
Consequence for the caller / system (see Step 2). |
|
Fully-qualified name of the affected |
|
ASIL of the safety goal the failure mode can violate (see ASIL rationale in Step 3). |
|
Monotonically increasing counter; increment on every content change. |
|
Why this failure mode is considered relevant. |
The record’s fully-qualified name, SampleLibrary.SampleFailureMode
(package + record name), is passed as a $FailureMode argument in Step 2,
linking it to its fault tree.
Step 2 — Analyse the effect, then decompose to causes
Effect (
failureeffect) — describe the consequence from the caller / system perspective, in worst-case terms, relative to the safety goal. “Returns a stale value that the controller uses to actuate” is a usable effect; “function returns wrong data” is not.Causes — build the Fault Tree (FTA) top-down from the failure mode to its root causes:
Use an OR gate when any single child cause is sufficient to produce the parent — this is the default for independent causes.
Use an AND gate only when all children must occur together (e.g. a fault plus the failure of a safety mechanism) — this is what justifies a lower residual risk.
Decompose until each leaf (
$RootCause) is an actionable root cause you can place a measure on — not a vague restatement of the failure.
Modeling the fault tree (PlantUML)
Each failure mode gets its own FTA diagram. A dedicated PlantUML metamodel
(fta_metamodel.puml,
located at plantuml/fta_metamodel.puml in the score-tooling repository)
provides the graphical elements as procedures; no standard PlantUML shapes are
needed. Every FTA .puml file must begin with !include fta_metamodel.puml
so that the procedure definitions are available.
Procedure |
Description |
|---|---|
|
The failure mode(s) at the root of the tree. Each |
|
An intermediate cause that is decomposed further. |
|
A root cause (leaf node). |
|
AND gate: all children must occur for the parent to occur.
|
|
OR gate: any single child is sufficient for the parent to occur.
|
|
Transfer-in gate linking to another FTA sub-tree. |
Each element points to its parent via the connection parameter — the
arrow goes from the element up to the parent. Declare the tree from the top
down:
Declare the
$FailureModefirst (noconnectionparameter — it is the root).Declare the gate(s) with
connectionset to the$FailureModealias (fm1).Declare
$IntermediateEvent/$RootCausenodes withconnectionset to the enclosing gate’s alias. An$IntermediateEventis decomposed further by gates whoseconnectionis its alias.
$FailureMode ← root, no connection
└── $OrGate(alias="OG_1", connection="FailureMode.fm1")
├── $RootCause(alias="RootCauseA", connection="OG_1")
└── $RootCause(alias="RootCauseB", connection="OG_1")
Gate and intermediate-event aliases (e.g. OG_1) are only local identifiers
within the diagram; they do not refer to TRLC records.
A dedicated Rust tool (puml_cli’s FTA parser) reads every $FailureMode/
$RootCause in the fault_trees’s srcs diagrams and generates a
TRLC stub package (fta_events.trlc, imported as the <fta_package>
package) with one RootCause record per reachable $RootCause alias; each
record’s failure_modes lists the FailureMode record(s) of the $FailureMode
root it hangs under. No manual linking
step is needed on the TRLC side beyond importing that generated package and
referencing its records from your measure definitions (Step 3).
Linking a $FailureMode to its failure mode(s)
Pass every FailureMode fully-qualified name a failure-mode node covers directly
as $FailureMode arguments. This lets several related failure modes be
consolidated under one failure-mode node and its fault tree, instead of
duplicating the same tree once per failure mode:
$FailureMode("Message lost, incomplete, or corrupted during transmission",
"MessagePassing.MessageLost",
"MessagePassing.MessagePartiallyDelivered",
"MessagePassing.MessageCorrupted")
For example, the fault tree for SampleLibrary.SampleFailureMode (the
FailureMode from Step 1) decomposes as follows:
@startuml SeoocExample_FTA
!include fta_metamodel.puml
$FailureMode("SampleFailureMode takes over the world", "SampleLibrary.SampleFailureMode")
$OrGate("OG1", "SampleLibrary.SampleFailureMode")
$IntermediateEvent("SampleFailureMode is Angry", "IEF", "OG1")
$RootCause("Just bad luck", "JustBadLuck", "OG1")
$AndGate("AG2", "IEF")
$RootCause("No More Cookies", "NoMoreCookies", "AG2")
$RootCause("No More Coffee", "NoMoreCoffee", "AG2")
@enduml
JustBadLuck alone is sufficient to cause the failure mode (OR gate),
whereas NoMoreCookies and NoMoreCoffee must occur together (AND gate)
to cause the intermediate event. Each of these root causes needs a measure in
Step 3.
Step 3 — Choose a countermeasure for every root cause
Every $RootCause needs to be addressed by at least one of the following.
Pick the path by who closes the root cause:
Type |
Use when the root cause is closed by… |
Closed via |
|---|---|---|
|
a reasoning why the root cause cannot occur at all (due to system design, …) |
|
|
a runtime/design measure, implemented and tested like any other component requirement. |
|
|
an obligation only the integrator/caller can guarantee, not the SEooC. |
mandatory |
Recording measures (TRLC)
Each measure imports the generated <fta_package> package and references the
RootCause stub(s) it addresses explicitly from its
root_causes/derived_from field. For example, the following
Mitigation records (from sample_safety_analysis_safetymeasures.trlc)
address the JustBadLuck, NoMoreCookies, and NoMoreCoffee root
causes from the FTA diagram in Step 2:
package SampleLibrary
import ScoreReq
import sample_safety_analysis_fta
ScoreReq.Mitigation JustBadLuck{
safety = ScoreReq.Asil.B
description = "Sometimes, the dark side wins. We shall be prepared for that."
justification = "This root cause is outside our control; no active measure can prevent it."
version = 1
root_causes = [sample_safety_analysis_fta.JustBadLuck]
}
ScoreReq.Mitigation NoMoreCookies{
safety = ScoreReq.Asil.B
description = "We shall only order family size cookie jars"
justification = "Ordering only family size jars keeps the reserve stable, so this root cause does not apply."
version = 1
root_causes = [sample_safety_analysis_fta.NoMoreCookies]
}
ScoreReq.Mitigation NoMoreCoffee{
safety = ScoreReq.Asil.B
description = "We shall keep a coffee reserve for emergencies"
justification = "An emergency reserve is kept on hand, so this root cause does not apply."
version = 1
root_causes = [sample_safety_analysis_fta.NoMoreCoffee]
}
The alias JustBadLuck in the FTA diagram matches the RootCause stub
sample_safety_analysis_fta.JustBadLuck referenced from the Mitigation’s
root_causes — and likewise for NoMoreCookies/NoMoreCoffee. This
explicit reference is how the traceability link is established (there is no
implicit name-matching between the diagram alias and the Mitigation
record’s own name).
A CompReq
can reference the generated RootCause stub directly in its
derived_from list (closing it via a normal, implemented-and-tested
component requirement instead of a dedicated safety measure), or an AoU
can reference it via its mandatory root_causes field (pushing the
obligation out to the integrator). The traceability report of the enclosing
dependable_element flags a root cause that is covered by none of
Mitigation, CompReq, or AoU; with maturity = "release" this
fails bazel test of the dependable_element.
An AoU is how you push an obligation outward when the SEooC cannot close a
root cause itself — it must be forwarded to the integrating project (see
Assumptions of Use).
The reverse direction is different: a CompReq lists an AoU in
derived_from only for an AoU received from another dependable_element
(one of this element’s deps). The element’s own AoUs are never a
derived_from source; they close a root cause through their own
root_causes field.
Step 4 — Wire the analysis into Bazel
load(
"@score_tooling//bazel/rules/rules_score:rules_score.bzl",
"failure_modes",
"fault_trees",
"safety_analysis",
)
filegroup(
name = "sample_fta",
srcs = [
"sample_fta.puml",
"sample_fta2.puml",
],
visibility = ["//visibility:public"],
)
failure_modes(
name = "sample_failure_modes",
srcs = ["sample_safety_analysis_failure_modes.trlc"],
)
fault_trees(
name = "sample_fault_trees",
srcs = [":sample_fta"],
fta_package = "sample_safety_analysis_fta",
visibility = ["//visibility:public"],
deps = [":sample_failure_modes"],
)
safety_analysis(
name = "sample_safety_analysis",
arch_design = "//design:sample_seooc_design",
failure_modes = [":sample_failure_modes"],
fault_trees = ":sample_fault_trees",
safety_measures = [
"//docs:sample_aous",
"sample_safety_analysis_safetymeasures.trlc",
],
visibility = ["//visibility:public"],
)
The analysis is split in three targets to avoid a dependency cycle: the
failure_modes target holds the FailureMode records, the fault_trees
target generates the <fta_package> package with the RootCause records
referencing them, the measures reference those records, and the
safety_analysis target lists the failure modes and the measures. Every
AoU references a root cause, so each assumptions_of_use target lists the
fault_trees target in its deps (so the generated package resolves when
the AoU file is parsed) and is passed in the safety_measures of the
safety_analysis (so it counts as a measure of the root cause):
assumptions_of_use(
name = "sample_aous",
srcs = ["aous.trlc"],
deps = ["//safety_analysis:sample_fault_trees"],
)
Raw .trlc files in safety_measures may only hold Mitigation
records, and a root cause whose failure mode is not in failure_modes fails
the build.
The dependable_element derives its own AoUs from the safety_measures of its
safety analyses, so the AoU reaches the dependees once, however many safety
analyses list it. An assumptions_of_use target that no safety_analysis
lists in safety_measures is neither rendered nor forwarded.
The dependability_analysis target of the dependable element then
references a safety_analysis target by label:
load(
"@score_tooling//bazel/rules/rules_score:rules_score.bzl",
"dependability_analysis",
)
dependability_analysis(
name = "sample_dependability_analysis",
arch_design = "//design:sample_seooc_design",
safety_analysis = ["//safety_analysis:sample_safety_analysis"],
)
Generated targets: <name> — bazel build produces the documentation
and traceability report; bazel test validates the full chain (see
Traceability Validation).
The dependable element stages the pages of each dependability_analysis under
its name as safety_analysis.rst and fta.puml. List one
safety_analysis per dependability_analysis; for several safety analyses,
use several dependability_analysis targets in the dependable_element.
Items shared by several safety analyses (e.g. the same AoU) are kept once.
For the complete attribute reference, see failure_modes, fault_trees, safety_analysis and dependability_analysis in the rule index.
Traceability Validation
Running bazel test on the dependability_analysis target (e.g.
bazel test //:sample_dependability_analysis in the SEooC example) executes
a traceability check that validates the chain from the public API down to the
root causes:
public_api interface ← FailureMode.interface
|
$FailureMode
|
AND / OR gate(s)
|
$RootCause
The check fails if:
A
$FailureModereferences a failure-mode fully-qualified name that does not match anyFailureModerecordA
$RootCausealias is not a plain TRLC identifier (dottedPackage.Namealiases are rejected for root causes)A
FailureModeis defined but not referenced by anyRootCause(no$RootCausehangs under a$FailureModecovering it)
The last link of the chain is checked by the traceability report of the
enclosing dependable_element:
$RootCause
|
Mitigation / AoU / CompReq
A root cause that is not referenced by any Mitigation.root_causes,
AoU.root_causes (from an assumptions_of_use target in safety_measures), or CompReq.derived_from
is reported as missing reference to Component Requirements or Safety Measures.
Fixing a traceability error means ensuring the reference is explicit: the
Mitigation/CompReq/AoU must import the generated <fta_package>
package (the fta_package of the fault_trees target) and reference the
RootCause stub by name (e.g.
sample_safety_analysis_fta.JustBadLuck).