Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,11 @@ bazel/rules/rules_score/examples/seooc
bazel/rules/rules_score/examples/some_other_library
# Separate local Bazel module (system integrator demo, depends on @seooc//)
bazel/rules/rules_score/examples/integrator
# minimal is a separate local Bazel module too, but its :doc_sources filegroup
# is still loaded directly from the parent workspace (rules_score_doc). Only
# ignore its own convenience symlinks, whose targets loop back into this
# directory and would otherwise trip a symlink-cycle error during globbing.
bazel/rules/rules_score/examples/minimal/bazel-bin
bazel/rules/rules_score/examples/minimal/bazel-minimal
bazel/rules/rules_score/examples/minimal/bazel-out
bazel/rules/rules_score/examples/minimal/bazel-testlogs
6 changes: 3 additions & 3 deletions .github/skills/rules-score/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Use this skill to coordinate; open the specialized skill for the actual work:
| `.trlc` requirement records, `ScoreReq` model, traceability, `assumed_system_requirements` / `feature_requirements` / `component_requirements` / `assumptions_of_use` | **score-requirements** |
| PlantUML diagrams, `architectural_design` / `unit` / `unit_design` / `component` / `dependable_element` structure, architecture/API/sequence validations | **score-architecture** |
| GoogleTest `lobster-tracing` + Given-When-Then, `test_case_coverage.lock.yaml`, attaching tests | **score-testing** |
| FMEA, `FailureMode` / `ControlMeasure` / FTA, `fmea` / `dependability_analysis` | **score-safety-analysis** |
| FMEA, `FailureMode` / `SafetyMeasure` / FTA, `safety_analysis` / `dependability_analysis` | **score-safety-analysis** |

---

Expand All @@ -59,7 +59,7 @@ with a traceability report.
| Architectural Design | `architectural_design` | score-architecture |
| Units & Components | `unit`, `unit_design`, `component` | score-architecture |
| Tests & Coverage | `tests` attr, `test_case_coverage_lock` | score-testing |
| Dependability Analysis | `fmea`, `dependability_analysis` | score-safety-analysis |
| Dependability Analysis | `safety_analysis`, `dependability_analysis` | score-safety-analysis |
| SEooC assembly | `dependable_element` | this skill |

### Hierarchy
Expand Down Expand Up @@ -182,7 +182,7 @@ FMEA, cross-module `deps`, and test-case coverage — see
3. **Implementation & tests** → back each `unit` with a `cc_library` + `cc_test`; annotate tests
with `lobster-tracing` + Given-When-Then; add `test_case_coverage_lock` on components.
*(score-testing)*
4. **Safety analysis** → add `fmea` (FailureMode + ControlMeasure + FTA) and wrap it in a
4. **Safety analysis** → add `safety_analysis` (FailureMode + SafetyMeasure + FTA) and wrap it in a
`dependability_analysis`. *(score-safety-analysis)*
5. **Assemble** → allocate `CompReq` to `component(requirements=…)` and `FeatReq` to
`dependable_element(requirements=…)`; wire `architectural_design`, `components`,
Expand Down
5 changes: 3 additions & 2 deletions .github/skills/score-requirements/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Bazel build/test rules.
## Not for

- Architecture diagrams, `unit` / `component` / `dependable_element` structure → **score-architecture**
- FMEA / FailureMode / ControlMeasure / FTA safety analysis → **score-safety-analysis**
- FMEA / FailureMode / SafetyMeasure / FTA safety analysis → **score-safety-analysis**
- Test annotation and coverage → **score-testing**
- End-to-end SEooC assembly / choosing which skill to use → **rules-score**

Expand Down Expand Up @@ -243,7 +243,8 @@ component-internal requirements with no feature-level parent.

### Assumptions of Use (AoU)

`AoU` extends `ControlMeasure` and captures conditions the integrating project must satisfy.
`AoU` extends `RequirementSafety` (not `SafetyMeasure`) and captures conditions the integrating
project must satisfy, via its own independent, optional `root_causes` field.
The `assumptions_of_use` rule accepts raw `.trlc` **or** `.rst` files carrying `aou_req`
directives (converted to TRLC automatically).

Expand Down
83 changes: 45 additions & 38 deletions .github/skills/score-safety-analysis/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: score-safety-analysis
description: "Step-by-step workflow for creating or extending a FMEA-based safety analysis in TRLC format for S-CORE software components. Use when asked to: add failure modes, create FTA diagrams, add control measures, or validate the safety analysis traceability chain. Covers clustering, FailureMode records, FTA PlantUML files, ControlMeasure records, BUILD wiring, and trlc validation."
description: "Step-by-step workflow for creating or extending a FMEA-based safety analysis in TRLC format for S-CORE software components. Use when asked to: add failure modes, create FTA diagrams, add mitigations, or validate the safety analysis traceability chain. Covers clustering, FailureMode records, FTA PlantUML files, Mitigation records, BUILD wiring, and trlc validation."
argument-hint: "interface or component name to analyse"
---

Expand All @@ -23,7 +23,7 @@ argument-hint: "interface or component name to analyse"

- Adding new failure modes to an existing safety analysis
- Creating FTA diagrams for root-cause decomposition
- Defining ControlMeasure / AoU records for identified root causes
- Defining Mitigation / CompReq / AoU records for identified root causes
- Validating the full traceability chain before a review of a safety analysis

## Key Files and Locations
Expand All @@ -32,9 +32,9 @@ argument-hint: "interface or component name to analyse"
score/<component>/dependability/
├── safety_analysis/
│ ├── failure_modes.trlc # FailureMode records (one per unique root-cause cluster)
│ ├── control_measures.trlc # ControlMeasure / PreventiveMeasure / AoU records
│ ├── safetymeasures.trlc # Mitigation / AoU / CompReq records
│ ├── fta_<failure_mode>.puml # One FTA diagram per FailureMode
│ └── BUILD # fmea() rule — must list all .puml in fta_files filegroup
│ └── BUILD # safety_analysis() rule — must list all .puml in fta_files filegroup
├── assumed_system/
│ └── aous.trlc # AoU records (caller obligations)
└── requirements/
Expand Down Expand Up @@ -64,7 +64,7 @@ than inventing it.

Only once the failure modes, causes, and measures are decided, transcribe them into the files
below (Steps 1–5). This is mechanical: one `FailureMode` per decided effect, one FTA per failure
mode, one measure record per `$BasicEvent`, matching aliases, BUILD wired, `trlc` clean.
mode, one measure record per `$RootCause`, matching aliases, BUILD wired, `trlc` clean.

## Authoring guidance: performing the FMEA

Expand All @@ -85,16 +85,15 @@ safety goal?"* Dismiss low-relevance models with a short rationale.
worst-case terms relative to the safety goal. Then build the FTA top-down to **actionable root
causes**: `$OrGate` (default) when any single cause suffices; `$AndGate` only when all must
co-occur (a fault *and* a failed safety mechanism — this justifies lower residual risk). Decompose
until each `$BasicEvent` is something you can place a measure on.
until each `$RootCause` is something you can place a measure on.

**Step 3 — one measure per root cause**, chosen by *when* it acts:
**Step 3 — one measure per root cause**, chosen by *who* closes it:

| Type | Use when it… | Acts |
|------|--------------|------|
| `PreventiveMeasure` | removes the cause so the fault cannot occur | before |
| `ControlMeasure` | detects/handles the fault at runtime (plausibility check, monitor) | during |
| `Mitigation` | reduces severity/probability after occurrence | after |
| `AoU` | can only be guaranteed by the **integrator/caller** | at integration |
| Type | Use when it… | Closed via |
|------|--------------|------------|
| `Mitigation` | the SEooC itself provides a dedicated safety measure (with a mandatory `justification`) | `root_causes` |
| `CompReq` | a normal, implemented-and-tested component requirement already closes it | `derived_from` |
| `AoU` | can only be guaranteed by the **integrator/caller** | `root_causes` (optional) |

Use an `AoU` to **push an obligation outward** when the SEooC cannot close a cause itself; it must
be forwarded to the integrating project. Record *why* a measure is sufficient (AND-gate argument,
Expand Down Expand Up @@ -128,19 +127,22 @@ ScoreReq.FailureMode <RecordName> {

## Step 2 — Create FTA Diagrams (`fta_<snake_name>.puml`)

One `.puml` file per `FailureMode` record. The `$TopEvent` alias **must** equal the fully-qualified TRLC record name (`<Package>.<RecordName>`).
Use `$FailureMode(name, fm1, fm2="", ..., fm8="")` to link the diagram's top-level node
directly to the `FailureMode` record(s) it covers — `fm1` is mandatory, up to
`fm8` may be given, each a fully-qualified `<Pkg>.<FailureModeRecord>` name.
`fm1` also doubles as the node's connection point for gates.

```plantuml
@startuml

!include fta_metamodel.puml

$TopEvent("<Human-readable top event label>", "<Pkg>.<FailureModeRecord>")
$FailureMode("<Human-readable failure-mode node label>", "<Pkg>.<FailureModeRecord>", "<Pkg>.<FailureModeRecord2>")

$OrGate("OG1", "<Pkg>.<FailureModeRecord>")

$BasicEvent("<Root cause label>", "<Pkg>.<ControlMeasureRecord>", "OG1")
$BasicEvent("<Root cause label 2>", "<Pkg>.<ControlMeasureRecord2>", "OG1")
$RootCause("<Root cause label>", "<RootCauseAlias>", "OG1")
$RootCause("<Root cause label 2>", "<RootCauseAlias2>", "OG1")

@enduml
```
Expand All @@ -149,37 +151,40 @@ $BasicEvent("<Root cause label 2>", "<Pkg>.<ControlMeasureRecord2>", "OG1")

| Procedure | Purpose | `connection` points to |
|-----------|---------|------------------------|
| `$TopEvent(name, alias)` | Top failure mode | — (root, no connection) |
| `$FailureMode(name, fm1, fm2, ..., fm8)` | Top failure mode; `fm1`..`fm8` are `FailureMode` FQNs it covers | — (root, no connection); `fm1` is the alias used by child gates |
| `$OrGate(alias, connection)` | Any child sufficient | parent alias |
| `$AndGate(alias, connection)` | All children required | parent alias |
| `$BasicEvent(name, alias, connection)` | Root cause / leaf | enclosing gate alias |
| `$RootCause(name, alias, connection)` | Root cause / leaf — `alias` must be a **plain TRLC identifier** (no dotted `Package.Name`) | enclosing gate alias |
| `$IntermediateEvent(name, alias, connection)` | Intermediate cause | parent gate alias |
| `$TransferInGate(name, alias, connection)` | Link to sub-tree | parent alias |

**Rules:**
- `$BasicEvent` alias = `<Package>.<ControlMeasureRecordName>` — this IS the traceability link.
- Build bottom-up in the file: `$TopEvent` first, then gates, then `$BasicEvent` leaves.
- The same `ControlMeasure` alias may appear in multiple FTAs (shared root cause).
- `$RootCause` alias is a plain identifier; the `puml_cli` FTA parser auto-generates a `fta_events.trlc` stub (imported as `<name>_fta`) containing a `RootCause` record named after that alias — `Mitigation`/`CompReq`/`AoU` records reference it explicitly (e.g. `root_causes = [sample_safety_analysis_fta.JustBadLuck]`); there is no implicit name-matching.
- Build top-down in the file: `$FailureMode` first, then gates, then `$RootCause` leaves.
- The same `$RootCause` alias may appear in multiple FTAs (shared root cause).
- `$OrGate` is the default for independent root causes; use `$AndGate` only when all causes must co-occur.

## Step 3 — Write ControlMeasure Records (`control_measures.trlc`)
## Step 3 — Write Mitigation Records (`safetymeasures.trlc`)

For every `$BasicEvent` alias in every FTA, define a matching record:
For every `$RootCause` alias in every FTA that isn't already closed by a `CompReq` or `AoU`,
define a matching `Mitigation` record. `root_causes` and `justification` are both
**mandatory**, and `root_causes` must reference the generated `RootCause` stub(s):

```trlc
ScoreReq.ControlMeasure <RecordName> {
safety = ScoreReq.Asil.B
description = "Normative measure text"
version = 1
ScoreReq.Mitigation <RecordName> {
safety = ScoreReq.Asil.B
description = "Normative measure text"
justification = "Why this measure is sufficient to close the root cause"
version = 1
root_causes = [<fta_package>.<RootCauseAlias>]
}
```

Other available types (same pattern, different semantics):
- `ScoreReq.PreventiveMeasure` — prevents the failure from occurring
- `ScoreReq.Mitigation` — reduces severity/probability after occurrence
- `ScoreReq.AoU` — assumption the caller must satisfy; add `mitigates = "<RecordName>"` field
Other ways to close a root cause:
- `ScoreReq.CompReq` — closes it via `derived_from`, referencing the `RootCause` stub alongside any `FeatReq`/`AssumedSystemReq`/`AoU` it also derives from
- `ScoreReq.AoU` — assumption the caller must satisfy; does **not** extend `SafetyMeasure` and has its own independent, optional `root_causes` field (no `justification` required)

**Rule:** `<Package>.<RecordName>` in TRLC must match the `$BasicEvent` alias verbatim.
**Rule:** every `$RootCause` alias must be referenced by at least one `Mitigation.root_causes`, `CompReq.derived_from`, or `AoU.root_causes` — this is validated by `bazel test` on the owning `dependability_analysis` target.

## Step 4 — Update BUILD

Expand All @@ -199,22 +204,24 @@ filegroup(

## Step 5 — Validate

**Pass criteria:** zero errors in `failure_modes.trlc`, `control_measures.trlc`, `aous.trlc`.
**Pass criteria:** zero errors in `failure_modes.trlc`, `safetymeasures.trlc`, `aous.trlc`.
Pre-existing RSL union-type errors (`expected identifier, encountered '['`) are a known trlc v2 / RSL version mismatch — ignore if they appear only in the tooling RSL, not in component files.

**Traceability chain that must be complete:**

```
FailureMode.interface → public_api interface name
FailureMode record → $TopEvent alias
$BasicEvent alias → ControlMeasure / AoU record name
FailureMode record → $FailureMode fm1..fm8 arguments
$RootCause alias → Mitigation.root_causes / CompReq.derived_from / AoU.root_causes
```

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| `$BasicEvent` alias does not match any TRLC record | Ensure `<Pkg>.<RecordName>` is spelled identically in both places |
| `$RootCause` alias is dotted (`Pkg.Name`) | Use a plain identifier — dotted aliases are rejected for root causes |
| `$FailureMode` fm1..fm8 argument does not match any TRLC record | Ensure `<Pkg>.<RecordName>` is spelled identically in both places |
| A root cause (`$RootCause`) is not referenced by any `Mitigation`/`CompReq`/`AoU` | Add an explicit reference to the generated `<fta_package>.<Alias>` `RootCause` stub |
| New `.puml` not in BUILD `fta_files` | Add the file path to the `srcs` list |
| AoU added to `control_measures.trlc` | AoUs belong in `aous.trlc`; both extend `Measure` so the FTA alias still resolves |
| AoU added to `safetymeasures.trlc` | AoUs belong in `aous.trlc`; `AoU` does not extend `SafetyMeasure`, it has its own independent `root_causes` field |
| Wrong RSL used for trlc validation | Always pass the tooling RSL as the first directory argument |
12 changes: 6 additions & 6 deletions bazel/rules/rules_score/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ exports_files([
"templates/section_page.template.rst",
"templates/unit.template.rst",
"templates/component.template.rst",
"templates/fmea.template.rst",
"templates/safety_analysis.template.rst",
"templates/puml_diagram.template.rst",
])

Expand Down Expand Up @@ -88,13 +88,13 @@ py_binary(
visibility = ["//visibility:public"],
)

# FMEA page assembler: builds the failure-mode-centric fmea.rst body in-process
# via the extended TRLCRST library and the FTA chains JSON from puml_cli.
# Safety-analysis page assembler: builds the failure-mode-centric safety_analysis.rst body
# in-process via the extended TRLCRST library and the FTA chains JSON from puml_cli.
py_binary(
name = "fmea_assembler",
srcs = ["src/fmea_assembler.py"],
name = "safety_analysis_assembler",
srcs = ["src/safety_analysis_assembler.py"],
imports = ["src"],
main = "src/fmea_assembler.py",
main = "src/safety_analysis_assembler.py",
visibility = ["//visibility:public"],
deps = [
"@trlc//tools/trlc_rst:trlc_rst_lib",
Expand Down
16 changes: 8 additions & 8 deletions bazel/rules/rules_score/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ for safety related automotive software.
| `architectural_design` | `ArchitecturalDesignInfo` |
| `unit` | `UnitInfo`, `CertifiedScope` |
| `component` | `ComponentInfo` |
| `fmea` | `AnalysisInfo` |
| `safety_analysis` | `AnalysisInfo` |
| `glossary` | `SphinxSourcesInfo` |
| `dependability_analysis` | `DependabilityAnalysisInfo` |
| `dependable_element` | HTML documentation zip (Sphinx) |
Expand Down Expand Up @@ -137,21 +137,21 @@ and collects requirement + architecture + test lobster sources.

---

## `fmea`
## `safety_analysis`

```starlark
fmea(
name = "my_fmea",
safety_analysis(
name = "my_safety_analysis",
failuremodes = [":failure_modes"],
controlmeasures = [":control_measures"],
root_causes = ["fta.puml"],
safetymeasures = [":safetymeasures"],
arch_design = ":my_design",
)
```

**`bazel build`** — generates `fmea.rst` (merged FM / CM / FTA sections),
**`bazel build`** — generates `safety_analysis.rst` (merged FM / Safety Measures / FTA sections),
runs `lobster-trlc` on TRLC inputs, and extracts FTA events from `.puml`
diagrams into `fta.lobster`. Build-only; traceability validation is done
diagrams into `fta_events.trlc`. Build-only; traceability validation is done
by the wrapping `dependability_analysis` test.

---
Expand Down Expand Up @@ -212,7 +212,7 @@ dependable_element(
```starlark
dependability_analysis(
name = "my_da",
fmea = [":my_fmea"],
safety_analysis = [":my_safety_analysis"],
arch_design = ":my_design",
)
```
Expand Down
8 changes: 4 additions & 4 deletions bazel/rules/rules_score/docs/_assets/SeoocExample_FTA.puml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@

!include fta_metamodel.puml

$TopEvent("SampleFailureMode takes over the world", "SampleLibrary.SampleFailureMode")
$FailureMode("SampleFailureMode takes over the world", "SampleLibrary.SampleFailureMode")

$OrGate("OG1", "SampleLibrary.SampleFailureMode")

$IntermediateEvent("SampleFailureMode is Angry", "IEF", "OG1")
$BasicEvent("Just bad luck", "SampleLibrary.JustBadLuck", "OG1")
$RootCause("Just bad luck", "JustBadLuck", "OG1")

$AndGate("AG2", "IEF")
$BasicEvent("No More Cookies", "SampleLibrary.NoMoreCookies", "AG2")
$BasicEvent("No More Coffee", "SampleLibrary.NoMoreCoffee", "AG2")
$RootCause("No More Cookies", "NoMoreCookies", "AG2")
$RootCause("No More Coffee", "NoMoreCoffee", "AG2")

@enduml
35 changes: 32 additions & 3 deletions bazel/rules/rules_score/docs/_assets/fta_metamodel.puml
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,45 @@ sprite $transferin <svg width="60" height="70" viewBox="0 0 60 70" xmlns="http:/
'skinparam linetype polyline
'skinparam linetype ortho

!procedure $TopEvent($name, $alias)
rectangle "$name" as $alias
' $FailureMode(name, fm1, fm2, ..., fm8): a fault-tree top-level node covering
' one or more failure modes, each given as a TRLC 'Package.Name' fully-qualified
' name. The *first* failure mode ($fm1) doubles as this node's alias -- the
' connection point every gate/root-cause ancestor attaches to. Any further
' failure modes ($fm2..$fm8) are rendered as extra notes so a reader can see
' at a glance that this node covers more than one failure mode. PlantUML
' procedures have no true variadic arguments, only trailing defaults, so the
' argument count is capped at 8 failure modes per node.
!procedure $FailureMode($name, $fm1, $fm2="", $fm3="", $fm4="", $fm5="", $fm6="", $fm7="", $fm8="")
rectangle "$name" as $fm1
!if ($fm2 != "")
note right of $fm1: $fm2
!endif
!if ($fm3 != "")
note right of $fm1: $fm3
!endif
!if ($fm4 != "")
note right of $fm1: $fm4
!endif
!if ($fm5 != "")
note right of $fm1: $fm5
!endif
!if ($fm6 != "")
note right of $fm1: $fm6
!endif
!if ($fm7 != "")
note right of $fm1: $fm7
!endif
!if ($fm8 != "")
note right of $fm1: $fm8
!endif
!endprocedure

!procedure $IntermediateEvent($name, $alias, $connection)
rectangle "$name" as $alias
$alias -u-> $connection
!endprocedure

!procedure $BasicEvent($name, $alias, $connection)
!procedure $RootCause($name, $alias, $connection)
usecase "$name" as $alias
$alias -u-> $connection
!endprocedure
Expand Down
Loading
Loading