From af4b472bc291df1f0f4c344f28636bc65411208a Mon Sep 17 00:00:00 2001 From: Jochen Hoenle Date: Wed, 24 Jun 2026 17:48:31 +0200 Subject: [PATCH 1/2] [rules score] add skills - rules score - requirements - architecture - safety-analysis - testing --- .github/skills/rules-score/SKILL.md | 230 ++++++++ .github/skills/score-architecture/SKILL.md | 522 ++++++++++++++++++ .github/skills/score-requirements/SKILL.md | 407 ++++++++++++++ .github/skills/score-safety-analysis/SKILL.md | 220 ++++++++ .github/skills/score-testing/SKILL.md | 212 +++++++ .../docs/user_guide/architectural_design.rst | 120 +++- .../user_guide/dependability_analysis.rst | 100 ++++ .../docs/user_guide/requirements.rst | 71 +++ .../docs/user_guide/tutorial/architecture.rst | 2 +- .../docs/user_guide/tutorial/index.rst | 35 ++ .../docs/user_guide/unit_design.rst | 26 + 11 files changed, 1934 insertions(+), 11 deletions(-) create mode 100644 .github/skills/rules-score/SKILL.md create mode 100644 .github/skills/score-architecture/SKILL.md create mode 100644 .github/skills/score-requirements/SKILL.md create mode 100644 .github/skills/score-safety-analysis/SKILL.md create mode 100644 .github/skills/score-testing/SKILL.md diff --git a/.github/skills/rules-score/SKILL.md b/.github/skills/rules-score/SKILL.md new file mode 100644 index 00000000..62ada196 --- /dev/null +++ b/.github/skills/rules-score/SKILL.md @@ -0,0 +1,230 @@ + + +--- +name: rules-score +description: "Central entry point for building a Safety Element out of Context (SEooC) with the rules_score Bazel rules. USE FOR: creating or assembling a dependable_element end to end, understanding the full requirements → architecture → units → tests → safety-analysis workflow, wiring all rules_score targets together, and deciding which specialized skill to use for each work product. Delegates to score-requirements, score-architecture, score-testing, and score-safety-analysis. Use when scaffolding a new SEooC, adding a component/unit across all layers, or coordinating multi-layer changes." +argument-hint: "the SEooC / dependable element to build or extend" +--- + +# rules_score — SEooC Orchestration Skill + +The central skill for building a **Safety Element out of Context (SEooC)** with the `rules_score` +Bazel rules. It gives the end-to-end picture and the assembly recipe for a `dependable_element`, +then **delegates the details of each work product to a specialized skill**. + +> **Source of truth**: the rule macros under `bazel/rules/rules_score/private/`, the aggregator +> [`bazel/rules/rules_score/rules_score.bzl`](../../../bazel/rules/rules_score/rules_score.bzl), the +> validator specs in [`validation/core/docs/specifications/`](../../../validation/core/docs/specifications), +> and the runnable examples in [`bazel/rules/rules_score/examples/`](../../../bazel/rules/rules_score/examples) +> (`minimal/` and `seooc/`). When source and documentation disagree, the source wins. + +--- + +## Delegation Map + +Use this skill to coordinate; open the specialized skill for the actual work: + +| You are working on… | Use skill | +|---------------------|-----------| +| `.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** | + +--- + +## The Dependable Element + +A `dependable_element` is the top-level SEooC entity. It aggregates every work product and, at +build time, verifies their mutual consistency and assembles them into Sphinx HTML documentation +with a traceability report. + +| Work product | Rule(s) | Skill | +|--------------|---------|-------| +| Assumed System Requirements | `assumed_system_requirements` | score-requirements | +| Feature Requirements | `feature_requirements` | score-requirements | +| Component Requirements | `component_requirements` | score-requirements | +| Assumptions of Use | `assumptions_of_use` | score-requirements | +| 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 | +| SEooC assembly | `dependable_element` | this skill | + +### Hierarchy + +``` +dependable_element (SEooC boundary) +└── component (groups units; owns component requirements + integration tests) + ├── unit (implementation + unit design + unit tests) + └── component (nestable to arbitrary depth) + └── unit +``` + +A `unit` must always sit inside a `component`; a `component` may contain units and/or other +components. + +--- + +## End-to-End Recipe + +All rules load from one aggregator: + +```starlark +load( + "@score_tooling//bazel/rules/rules_score:rules_score.bzl", + "architectural_design", + "assumed_system_requirements", + "component", + "dependable_element", + "feature_requirements", + "unit", + "unit_design", +) +``` + +The smallest complete SEooC, wired end-to-end in a single BUILD file (verbatim from +[`examples/minimal/BUILD`](../../../bazel/rules/rules_score/examples/minimal/BUILD)): + +```starlark +assumed_system_requirements( + name = "assumed_system_requirements", + srcs = ["requirements/asr.trlc"], + visibility = ["//visibility:public"], +) + +feature_requirements( + name = "feature_requirements", + srcs = ["requirements/feature_requirements.trlc"], + visibility = ["//visibility:public"], + deps = [":assumed_system_requirements"], +) + +architectural_design( + name = "my_arch", + static = ["docs/static_design.puml"], +) + +cc_library( + name = "my_unit_lib", + srcs = ["src/my_unit.cpp"], + hdrs = ["src/my_unit.h"], +) + +cc_test( + name = "my_unit_test", + srcs = ["test/my_unit_test.cpp"], + deps = [":my_unit_lib", "@googletest//:gtest_main"], +) + +unit_design( + name = "MyUnit_design", + static = ["docs/class_design.puml"], +) + +unit( + name = "MyUnit", + scope = ["//:my_unit_lib"], + tests = [":my_unit_test"], + unit_design = [":MyUnit_design"], + implementation = [":my_unit_lib"], +) + +component( + name = "MyComponent", + components = [":MyUnit"], + requirements = [], + tests = [], +) + +dependable_element( + name = "my_element", + architectural_design = [":my_arch"], + assumptions_of_use = [], + components = [":MyComponent"], + dependability_analysis = [], + integrity_level = "B", + requirements = [":feature_requirements"], + tests = [], +) +``` + +Note how the diagram alias must equal the target name — `docs/static_design.puml` declares +`package "my_element" ... { component "MyComponent" { component "MyUnit" } }`, matching +`dependable_element(name = "my_element")`, `component(name = "MyComponent")`, and +`unit(name = "MyUnit")`. This is enforced by the `bazel_component` validator (see +**score-architecture**). + +For a fuller SEooC — nested components, public/internal API, sequence diagrams, AoUs, glossary, +FMEA, cross-module `deps`, and test-case coverage — see +[`examples/seooc/`](../../../bazel/rules/rules_score/examples/seooc). + +--- + +## Suggested Order of Work + +1. **Requirements** → write `AssumedSystemReq`, `FeatReq`, then `CompReq`; wire the three rules with + `deps` chaining. *(score-requirements)* +2. **Architecture** → draw the `static` PlantUML tree and add `dynamic`/`public_api`/`internal_api` + as needed; create `unit`, `unit_design`, and `component` targets whose names match the diagram. + *(score-architecture)* +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 + `dependability_analysis`. *(score-safety-analysis)* +5. **Assemble** → allocate `CompReq` to `component(requirements=…)` and `FeatReq` to + `dependable_element(requirements=…)`; wire `architectural_design`, `components`, + `assumptions_of_use`, `dependability_analysis`, `glossary`, and `integrity_level`. + +--- + +## Build, Test, Validate + +```bash +# Build the SEooC docs + run every consistency validation +bazel build //path/to:my_element + +# Run all tests: unit/integration tests, trlc --verify, coverage-drift checks +bazel test //... + +# Refresh a component's coverage lock after intended test changes +bazel run //path/to:MyComponent.update +``` + +`dependable_element(maturity = "development")` downgrades scope- and coverage-violations to +warnings while a design is in progress; switch to `"release"` before certification. + +--- + +## Key Cross-Cutting Facts + +- **ASIL vs. integrity_level**: requirement records use `ScoreReq.Asil` = `QM`/`B`/`D` only; + `dependable_element.integrity_level` uses `A`/`B`/`C`/`D` (D > C > B > A) for the element + hierarchy. +- **Naming**: diagram aliases must equal Bazel target names. `bazel_component` is case-insensitive; + all other validators are case-sensitive. +- **Traceability spine**: `AssumedSystemReq → FeatReq → CompReq` (version-pinned `@`) → + `lobster-tracing` in tests → `test_case_coverage.lock.yaml`; `FailureMode.interface` links safety + analysis to the `public_api` diagram. + +--- + +## References + +- [`examples/minimal/`](../../../bazel/rules/rules_score/examples/minimal) — smallest end-to-end SEooC +- [`examples/seooc/`](../../../bazel/rules/rules_score/examples/seooc) — full-featured SEooC +- [`rules_score.bzl`](../../../bazel/rules/rules_score/rules_score.bzl) — the rule aggregator (all public symbols) +- [`docs/user_guide/general.rst`](../../../bazel/rules/rules_score/docs/user_guide/general.rst) — dependable-element concept + validations +- [`validation/core/docs/specifications/`](../../../validation/core/docs/specifications) — normative validator specs diff --git a/.github/skills/score-architecture/SKILL.md b/.github/skills/score-architecture/SKILL.md new file mode 100644 index 00000000..f1a3f1a3 --- /dev/null +++ b/.github/skills/score-architecture/SKILL.md @@ -0,0 +1,522 @@ + + +--- +name: score-architecture +description: "Software architectural design for S-CORE SEooCs using the rules_score Bazel rules. USE FOR: writing PlantUML static/dynamic/public_api/internal_api diagrams, structuring dependable_element → component → unit hierarchies, wiring architectural_design / unit / unit_design / component / dependable_element targets, PlantUML stereotype and interface/port conventions, the declared-vs-implemented architecture consistency check, integrity levels, certified scope, and requirement allocation to architectural elements. Use when working on architecture, .puml files, component/unit structure, or the rules_score architecture rules." +argument-hint: "component/unit or diagram to model" +--- + +# S-CORE Architecture Skill + +Software architectural design for a **Safety Element out of Context (SEooC)** using the +`rules_score` Bazel rules. Architecture is expressed as PlantUML diagrams **and** as Bazel +targets; `rules_score` automatically verifies that the two stay consistent and assembles them +into Sphinx documentation with a traceability report. + +**The PlantUML `static` diagram is the design; the Bazel model is a mechanical transcription of +it.** Always design first (collaboratively, in PlantUML), agree the decomposition with the user, +*then* write the Bazel targets. See **Workflow** below. + +> **Source of truth**: the rule macros under `bazel/rules/rules_score/private/` +> (`architectural_design.bzl`, `unit.bzl`, `unit_design.bzl`, `component.bzl`, +> `dependable_element.bzl`), the validator specifications in +> [`validation/core/docs/specifications/`](../../../validation/core/docs/specifications) (what is +> checked + which PlantUML notation is valid), and the standalone examples in +> [`bazel/rules/rules_score/examples/`](../../../bazel/rules/rules_score/examples) +> (`seooc/` and `minimal/`). When source and documentation disagree, the source wins. Every +> code block in this skill is quoted verbatim from those examples/specs. + +## When to use + +- Writing PlantUML architecture diagrams (`static`, `dynamic`, `public_api`, `internal_api`) +- Structuring the `dependable_element → component → unit` hierarchy in Bazel +- Wiring `architectural_design`, `unit`, `unit_design`, `component`, `dependable_element` +- Understanding the architecture consistency, certified-scope, and integrity-level checks + +## Not for + +- Requirement records and traceability → **score-requirements** +- FMEA / FailureMode / FTA safety analysis → **score-safety-analysis** +- Test annotation and coverage → **score-testing** +- End-to-end SEooC assembly / choosing which skill to use → **rules-score** + +--- + +## Workflow: design first in PlantUML, then model in Bazel + +Architecture work in `rules_score` has **two phases, in strict order**. Do not merge them, and do +not start with the Bazel targets. + +### Step 1 — Design the *targeted architecture* (PlantUML `static`) — collaborative + +The static PlantUML diagram is the **design artifact** and the **first thing you produce**. It +captures the intended decomposition: which components and units exist, how they nest, and which +interfaces connect them. This is a *design decision*, not a transcription — use the criteria in +**Determining Components & Units** below to shape it. + +**Collaborate with the user here — do NOT silently guess a decomposition.** Unit/component +boundaries are rarely unambiguous, so when uncertain: + +- **Propose** one or two concrete decomposition options, each with the trade-offs (cohesion, + interface count, requirement allocation, failure containment), and ask the user to choose. +- **Ask** clarifying questions about feature boundaries, ownership, which behaviour is public vs + internal, and expected unit interactions. +- **Iterate** on the `static` diagram (and, if useful, `public_api` / `internal_api` / `dynamic`) + until the user **explicitly agrees**. The agreed diagram is the **targeted architecture**. + +> Step 1 is not done until the user has signed off on the decomposition. Treat an unclear +> component/unit split as a blocker to resolve *with the user*, not a detail to invent. + +### Step 2 — Model the agreed architecture in Bazel — mechanical + +Only *after* the targeted architecture is agreed, translate it **1:1** into Bazel targets. This +step is **mechanical**: every `<>` / `<>` / `<>` in the diagram becomes a +`dependable_element` / `component` / `unit` target **with the same name and the same nesting**. +The `bazel_component` validator enforces that the Bazel model matches the diagram exactly, so +there are no design decisions left here — only faithful transcription. + +> Never write the Bazel targets first and reverse-engineer a diagram to match. The diagram is the +> design; the Bazel model follows it. + +--- + +## Declared vs. Implemented Architecture + +- **Declared** — the PlantUML diagrams passed to `architectural_design` (`static`, `dynamic`, + `public_api`, `internal_api`). What the architecture is *supposed* to look like. +- **Implemented** — the actual Bazel targets: `unit(implementation = [...])` wraps real source, + `component(components = [...])` groups units, `dependable_element(components = [...])` assembles + the SEooC. What the architecture *actually* is. + +Because both views are authored independently they can drift. `rules_score` runs an **architecture +consistency check** at build time: every component/unit in `dependable_element.components` must +appear, under the same name, in the `static` PlantUML diagram — and vice versa. A mismatch fails +the build. + +--- + +## Hierarchy + +``` +dependable_element (SEooC — complete Safety Element out of Context) +└── component (groups units; owns component requirements + integration tests) + ├── unit (smallest independently verifiable element: implementation + unit tests) + └── component (components can nest for deeper hierarchies) + └── unit +``` + +Two rules: + +- A `unit` must always be wrapped in a `component` — it cannot sit directly under + `dependable_element`. +- A `component` may nest: it can contain other components as well as units, to arbitrary depth. + +--- + +## Determining Components & Units (authoring guidance) + +The rules only check that the declared and implemented structure *match* — not +that the decomposition is *good*. Deciding what becomes a `component` vs a `unit` +is a design activity governed by the S-CORE +[Architecture Design process area](https://eclipse-score.github.io/process_description/main/process_areas/architecture_design/index.html); +the authoritative distillation lives in +[`docs/user_guide/architectural_design.rst`](../../../bazel/rules/rules_score/docs/user_guide/architectural_design.rst) +(*Determining Components and Units*). Decompose **top-down** from the SEooC's +public API and requirements until every leaf is a testable unit. + +**A `unit` is the smallest independently verifiable element.** Model as a unit when ALL hold: + +- Single responsibility — statable in one sentence with no "and". +- Independently unit-testable through a narrow interface, without the rest of the SEooC. +- Cohesive implementation — its source files change together and share data. +- One owner; backed by a `unit_design` validated against the code. + +> Split a unit as soon as its class diagram grows unrelated class clusters or its +> tests split into groups that never share fixtures. + +**A `component` groups collaborating units** that realise one feature or provide one +internal interface. Introduce a component when: + +- Several units together deliver one feature / one internal API. +- There is behaviour that only emerges from unit *interaction* → owns **component + integration tests** and **`CompReq`** records. +- You need a stable boundary to allocate requirements and reason about in safety analysis. + +**Boundary heuristics** (high cohesion / low coupling applied to the element levels): + +| Heuristic | Rule of thumb | +|-----------|---------------| +| Cohesion first | Group what changes together / shares data; split what changes for different reasons. | +| Minimise interfaces | Prefer the decomposition with the *fewest, narrowest* interfaces between elements. A chatty boundary is misplaced. | +| Follow requirement allocation | A `CompReq` belongs to exactly one component. A req that splits across groups marks a boundary; one that stays inside keeps the group together. | +| Failure containment | A boundary is also a safety-analysis boundary — draw it so a failure can be argued and a control measure placed at one element. | +| Isolation-testable | If a candidate unit can't be unit-tested alone, merge it or introduce an internal-API seam to substitute the dependency. | + +**Public vs internal interface:** an interface is **public API** only when it is part +of the SEooC's contract with its environment (bound from `<>`, feeds +`FailureMode.interface`). Keep it minimal — every public method is an obligation and a +safety-analysis entry point. Everything else is **internal API**, modeled inside the +owning element's namespace. Promote internal → public only when an external requirement forces it. + +**Anti-patterns:** folder-driven decomposition (component per directory), god unit +(unrelated responsibilities accreted into one unit), anaemic component (only forwards +calls, owns no tests/requirements), leaky public API (exposed for convenience, drags in +extra failure modes and AoUs). + +--- + +## Static Architecture (PlantUML) — Step 1 (design) + +This is the **design step**: produce and agree this diagram with the user *before* any Bazel +targets exist (see **Workflow**). Write a class/component diagram that names **every** `component` +and `unit` of the intended decomposition. +The validator identifies elements by their **stereotype**, not by the PlantUML keyword — both +`package` and `component` keywords are accepted at each level. + +| Stereotype | Valid keywords | Meaning | Bazel rule | +|------------|----------------|---------|------------| +| `<>` | `package`, `component` | SEooC boundary | `dependable_element` | +| `<>` | `component`, `package` | Architectural component | `component` | +| `<>` | `component`, `package` | Leaf implementation unit | `unit` | + +```text +@startuml static_design + +package "Safety Software SEooC Example" as safety_software_seooc_example <> { + component "ComponentExample" as component_example <> { + component "Unit 1" as unit_1 <> + component "Unit 2" as unit_2 <> + component "Sub Component Example" as sub_component_example <> + + interface "InternalInterface" as InternalInterface + unit_1 -l-( InternalInterface + unit_2 )-r- InternalInterface + } +} + +interface "SampleLibraryAPI" as SampleLibraryAPI + +safety_software_seooc_example )-d- SampleLibraryAPI + +@enduml +``` + +*(Verbatim from [`examples/seooc/design/static_design.puml`](../../../bazel/rules/rules_score/examples/seooc/design/static_design.puml). The three `<>`/`<>` +names — `component_example`, `unit_1`, `unit_2`, `sub_component_example` — are exactly the Bazel +target names, and `safety_software_seooc_example` matches the `dependable_element` name.)* + +### Interfaces & ports + +Any component-type element (`<>` or `<>`) can bind an interface with lollipop +syntax — `-(` for a **required** (incoming) binding and `)-` for a **provided** (outgoing) one. +The `bazel_component` validator does not check interface bindings themselves, but the API and +sequence validators do (see below). + +When you need an explicitly named, standalone binding point (e.g. to distinguish multiple provided +interfaces), declare a `portin` / `portout` inside the `<>` or `<>` element +(from the `bazel_component` validator spec): + +```text +package "MySeooc" as MySeooc <> { + portin " " as p_in ' required interface port + portout " " as p_out ' provided interface port +} + +interface "IRequired" as IRequired +interface "IProvided" as IProvided + +p_in -( IRequired : requires +p_out )- IProvided : provides +``` + +- `portin` / `portout` must be declared **inside** the `<>` or `<>` element. +- A plain `package` **without** a stereotype cannot carry interface bindings. +- Elements with other stereotypes (`actor`, `database`, …) are not valid on the left of a binding. + +--- + +## Dynamic, Public API, Internal API + +Every diagram kind is checked by one or more validators (see **Active validations** below) — none +of them is "documentation only". `static` is checked against the Bazel structure; the others are +cross-checked against each other and against the implementation. + +| View | Attribute | Cross-checked against | Purpose | +|------|-----------|-----------------------|---------| +| **Static** | `static` | Bazel `dependable_element`/`component`/`unit` tree; internal API; public API; sequences | Structural component/unit tree — the anchor for all other checks | +| **Dynamic** (sequence) | `dynamic` | Static component diagram; internal API | Unit interactions — participant aliases and cross-unit calls | +| **Public API** | `public_api` | Static diagram (top-level interfaces bound from the SEooC) | Interfaces the SEooC exposes; also feeds `FailureMode.interface` traceability | +| **Internal API** | `internal_api` | Static component diagram; sequences | Interfaces between components/units inside the SEooC | + +Real example diagrams (verbatim, from [`examples/seooc/design/`](../../../bazel/rules/rules_score/examples/seooc/design)): + +```text +' dynamic_design.puml — sequence diagram +@startuml +participant "Unit 1" as unit_1 <> +participant "Unit 2" as unit_2 <> + +unit_1 -> unit_2 : GetData() +unit_2 --> unit_1 : return : Data* +@enduml +``` + +```text +' public_api.puml — top-level interface exposed by the SEooC +@startuml +namespace safety_software_seooc_example { + interface "SampleLibraryAPI" as SampleLibraryAPI { + + GetNumber(): int + } +} +@enduml +``` + +```text +' internal_api.puml — interface modeled inside its owning component's namespace +@startuml +namespace safety_software_seooc_example { + namespace component_example { + interface "InternalInterface" as InternalInterface <>{ + {abstract} GetData(BindingType binding): Data* + } + } +} +@enduml +``` + +- **Public API** interfaces must be top-level in the static diagram and bound from the `<>` + (e.g. `safety_software_seooc_example )-d- SampleLibraryAPI`); interfaces nested inside + components/units are treated as *internal* API. +- **Internal API** interfaces are modeled inside the owning element's `namespace` so their + fully-qualified name reflects containment. + +--- + +## Bazel Rules — Step 2 (mechanical) + +Do this **only after the targeted architecture is agreed with the user** (see **Workflow**). Each +`<>` / `<>` / `<>` in the agreed `static` diagram maps to exactly one +target below, **same name, same nesting** — a faithful transcription, not a new design. + +Load from the aggregator: + +```starlark +load( + "@score_tooling//bazel/rules/rules_score:rules_score.bzl", + "architectural_design", + "unit", + "unit_design", + "component", + "dependable_element", +) +``` + +### `architectural_design` + +One target bundles every diagram kind (from [`examples/seooc/design/BUILD`](../../../bazel/rules/rules_score/examples/seooc/design/BUILD)): + +```starlark +architectural_design( + name = "sample_seooc_design", + static = ["static_design.puml", "arch_design.rst"], + dynamic = ["dynamic_design.puml"], + public_api = ["public_api.puml"], + internal_api = ["internal_api.puml"], + visibility = ["//visibility:public"], + # maturity = "development", # write validation findings without failing the build +) +``` + +`static`/`dynamic` accept `.puml`, `.plantuml`, `.png`, `.svg`, `.rst`, `.md`. To combine a +diagram with prose, add both the RST/Markdown wrapper *and* the referenced `.puml` to the same +list (as `static_design.puml` + `arch_design.rst` above); the wrapper embeds the diagram with +`.. uml:: file.puml`. + +### `unit_design` + +```starlark +# examples/seooc/unit_1/docs/BUILD — globs the class-diagram .puml + its RST wrapper +unit_design( + name = "unit_design", + static = glob(["*.puml", "*.rst"]), + visibility = ["//visibility:public"], +) +``` + +The unit-design class diagram is validated against the C++ implementation (see **Active +validations**). Real class diagram from [`unit_1/docs/unit_1_class_diagram.puml`](../../../bazel/rules/rules_score/examples/seooc/unit_1/docs/unit_1_class_diagram.puml): + +```text +@startuml unit_1_class_diagram +namespace unit_1 { + class Foo { + + GetNumber() : uint8_t + + SetNumber(value : uint8_t) : void + } +} +@enduml +``` + +**RST heading convention**: unit-design RST fragments are `.. include::`-d into the generated unit +page, which already uses `=` and `-` headings. Any title inside the fragment **must** use a +not-yet-used character such as `^` so it becomes a sub-subsection. + +### `unit` + +From [`examples/seooc/unit_1/BUILD`](../../../bazel/rules/rules_score/examples/seooc/unit_1/BUILD): + +```starlark +unit( + name = "unit_1", + unit_design = ["//unit_1/docs:unit_design"], + implementation = [":unit_1_lib"], # cc_library/cc_binary/rust_library/... + scope = ["//unit_1:unit_1_lib"], # extra targets in the certified package tree + tests = [":unit_1_test"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "unit_1_lib", + srcs = ["foo.cpp"], + hdrs = ["foo.h"], + deps = ["@some_other_library"], +) + +cc_test( + name = "unit_1_test", + srcs = ["foo_test.cpp"], + deps = [":unit_1_lib", "@googletest//:gtest_main"], +) +``` + +### `component` + +From [`examples/seooc/BUILD`](../../../bazel/rules/rules_score/examples/seooc/BUILD) — a component can +contain both units and nested components: + +```starlark +component( + name = "component_example", + components = [ + "//unit_1:unit_1", + "//unit_2:unit_2", + ":sub_component_example", # nested component + ], + requirements = ["//docs/requirements:component_requirements"], # component_requirements targets + test_case_coverage_lock = "test_case_coverage.lock.yaml", # see score-testing + tests = [], +) + +component( + name = "sub_component_example", + requirements = ["//docs/requirements:component_requirements_sub"], + tests = [], +) +``` + +### `dependable_element` (SEooC) + +From [`examples/seooc/BUILD`](../../../bazel/rules/rules_score/examples/seooc/BUILD): + +```starlark +dependable_element( + name = "safety_software_seooc_example", + architectural_design = ["//design:sample_seooc_design"], + requirements = ["//docs/requirements:feature_requirements"], # FeatReq targets + assumptions_of_use = ["//docs:sample_aous"], + dependability_analysis = [":sample_dependability_analysis"], + components = [":component_example"], + tests = [], + integrity_level = "B", # A/B/C/D, hierarchy D > C > B > A + glossary = ["//docs:glossary"], + maturity = "development", # scope/coverage violations become warnings + aou_forwarding = "aou_forwarding.yaml", + deps = ["@some_other_library//:other_seooc"], + visibility = ["//visibility:public"], +) +``` + +> `integrity_level` uses `A`/`B`/`C`/`D` for the element hierarchy. Requirement records +> themselves use the `ScoreReq.Asil` enum, which only has `QM`/`B`/`D` (see **score-requirements**). + +For the smallest possible SEooC wired end-to-end in a single BUILD file, see +[`examples/minimal/BUILD`](../../../bazel/rules/rules_score/examples/minimal/BUILD). + +--- + +## Active Validations (build time) + +`rules_score` runs a set of consistency validators at `bazel build`/`bazel test` time. Their +normative behaviour is specified in +[`validation/core/docs/specifications/`](../../../validation/core/docs/specifications) — **this is +the source of truth for what is checked and which notation is valid**. + +| Validator | Spec | Compares | Case | +|-----------|------|----------|------| +| **Bazel ↔ component** | `bazel_component.md` | `dependable_element`/`component`/`unit` targets ↔ `static` PlantUML | insensitive | +| **Component ↔ public API** | `component_public_api.md` | top-level interfaces in `static` ↔ `public_api` class diagram; must be bound from the SEooC | sensitive | +| **Component ↔ internal API** | `component_internal_api.md` | interfaces in `static` ↔ `internal_api` diagram | sensitive | +| **Component ↔ sequence** | `component_sequence.md` | unit aliases + interface connections in `static` ↔ `dynamic` sequence diagrams | sensitive | +| **Sequence ↔ internal API** | `sequence_internal_api.md` | sequence method calls ↔ `internal_api` interfaces (method name, consumer/provider role, interface coverage) | sensitive | +| **Class design ↔ implementation** | `class_design_implementation.md` | `unit_design` class diagram ↔ C++ implementation (entities, methods, variables, enums, relationships, templates) | sensitive + type normalization | + +Additional element-level checks (see [`docs/user_guide/general.rst`](../../../bazel/rules/rules_score/docs/user_guide/general.rst)): + +| Check | Rule | Effect | +|-------|------|--------| +| **Certified scope** | every target reached via `unit.implementation` must lie in the package tree declared by the element's `unit`/`component` targets | non-certified external deps fail the build | +| **Integrity level** | a `dependable_element` must not `deps` on one with a lower `integrity_level` (D > C > B > A) | violation fails the build | + +> **Only `bazel_component` is case-insensitive** — every other validator matches names +> case-sensitively. Keep diagram aliases identical to the code/target names. +> +> `maturity = "development"` downgrades scope and coverage violations to warnings; switch back to +> `"release"` before certification. + +--- + +## Requirement Allocation + +- **`CompReq`** → `component(requirements = [...])` (one component per file). +- **`FeatReq`** → `dependable_element(requirements = [...])`. + +Traceability from a feature requirement down to implementing components runs through the +`FeatReq → CompReq → component` chain. See **score-requirements** for details. + +--- + +## Conventions + +- Diagram aliases must **match the Bazel target name** — `bazel_component` compares + case-insensitively, but every other validator is case-sensitive, so keep them identical. +- Model down to the `unit` level in the static diagram; every implemented unit must appear (and no + extra ones — missing *and* extra elements both fail). +- Sequence diagrams are validated: participant aliases must equal the component-diagram unit + aliases, and every cross-unit call must correspond to an interface connection (and be declared in + the internal API). Keep them at the unit-interaction level. +- Unit-design class diagrams are validated against the C++ implementation — the design is the + contract (implementation-only members are allowed, design-only members are not). +- Prefer `.svg` over `.png` for diagrams checked into git (text-based, diffs cleanly). + +--- + +## References + +- [`examples/seooc/`](../../../bazel/rules/rules_score/examples/seooc) — complete working SEooC (`BUILD`, `design/`, `unit_1/`) +- [`examples/minimal/`](../../../bazel/rules/rules_score/examples/minimal) — smallest end-to-end SEooC in one BUILD file +- [`validation/core/docs/specifications/`](../../../validation/core/docs/specifications) — normative validator specs (source of truth for notation) +- [`docs/user_guide/architectural_design.rst`](../../../bazel/rules/rules_score/docs/user_guide/architectural_design.rst) — narrative guide +- [`docs/user_guide/general.rst`](../../../bazel/rules/rules_score/docs/user_guide/general.rst) — element-level validation reference +- [PlantUML](https://plantuml.com/) — diagram notation diff --git a/.github/skills/score-requirements/SKILL.md b/.github/skills/score-requirements/SKILL.md new file mode 100644 index 00000000..430b895c --- /dev/null +++ b/.github/skills/score-requirements/SKILL.md @@ -0,0 +1,407 @@ + + +--- +name: score-requirements +description: "Requirements engineering for S-CORE projects with TRLC and the rules_score Bazel rules. USE FOR: writing .trlc requirement records (AssumedSystemReq, FeatReq, CompReq, AoU), understanding the ScoreReq requirements model (.rsl), traceability chains (AssumedSystemReq → FeatReq → CompReq), version pinning, ASIL classification, wiring assumed_system_requirements / feature_requirements / component_requirements / assumptions_of_use Bazel targets, requirement allocation to components, embedding images/diagrams in descriptions, and validating requirements with bazel test. Use when working on requirements, .trlc/.rsl files, traceability, or safety classifications." +argument-hint: "requirement level (system/feature/component) or record to add" +--- + +# S-CORE Requirements Skill + +Requirements engineering for **S-CORE** projects using [TRLC](https://github.com/bmw-software-engineering/trlc) +(Treat Requirements Like Code) and the `rules_score` Bazel rules. Requirements are written as +code, stored next to the source, version-controlled with Git, and validated automatically by +Bazel build/test rules. + +> **Source of truth**: the `ScoreReq` model in +> [`bazel/rules/rules_score/trlc/config/score_requirements_model.rsl`](../../../bazel/rules/rules_score/trlc/config/score_requirements_model.rsl) +> and the rule macros under `bazel/rules/rules_score/private/`. A complete, standalone working +> example lives in [`bazel/rules/rules_score/examples/seooc/`](../../../bazel/rules/rules_score/examples/seooc). +> When source and documentation disagree, the source wins. + +## When to use + +- Writing `.trlc` requirement records or `.rsl` schema extensions +- Wiring `assumed_system_requirements`, `feature_requirements`, `component_requirements`, + or `assumptions_of_use` Bazel targets +- Establishing or updating traceability (`derived_from`) with version pinning +- Allocating requirements to components / dependable elements +- Validating requirements with `bazel test` + +## Not for + +- Architecture diagrams, `unit` / `component` / `dependable_element` structure → **score-architecture** +- FMEA / FailureMode / ControlMeasure / FTA safety analysis → **score-safety-analysis** +- Test annotation and coverage → **score-testing** +- End-to-end SEooC assembly / choosing which skill to use → **rules-score** + +--- + +## Requirement Hierarchy & Traceability + +``` +AssumedSystemReq → FeatReq → CompReq + (System) (Feature) (Component) + \ ↑ + \________________________/ +``` + +| Type | Description | Traceability | +|------|-------------|-------------| +| **AssumedSystemReq** | System-level requirements the SEooC receives from the wider context. Too high-level for one component. | Root — no parent | +| **FeatReq** | Refined feature/safety requirements. Still require multiple components. | **Must** reference ≥1 `AssumedSystemReq` via `derived_from` | +| **CompReq** | Requirements allocated to exactly one component; directly implementable and testable. | **Optionally** references ≥1 `FeatReq`/`AssumedSystemReq` via `derived_from` | + +Traceability is enforced by the TRLC type system. **Version pinning** (e.g. `@1`) means that when +a parent requirement's content changes (and its `version` is bumped), every downstream reference +must be explicitly updated — a change is never silently absorbed. + +--- + +## Workflow: author the content first, then wire it + +Requirements work has **two phases, in order** — do not jump straight to TRLC syntax: + +### Step 1 — Author the requirement (engineering, sometimes collaborative) + +Decide *what* the requirement says and *at which level* it belongs. This is an engineering +decision governed by the definition of the requirements process in +[`docs/user_guide/requirements.rst`](../../../bazel/rules/rules_score/docs/user_guide/requirements.rst) +(*Writing Good Requirements*). Refine **top-down**: `AssumedSystemReq` → `FeatReq` → `CompReq`. + +**Collaborate when the level or intent is unclear — do NOT silently invent a requirement.** + +- If it is ambiguous whether a need is system-, feature-, or component-level, **propose** a + placement (with rationale) and confirm with the requirements owner. +- If a source statement is vague, unverifiable, or bundles several concerns, **propose a + sharpened, atomic rewrite** and ask before committing to it. +- Confirm the `safety` (ASIL) level rather than assuming it. + +### Step 2 — Wire it in TRLC + Bazel (mechanical) + +Only once the content and level are agreed, transcribe it into a `.trlc` record and wire the +Bazel target. This is mechanical: pick the matching `ScoreReq` type, fill the mandatory fields, +add version-pinned `derived_from`, and add the file to the requirement rule's `srcs`. + +## Authoring guidance: writing good requirements + +Authoritative rules: [`requirements_guidelines.md`](../../../validation/ai_checker/guidelines/requirements/requirements_guidelines.md) +(applied by the AI quality check) and *Writing Good Requirements* in the doc above. + +**Template:** *subject* **shall** *verb* [*object*] [*parameter*] [*condition*] — at least one +of object/parameter/condition present. E.g. "The component **shall** detect if a key-value pair +got corrupted and set its status to INVALID during every restart." + +**Quality criteria:** + +- **Unambiguous** — one reading; `:term:` glossary nouns over vague words (*fast*, *as appropriate*). +- **Verifiable** — a test/review can objectively pass/fail it. +- **Atomic** — one `shall`; split "and"/"or" and unbounded lists (*etc.*). +- **Consistent** — no contradiction with other requirements. +- **Complete** — subject + verb + ≥1 of object/parameter/condition; fully specifies behaviour + *at its own level* without pre-empting lower-level detail. +- **Necessary** — traces to a parent or `rationale`. + +Use `shall` for obligations (non-normative notes → `note`, not `description`). Keep implementation +detail out of system/feature reqs — but an *intentional* design constraint is a legitimate +requirement, not a level violation. + +**Choose the level by scope/observer:** `AssumedSystemReq` = need from *outside* the SEooC (black +box); `FeatReq` = spans **several** components on one feature, solution-neutral; `CompReq` = fully +implementable/testable **inside one component**. + +**Refinement (`derived_from`):** each child is a narrower, consistent refinement; children together +must be **sufficient** to satisfy the parent; inherit the parent's ASIL or **justify** lowering it; +bump `version` and re-pin children (`@1`→`@2`) on any content change. + +```text +# Weak — unverifiable, multi-concern, leaks implementation +"The manager should quickly handle values and store them efficiently in a hash map." +# Better — atomic, verifiable, follows the template +"The numeric value manager shall return the most recently stored uint8_t value on every read access." +``` + +--- + +## The ScoreReq Model (source of truth) + +The schema is defined once in `score_requirements_model.rsl` (package `ScoreReq`). Projects +**import** it — they do not redefine it. Key facts: + +### `Asil` enum — only three values + +``` +QM B D +``` + +There is **no** `A` or `C` in the requirements model. Reference as `ScoreReq.Asil.QM`, +`ScoreReq.Asil.B`, `ScoreReq.Asil.D`. + +> Note: `dependable_element(integrity_level = ...)` in Bazel accepts `A`/`B`/`C`/`D` for the +> element hierarchy, but requirement records themselves only use `QM`/`B`/`D`. + +### Field inheritance + +``` +Requirement → description (Markup_String), version (Integer), + note (optional String), status (frozen = valid) + RequirementSafety → + safety (Asil) + AssumedSystemReq → + rationale (String) + FeatReq → + derived_from (AssumedSystemReqId[1..*]) + CompReq → + derived_from (CompReqSourceId[1..*]) # FeatReq or AssumedSystemReq +``` + +- `description` is a `Markup_String` — it may contain `:term:` references and embedded + directives (see *Images & diagrams* below). +- `status` is **frozen** to `valid` — do not set it. +- `note` is optional and non-normative. +- The model does **not** enforce a "shall/should" keyword check (unlike some downstream + projects). Still, write requirements with "shall" for clarity. + +### Versioned reference tuples + +`AssumedSystemReqId`, `FeatReqId`, `CompReqSourceId`, `CompReqId` all use the form +`Package.RecordId@version`, e.g. `SampleSEooC.ASR_SAMPLE_001@1`. + +--- + +## Writing Requirements + +Package names are **project-specific** (e.g. `SampleSEooC`, `SampleComponent`). Every file +declares its package and imports `ScoreReq` (plus any package it cross-references). + +### AssumedSystemReq + +```trlc +package SampleSEooC +import ScoreReq + +ScoreReq.AssumedSystemReq ASR_SAMPLE_001 { + description = "The system shall provide safe and reliable numeric value management, compliant with the selected :term:`integrity level`." + safety = ScoreReq.Asil.B + version = 1 + rationale = "System-level requirement for managing numeric values in a safety-critical context" +} +``` + +**Required**: `description`, `safety`, `version`, `rationale`. + +### FeatReq + +```trlc +package SampleSEooC +import ScoreReq + +ScoreReq.FeatReq FEAT_001 { + description = "The :term:`component` shall provide a numeric value management interface that returns a uint8_t value on every read access." + safety = ScoreReq.Asil.B + derived_from = [SampleSEooC.ASR_SAMPLE_001@1] + version = 1 +} +``` + +**Required**: `description`, `safety`, `version`, `derived_from` (≥1 version-pinned `AssumedSystemReq`). + +### CompReq + +`derived_from` uses the versioned tuple syntax `[Package.RecordId@version]` and may reference +**more than one** parent (a `FeatReq` or an `AssumedSystemReq`). + +```trlc +package SampleComponent +import ScoreReq +import SampleSEooC + +ScoreReq.CompReq REQ_COMP_001 { + description = "The numeric value management interface shall provide a read operation that returns a uint8_t value" + safety = ScoreReq.Asil.B + derived_from = [SampleSEooC.FEAT_001@1] + version = 1 +} + +ScoreReq.CompReq REQ_COMP_004 { + description = "The numeric value validator shall accept a numeric value manager instance as its sole constructor argument" + safety = ScoreReq.Asil.B + derived_from = [SampleSEooC.FEAT_003@1, SampleSEooC.FEAT_004@1] + version = 1 +} +``` + +**Required**: `description`, `safety`, `version`. `derived_from` is optional — omit it only for +component-internal requirements with no feature-level parent. + +### Assumptions of Use (AoU) + +`AoU` extends `ControlMeasure` and captures conditions the integrating project must satisfy. +The `assumptions_of_use` rule accepts raw `.trlc` **or** `.rst` files carrying `aou_req` +directives (converted to TRLC automatically). + +--- + +## Bazel Rules + +Load from the aggregator: + +```starlark +load( + "@score_tooling//bazel/rules/rules_score:rules_score.bzl", + "assumed_system_requirements", + "feature_requirements", + "component_requirements", + "assumptions_of_use", +) +``` + +Each requirement rule takes `name`, `srcs` (the `.trlc` files), and `deps` (other requirement +targets whose records are cross-referenced). All three share the same optional attributes: +`spec` (defaults to the `ScoreReq` model — override only for a custom schema), `lobster_config`, +`ref_package`, and `image_srcs`. + +```starlark +# docs/requirements/BUILD + +assumed_system_requirements( + name = "assumed_system_requirements", + srcs = ["assumed_system_requirements.trlc"], + visibility = ["//visibility:public"], +) + +feature_requirements( + name = "feature_requirements", + srcs = ["feature_requirements.trlc"], + deps = [":assumed_system_requirements"], # resolve derived_from refs + visibility = ["//visibility:public"], +) + +component_requirements( + name = "component_requirements", + srcs = ["component_requirements.trlc"], + deps = [ + ":assumed_system_requirements", + ":feature_requirements", + ], + visibility = ["//visibility:public"], +) +``` + +Every requirement target automatically generates a `_test` target that runs +`trlc --verify`. Because these rules emit `TrlcProviderInfo`, downstream targets can list them +directly in `deps` without any intermediate `trlc_requirements` wrapper. + +### Requirement Allocation to Architecture + +- **`CompReq`** → allocated to exactly one component via the `component(requirements = [...])` + attribute. Because the whole file is assigned to one component, split CompReqs into per-component + files. +- **`FeatReq`** → allocated to the SEooC as a whole via `dependable_element(requirements = [...])`. + Traceability to the implementing components runs through the `FeatReq → CompReq → component` chain. + +```starlark +component( + name = "MyComponent", + components = [":MyUnit"], + requirements = [":component_requirements"], + tests = [], +) + +dependable_element( + name = "my_element", + requirements = [":feature_requirements"], # FeatReq targets + # ... +) +``` + +--- + +## Images & Diagrams in Descriptions + +A `description` can embed images and PlantUML so they render in the generated Sphinx docs next +to the requirement text: + +- **Markdown image** `![alt](path)` → converted to `.. image::`. +- **Raw RST directive** (`.. uml::`, `.. image::`, `.. figure::`) → passed through unchanged. + +The referenced file must also be declared via the `image_srcs` attribute (available on all three +requirement rules) and the path in the directive must match the file's package-relative path. +Prefer `.svg` over `.png` for anything checked into git. + +```trlc +ScoreReq.CompReq COMP_003 { + description = '''The `ClientConnection` shall maintain a state machine. + + .. uml:: client_connection_activity_diagram.puml''' + safety = ScoreReq.Asil.B + derived_from = [MySeooc.FEAT_001@1] + version = 1 +} +``` + +--- + +## Validation + +```bash +# Type-check all requirement .trlc files (build) +bazel build //docs/requirements/... + +# Run trlc --verify on every requirement target (test) +bazel test //docs/requirements/... + +# A single target +bazel test //docs/requirements:feature_requirements_test +``` + +`trlc --verify` catches: syntax errors, type errors (wrong value type for a field), missing +mandatory fields (`description`, `safety`, `version`), broken cross-references (a `derived_from` +pointing at a non-existent record), and unknown fields not defined in the model. + +### AI-Powered Quality Check (optional) + +`trlc_requirements_ai_test` evaluates requirement *quality* (clarity, testability, completeness) +with an LLM. It is non-deterministic — tag it `manual` and do **not** run it in CI. + +```starlark +load("@score_tooling//validation/ai_checker:ai_checker.bzl", "trlc_requirements_ai_test") + +trlc_requirements_ai_test( + name = "feature_requirements_ai_check", + reqs = [":feature_requirements"], + score_threshold = "6.0", + tags = ["manual"], +) +``` + +--- + +## Workflow + +1. **Create or modify** `.trlc` files under your `requirements/` (or `docs/requirements/`) directory. +2. **Wire BUILD targets** — add new `.trlc` files to `srcs`; add cross-referenced targets to `deps`. +3. **Validate locally**: `bazel test //.../requirements/...`. +4. **Commit** on a feature branch and open a PR. + +### Updating an existing requirement + +- **Increment `version`** on every content change. +- **Update all downstream `derived_from` references** to the new version (`@1` → `@2`). +- Version pinning forces a conscious review of every child when a parent changes. + +--- + +## References + +- [`score_requirements_model.rsl`](../../../bazel/rules/rules_score/trlc/config/score_requirements_model.rsl) — the `ScoreReq` schema (source of truth) +- [`examples/seooc/`](../../../bazel/rules/rules_score/examples/seooc) — complete working example +- [`docs/user_guide/requirements.rst`](../../../bazel/rules/rules_score/docs/user_guide/requirements.rst) — narrative guide +- [TRLC](https://github.com/bmw-software-engineering/trlc) — language and tooling diff --git a/.github/skills/score-safety-analysis/SKILL.md b/.github/skills/score-safety-analysis/SKILL.md new file mode 100644 index 00000000..c88a19ce --- /dev/null +++ b/.github/skills/score-safety-analysis/SKILL.md @@ -0,0 +1,220 @@ + + +--- +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." +argument-hint: "interface or component name to analyse" +--- + +# S-CORE Safety Analysis — TRLC Workflow + +## When to Use + +- Adding new failure modes to an existing safety analysis +- Creating FTA diagrams for root-cause decomposition +- Defining ControlMeasure / AoU records for identified root causes +- Validating the full traceability chain before a review of a safety analysis + +## Key Files and Locations + +``` +score//dependability/ +├── safety_analysis/ +│ ├── failure_modes.trlc # FailureMode records (one per unique root-cause cluster) +│ ├── control_measures.trlc # ControlMeasure / PreventiveMeasure / AoU records +│ ├── fta_.puml # One FTA diagram per FailureMode +│ └── BUILD # fmea() rule — must list all .puml in fta_files filegroup +├── assumed_system/ +│ └── aous.trlc # AoU records (caller obligations) +└── requirements/ + └── component_requirements.trlc +``` + +The canonical RSL (type definitions) lives in: +`eclipse-score-tooling/bazel/rules/rules_score/trlc/config/score_requirements_model.rsl` + +## Workflow: analyse first, then wire the artifacts + +Safety analysis has **two phases, in order** — the TRLC/FTA files are the *output* of the +analysis, not the analysis itself: + +### Step A — Perform the FMEA (safety reasoning) + +Reason about failures *before* writing records. This is a safety-engineering activity governed by +the S-CORE Safety Analysis in +[`docs/user_guide/dependability_analysis.rst`](../../../bazel/rules/rules_score/docs/user_guide/dependability_analysis.rst) +(*Performing the Analysis*). See **Authoring guidance** below. + +**Collaborate when severity, plausibility, or measure sufficiency is uncertain** — propose the +failure modes / causes / measures you intend to record and confirm the safety judgement rather +than inventing it. + +### Step B — Wire the artifacts (mechanical) + +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. + +## Authoring guidance: performing the FMEA + +**Step 1 — identify failure modes per interface.** Walk the `public_api` **method by method**; +for each, apply the applicable fault models and ask *"can this occur, and would it violate a +safety goal?"* Dismiss low-relevance models with a short rationale. + +| Fault-model category | Example models | `GuideWord` labels | +|----------------------|----------------|--------------------| +| Message (send/receive) | not sent/received, corrupted, lost, unintended (`MF_01_*`) | `LossOfFunction`, `PartialFunction`, `Corrupted`, `UnintendedFunction`, `Wrong` | +| Timing / duration | too late/early, boundary violated (`CO_01_*`) | `TooEarly`, `TooLate`, `DelayedFunction` | +| Execution | wrong result, loss, arbitrary/incomplete (`EX_01_*`) | `Wrong`, `LossOfFunction`, `ExceedingFunction`, `ArbitraryExecution` | + +> **Clustering:** one `FailureMode` per *(interface, guide word)* effect — group methods sharing a +> root cause in the `interface` field; one root cause under two guide words → two records. + +**Step 2 — effect, then causes.** Write `failureeffect` from the **caller/system perspective** in +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. + +**Step 3 — one measure per root cause**, chosen by *when* it acts: + +| 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 | + +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, +diagnostic coverage), and give a `FailureMode` the ASIL of the safety goal it can violate. + +## Step 1 — Write FailureMode Records (`failure_modes.trlc`) + +Package: match the existing package declaration in the folder structure + +```trlc +package +import ScoreReq + +ScoreReq.FailureMode { + guideword = ScoreReq.GuideWord. + description = "statement describing the expected behaviour" + failureeffect = "What goes wrong for the caller / system" + potentialcause = "Root cause(s) from the FMEA" + interface = ".[, .]" + version = 1 + safety = ScoreReq.Asil.B +} +``` + +**GuideWord enum values:** `LossOfFunction`, `PartialFunction`, `Corrupted`, `UnintendedFunction`, `TooEarly`, `TooLate`, `Wrong`, `DelayedFunction`, `ExceedingFunction`, `ArbitraryExecution` + +**Rules:** +- One record per FailureMode, not per interface method. +- Same root cause spanning multiple guide words → separate records (trlc only allows one `guideword` per record). +- `interface` may list multiple methods as a comma-separated string when root cause is shared. + +## Step 2 — Create FTA Diagrams (`fta_.puml`) + +One `.puml` file per `FailureMode` record. The `$TopEvent` alias **must** equal the fully-qualified TRLC record name (`.`). + +```plantuml +@startuml + +!include fta_metamodel.puml + +$TopEvent("", ".") + +$OrGate("OG1", ".") + +$BasicEvent("", ".", "OG1") +$BasicEvent("", ".", "OG1") + +@enduml +``` + +**Procedures reference:** + +| Procedure | Purpose | `connection` points to | +|-----------|---------|------------------------| +| `$TopEvent(name, alias)` | Top failure mode | — (root, no connection) | +| `$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 | +| `$IntermediateEvent(name, alias, connection)` | Intermediate cause | parent gate alias | +| `$TransferInGate(name, alias, connection)` | Link to sub-tree | parent alias | + +**Rules:** +- `$BasicEvent` alias = `.` — 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). +- `$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`) + +For every `$BasicEvent` alias in every FTA, define a matching record: + +```trlc +ScoreReq.ControlMeasure { + safety = ScoreReq.Asil.B + description = "Normative measure text" + version = 1 +} +``` + +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 = ""` field + +**Rule:** `.` in TRLC must match the `$BasicEvent` alias verbatim. + +## Step 4 — Update BUILD + +Add every new `.puml` to the `fta_files` filegroup **and** keep the list alphabetically sorted: + +```python +filegroup( + name = "fta_files", + srcs = [ + "fta_api_called_before_lifecycle_ready.puml", + "fta_client_connection_failed.puml", + # ... one entry per FTA file, alphabetical + ], + visibility = ["//score//dependability:__pkg__"], +) +``` + +## Step 5 — Validate + +**Pass criteria:** zero errors in `failure_modes.trlc`, `control_measures.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 +``` + +## Common Mistakes + +| Mistake | Fix | +|---------|-----| +| `$BasicEvent` alias does not match any TRLC record | Ensure `.` is spelled identically in both places | +| 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 | +| Wrong RSL used for trlc validation | Always pass the tooling RSL as the first directory argument | diff --git a/.github/skills/score-testing/SKILL.md b/.github/skills/score-testing/SKILL.md new file mode 100644 index 00000000..155f2bab --- /dev/null +++ b/.github/skills/score-testing/SKILL.md @@ -0,0 +1,212 @@ + + +--- +name: score-testing +description: "Testing and test-coverage traceability for S-CORE SEooCs using the rules_score Bazel rules. USE FOR: attaching tests to unit/component/dependable_element targets, annotating GoogleTest cases with lobster-tracing and Given-When-Then RecordProperty calls, requirement-to-test traceability, the test_case_coverage.lock.yaml workflow (bazel run .update vs bazel test drift check), maturity-driven enforcement, and running rules_score tests. Use when writing tests, wiring test targets, annotating tests for traceability, or measuring test-case coverage." +argument-hint: "unit/component test or coverage task" +--- + +# S-CORE Testing Skill + +Testing and test-case-coverage traceability for a **Safety Element out of Context (SEooC)** built +with the `rules_score` Bazel rules. Tests are attached to architectural elements, annotated for +requirement traceability, and their coverage is locked and verified automatically at build time. + +> **Source of truth**: the rule macros under `bazel/rules/rules_score/private/` (`unit.bzl`, +> `component.bzl`, `dependable_element.bzl`), the test-case-coverage tooling under +> [`bazel/rules/rules_score/src/test_case_coverage/`](../../../bazel/rules/rules_score/src/test_case_coverage), +> and the runnable examples in +> [`bazel/rules/rules_score/examples/`](../../../bazel/rules/rules_score/examples). +> When source and documentation disagree, the source wins. + +## When to use + +- Attaching tests to `unit`, `component`, or `dependable_element` via the `tests` attribute +- Annotating GoogleTest cases with `lobster-tracing` + Given-When-Then +- Setting up and maintaining `test_case_coverage.lock.yaml` +- Running `rules_score` tests and interpreting coverage-drift failures + +## Not for + +- Requirement records and traceability model → **score-requirements** +- Architecture structure and diagrams → **score-architecture** +- FMEA / safety analysis → **score-safety-analysis** +- End-to-end SEooC assembly / choosing which skill to use → **rules-score** + +--- + +## Where Tests Attach + +Test targets are attached at three architectural levels through the `tests` attribute. Match the +test scope to the level: + +| Level | Attribute | Test focus | +|-------|-----------|------------| +| `unit` | `unit(tests = [...])` | Unit tests — the smallest verifiable element | +| `component` | `component(tests = [...])` | Integration tests across the component's units | +| `dependable_element` | `dependable_element(tests = [...])` | System / integration tests for the whole SEooC | + +`rules_score` does **not** require a separate test-specification document. Test intent is captured +as a **Given-When-Then** description right next to the code, then rendered in the traceability +report together with the test results and coverage. + +--- + +## Annotating Tests (GoogleTest + lobster-tracing) + +Unit tests are written with **GoogleTest** and built with `cc_test`. A test case that covers a +requirement carries `RecordProperty` annotations inside its body: + +```cpp +TEST(MyUnitTest, ConfigureAndGet) { + ::testing::Test::RecordProperty( + "lobster-tracing", "MinimalExample.FEAT_001 MinimalExample.FEAT_002"); + ::testing::Test::RecordProperty("given", "a default-constructed MyUnit instance"); + ::testing::Test::RecordProperty("when", "configure is called with a known key"); + ::testing::Test::RecordProperty("then", "get returns the configured value"); + + MyUnit unit; + unit.configure("mode", "fast"); + EXPECT_EQ(unit.get("mode"), "fast"); +} +``` + +| Property | Required | Description | +|----------|----------|-------------| +| `lobster-tracing` | yes | One or more requirement IDs (`Package.RecordId`), linking the test to `CompReq` records. Multiple IDs are separated by whitespace (the docs also describe comma-separated). | +| `given` | no | Initial state / precondition | +| `when` | no | Action or event under test | +| `then` | no | Expected outcome | + +- A test **without** `lobster-tracing` has no traceability and is excluded from coverage tracking. +- The referenced IDs must resolve to `CompReq` records exposed through the component's + `requirements` targets. + +```starlark +cc_test( + name = "my_unit_test", + srcs = ["test/my_unit_test.cpp"], + deps = [":my_unit_lib", "@googletest//:gtest_main"], +) + +unit( + name = "MyUnit", + unit_design = [":MyUnit_design"], + implementation = [":my_unit_lib"], + tests = [":my_unit_test"], +) +``` + +--- + +## Test-Case Coverage (`test_case_coverage.lock.yaml`) + +Coverage is **declared** through a committed lock file that lists, per requirement, every test +case (uid + Given-When-Then) that covers it. Committing the file is the coverage claim. Link it to +the `component` rule: + +```starlark +component( + name = "my_component", + requirements = [":my_component_requirements"], + components = [":unit_a", ":unit_b"], + test_case_coverage_lock = "test_case_coverage.lock.yaml", +) +``` + +### Lock file format + +```yaml +schema_version: 3 +requirements: + - id: MessagePassing.OsIpcFaultHandling + test_cases: + - uid: "//score/message_passing/ConnectionSuite:OsIpcFaultHandlingTest" + given: a connected client + when: the OS IPC call fails + then: the client receives an error +``` + +- `requirements[].id` — matches the `lobster-tracing` value; extracted from the requirements targets. +- `test_cases[].uid` — `//bazel_package/SuiteName:TestName`, a package-scoped gtest tag. +- `given` / `when` / `then` — the GWT fields from `RecordProperty`; **any** change makes the lock stale. + +### Two workflows keep the lock in sync + +- **`bazel run //:.update`** — reads current test results and **rewrites** + `test_case_coverage.lock.yaml` in the source tree. Review `git diff`, then commit to approve. +- **`bazel test //...`** — a build action recomputes coverage from the same test results and + **compares** it against the committed lock. Any drift (new/removed test, changed GWT text, + version bump) fails the build until the lock is refreshed and re-committed. + +The `.update` target is only generated when `test_case_coverage_lock` is set on the component. + +### Enforcement stringency follows `dependable_element.maturity` + +The coverage check runs inside the enclosing `dependable_element`, and its `maturity` attribute +controls how strict it is: + +| `maturity` | Behavior | +|------------|----------| +| `"development"` | `--allow-check-failures`: lock-drift **and** missing-GWT-annotation errors are downgraded to warnings; the `.lobster` artifact is always produced. | +| `"release"` | A drift or a missing GWT annotation fails the Bazel build action directly. | + +Switch back to `"release"` before certification. + +--- + +## Running Tests + +```bash +# Everything (also runs coverage-drift checks and trlc --verify) +bazel test //... + +# A single test target +bazel test //:my_unit_test + +# Requirement-validation tests only +bazel test //docs/requirements/... + +# Refresh a component's coverage lock after intended test changes +bazel run //:.update +``` + +Useful options: `--test_output=errors` (default), `--test_output=all`, +`--nocache_test_results` (force re-run). + +--- + +## Traceability Flow (how it fits together) + +``` +CompReq (requirements target, .lobster) + ▲ lobster-tracing "Package.CompReq" +GoogleTest case (RecordProperty) + │ subrule_lobster_gtest → gtest.lobster +component(test_case_coverage_lock=…) + │ compute_lock ── compared against committed lock (bazel test) + └─ rendered in the dependable_element traceability report +``` + +Requirement → test coverage is complete when every `CompReq` in the component's `requirements` +appears in the lock file with at least one covering test case. + +--- + +## References + +- [`docs/user_guide/validation.rst`](../../../bazel/rules/rules_score/docs/user_guide/validation.rst) — annotation & coverage guide +- [`docs/tool_reference/test_case_coverage.rst`](../../../bazel/rules/rules_score/docs/tool_reference/test_case_coverage.rst) — lock format, phases, data flow +- [`examples/minimal/`](../../../bazel/rules/rules_score/examples/minimal) — minimal annotated test +- [`examples/seooc/`](../../../bazel/rules/rules_score/examples/seooc) — full SEooC with `test_case_coverage.lock.yaml` diff --git a/bazel/rules/rules_score/docs/user_guide/architectural_design.rst b/bazel/rules/rules_score/docs/user_guide/architectural_design.rst index e1fd2599..9f0799bc 100644 --- a/bazel/rules/rules_score/docs/user_guide/architectural_design.rst +++ b/bazel/rules/rules_score/docs/user_guide/architectural_design.rst @@ -27,16 +27,6 @@ Because these two views are authored independently, they can drift apart. Theref Overview and Hierarchy ------------------------ -- **Static** — the structural organisation: which components and units exist, how they nest, and how they depend on each other. Validated against the Bazel model at build time. -- **Dynamic** — behavioural sequences, state transitions, and activity flows. Documentation only, not validated against Bazel targets. -- **Public API** — the interfaces the SEooC exposes to its environment, linked to safety analysis via ``FailureMode.interface``. -- **Internal API** — interfaces exposed between components inside the SEooC that are not part of the public boundary. - -Static Architecture --------------------- - -The static view describes the **structural organisation** of your software: what components and units exist, how they relate to each other, and which dependencies they carry. It is the primary input for the architecture consistency check. - Software in ``rules_score`` is structured in three levels: :: @@ -52,6 +42,116 @@ Two rules apply: - ``unit`` targets must always be wrapped in a ``component`` — they cannot be placed directly under ``dependable_element``. - ``component`` targets can be nested: a component may contain other components as well as units, allowing arbitrary depth. +Below the different levels there are multiple views which present the architecture from different perspectives: + +- **Static** — the structural organisation: which components and units exist, how they nest, and how they depend on each other. Validated against the Bazel model at build time. +- **Dynamic** — behavioural sequences, state transitions, and activity flows. Documentation only, not validated against Bazel targets. +- **Public API** — the interfaces the SEooC exposes to its environment, linked to safety analysis via ``FailureMode.interface``. +- **Internal API** — interfaces exposed between components inside the SEooC that are not part of the public boundary. + + +Determining Components and Units +-------------------------------- + +The Bazel rules and the consistency check only verify that your declared and +implemented structure *match* — they cannot tell you whether the structure is +*good*. Deciding what becomes a ``component`` and what becomes a ``unit`` is a +design activity. + +Start from the requirements and the public interface, then decompose top-down: +the ``dependable_element`` is fixed by the SEooC boundary (its public API), and +you refine it into components and units until every leaf is small enough to be +implemented and tested by one owner. + +What makes a unit +~~~~~~~~~~~~~~~~~~ + +A **unit** is the smallest architectural element that is *independently +verifiable*. Model something as a unit when it satisfies all of the following: + +- **Single responsibility** — it does one thing; you can state its purpose in a + single sentence without using "and". +- **Independently verifiable** — its behaviour can be fully covered by unit + tests through a narrow interface, without standing up the rest of the SEooC. +- **Cohesive implementation** — its source files (the ``cc_library`` behind + ``unit.implementation``) change together and share the same data. +- **One owner** — a single team/person is responsible for its design and tests. +- **Backed by a unit design** — its internal class structure is documented and + validated against the code via :doc:`unit_design`. + +If a unit's class diagram grows several unrelated clusters of classes, or its +unit tests split into groups that never share fixtures, it is really two units. + +What makes a component +~~~~~~~~~~~~~~~~~~~~~~~~ + +A **component** *groups* units (and possibly sub-components) that collaborate to +deliver a coherent piece of feature behaviour. Introduce a component when: + +- Several units together realise **one feature** or provide **one internal + interface** to the rest of the SEooC. +- The grouping owns behaviour that only emerges from unit *interaction* — + captured by **component-level integration tests** and **component + requirements** (``CompReq``). +- It gives you a stable boundary you can allocate requirements to and reason + about in the safety analysis. + +Nest a component inside another component only when the inner grouping has its +own meaningful interface and requirements; do not nest purely to mirror source +folders. + +Deciding the boundaries +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use these heuristics — most are the classic **high-cohesion / low-coupling** +rules applied to the ``rules_score`` element levels: + +- **Cohesion first** — put things that change together and share data in the + same element; split things that change for different reasons. +- **Minimise the interface** — prefer a decomposition that yields the *fewest, + narrowest* interfaces between elements. A boundary that needs a wide, + chatty interface is usually in the wrong place. +- **Follow the requirement allocation** — a ``CompReq`` is allocated to exactly + one component. If a candidate requirement naturally splits across two groups, + that is a component boundary; if it lands entirely inside one group, keep it + together. +- **Match the failure-containment goal** — a component/unit boundary is also a + boundary for the safety analysis. Draw boundaries so that a failure can be + argued about, and a control measure placed, at a single element (see + :doc:`dependability_analysis`). +- **Keep units testable in isolation** — if you cannot unit-test a candidate + unit without a second unit present, either merge them or introduce an + interface (internal API) so the dependency can be substituted. + +Public vs. internal interfaces +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- An interface is **public API** when it is part of the SEooC's contract with + its environment — it is bound from the ``<>`` element and feeds + ``FailureMode.interface`` traceability. Keep the public API as small as the + requirements allow; every public method is a contract with the user and a safety-analysis + entry point. +- An interface is **internal API** when it exists only *between* components/units + inside the SEooC. Model it inside the owning element's namespace. Promote an + interface from internal to public only when an external requirement forces it. + +Common anti-patterns +~~~~~~~~~~~~~~~~~~~~~~ + +- **Folder-driven decomposition** — creating a component per source directory + instead of per feature/interface. Structure follows responsibility, not layout. +- **God unit** — one unit that accumulates unrelated responsibilities because it + was the first one created. Split as soon as a second responsibility appears. +- **Anaemic component** — a component that only forwards calls and owns no + integration tests or requirements. Either give it a real boundary or flatten it. +- **Leaky public API** — exposing an interface publicly for convenience. It then + drags in unnecessary failure modes and AoUs. + +Static Architecture +-------------------- + +The static view describes the **structural organisation** of your software: what components and units exist, how they relate to each other, and which dependencies they carry. It is the primary input for the architecture consistency check. + PlantUML ~~~~~~~~~ diff --git a/bazel/rules/rules_score/docs/user_guide/dependability_analysis.rst b/bazel/rules/rules_score/docs/user_guide/dependability_analysis.rst index 9f7661fd..00b1b020 100644 --- a/bazel/rules/rules_score/docs/user_guide/dependability_analysis.rst +++ b/bazel/rules/rules_score/docs/user_guide/dependability_analysis.rst @@ -66,6 +66,106 @@ loss, delay, corruption, non-determinism). The ``Guideword`` enum in the The description below covers the FMEA-based **safety** analysis for a software module. +Performing the Analysis +----------------------- + +The Bazel rule and traceability check only verify that the artifacts are +*linked* — 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 governed by the S-CORE +`Safety Analysis process area `_ +and its +`FMEA fault models guideline `_. +Work through it in this order. + +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?"* Record only the plausible, safety-relevant ones — the guideline marks +many models as *low relevance* (e.g. "message received too early") that you can +dismiss with a short rationale. + +The ``Guideword`` enum labels the fault-model category on each ``FailureMode``: + +.. list-table:: + :header-rows: 1 + :widths: 22 33 45 + + * - Fault-model category + - Example fault models + - ``Guideword`` labels + * - **Message** (send/receive) + - not sent / not received, corrupted, lost, unintended (``MF_01_*``) + - ``LossOfFunction``, ``PartialFunction``, ``Corrupted``, + ``UnintendedFunction``, ``Wrong`` + * - **Timing / duration constraint** + - too late / too early, boundary violated (``CO_01_*``) + - ``TooEarly``, ``TooLate``, ``DelayedFunction`` + * - **Execution** + - wrong result, loss of execution, arbitrary/incomplete (``EX_01_*``) + - ``Wrong``, ``LossOfFunction``, ``ExceedingFunction``, ``ArbitraryExecution`` + +**Clustering:** create **one** ``FailureMode`` record per *(interface, guideword)* +effect, not one per method blindly. If the same root cause produces the same +effect across several methods, list them together in the ``interface`` field. A +single root cause that manifests under two guide words needs two records (TRLC +allows one ``guidewords`` classification per record). + +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 (``$BasicEvent``) is an **actionable root cause** you + can place a measure on — not a vague restatement of the failure. + +Step 3 — Choose a countermeasure for every root cause +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Every ``$BasicEvent`` needs exactly one measure record. Pick the type by *when* it +acts: + +.. list-table:: + :header-rows: 1 + :widths: 24 46 30 + + * - Type + - Use when the measure… + - Acts + * - ``PreventiveMeasure`` + - removes the cause so the fault cannot occur. + - before + * - ``ControlMeasure`` + - detects and handles the fault at runtime (plausibility check, monitor). + - during + * - ``Mitigation`` + - reduces severity/probability after the fault has occurred. + - after + * - ``AoU`` (Assumption of Use) + - can only be guaranteed by the **integrator/caller**, not inside the SEooC. + - at integration + +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 +:doc:`assumptions_of_use`). + +**ASIL rationale:** the ``safety`` level on a ``FailureMode`` follows the safety +goal it can violate; a ``ControlMeasure`` that an ASIL argument relies on inherits +that level. Record *why* a measure is sufficient — an AND-gate decomposition or a +diagnostic coverage claim — rather than only *that* it exists. + Bazel Rule ``dependability_analysis`` ---------------------------------------- diff --git a/bazel/rules/rules_score/docs/user_guide/requirements.rst b/bazel/rules/rules_score/docs/user_guide/requirements.rst index 15e2719c..de884aa3 100644 --- a/bazel/rules/rules_score/docs/user_guide/requirements.rst +++ b/bazel/rules/rules_score/docs/user_guide/requirements.rst @@ -62,6 +62,77 @@ It includes also (manual) version pinning (e.g. ``@1``) of requirements, which e that when a parent requirement changes its content (and thus version), all downstream references must be explicitly updated. +Writing Good Requirements +------------------------- + +The rules only check that requirements are *well-formed and traceable* — not that +they are *well-written*. The authoritative writing rules live in the +`Requirements Writing Guidelines `_ +(the same guidelines the AI quality check applies) and the S-CORE +`Requirements Engineering process area `_. + +The guideline document defines: a mandatory **sentence template** for every requirement; a set +of **quality criteria** (unambiguous, verifiable, atomic, consistent, complete, necessary) with +terms and patterns to avoid; a short explanation of the **requirement types** (functional, +interface, non-functional, process) and how each is verified; and a list of **pre-flight checks** +that the AI quality check applies before raising a finding (e.g. not flagging formally defined +domain terms as vague, or intentional design constraints as level mismatches). The essentials are +distilled below. + +Sentence template +~~~~~~~~~~~~~~~~~~ + +Every requirement follows one structure — **subject** *shall* **verb** [*object*] +[*parameter*] [*condition*] — with at least one of object / parameter / condition present: + + The component *shall* detect if a key-value pair got corrupted and set its status to + ``INVALID`` during every restart of the SW platform. + +Quality criteria +~~~~~~~~~~~~~~~~~~ + +- **Unambiguous** — one interpretation. Prefer ``:term:`` glossary nouns over pronouns and + vague words (*fast*, *efficient*, *as appropriate*). +- **Verifiable** — a test or review can objectively pass or fail it. +- **Atomic** — one ``shall``; split "and"/"or" and unbounded lists (*etc.*, *and so on*). +- **Consistent** — no contradiction with other requirements. +- **Complete** — has subject + verb + at least one of object/parameter/condition, and fully + specifies behaviour *at its own level* without pre-empting lower-level detail. +- **Necessary** — traces to a parent or a ``rationale``. + +Use *shall* for obligations; put non-normative notes in the ``note`` field, not +``description``. Keep implementation detail out of system/feature requirements — but an +*intentional* design constraint (mandating a transport, mechanism, or safety property) is a +legitimate requirement, not a level violation. + +Choosing the level +~~~~~~~~~~~~~~~~~~~~ + +Levels differ by **scope and observer**, not wording: + +- **AssumedSystemReq** — the need comes from *outside* the SEooC; describes it as a black box. +- **FeatReq** — spans **several** components on one feature; phrased against the public + interface, solution-neutral. +- **CompReq** — fully implementable and testable **inside one component**. + +If the level is unclear, discuss the boundary with the requirements owner — it drives +allocation and traceability. + +Refining a parent into children +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``derived_from`` is a refinement claim: each child is a narrower, consistent refinement; the +children together must be **sufficient** to satisfy the parent; a child inherits the parent's +``safety`` level unless a lower one is justified. On any content change, bump ``version`` and +re-pin every child (``@1`` → ``@2``). + +.. code-block:: text + + # Weak — unverifiable, multi-concern, leaks implementation + "The manager should quickly handle values and store them efficiently in a hash map." + # Better — atomic, verifiable, follows the template + "The numeric value manager shall return the most recently stored uint8_t value on every read access." + Modeling Requirements --------------------- diff --git a/bazel/rules/rules_score/docs/user_guide/tutorial/architecture.rst b/bazel/rules/rules_score/docs/user_guide/tutorial/architecture.rst index f9c32b33..03c9c313 100644 --- a/bazel/rules/rules_score/docs/user_guide/tutorial/architecture.rst +++ b/bazel/rules/rules_score/docs/user_guide/tutorial/architecture.rst @@ -21,7 +21,7 @@ Once the requirements are in place, add a static architecture diagram that names - its components, and - its units -During the build process every plantuml diagram will be parsed and checked for consistency +This PlantUML Diagram represents the targetted architecture. During the build process every PlantUML diagram will be parsed and checked for consistency with the implemented architecture / design. To enable this certain rules and guidelines have to be followed while implementing the architecture diagram. diff --git a/bazel/rules/rules_score/docs/user_guide/tutorial/index.rst b/bazel/rules/rules_score/docs/user_guide/tutorial/index.rst index 506c050e..c50dd10a 100644 --- a/bazel/rules/rules_score/docs/user_guide/tutorial/index.rst +++ b/bazel/rules/rules_score/docs/user_guide/tutorial/index.rst @@ -26,6 +26,41 @@ from the standalone module at By the end you will have a fully validated SEooC with requirements, a static architecture diagram, a unit design, and a passing build. +Workflow +-------- + +The tutorial follows the S-CORE development flow top-down: specify *what* the +element must do, design *how* it is structured, implement and verify it, and let +the build check that every artefact stays consistent. + +:: + + Step 1 Step 2 Step 3 Step 4 Step 5 + Requirements → SW Architectural → Unit Design → Validation → Build + Design + ─────────── ─────────────── ─────────── ────────── ───────── + AssumedSystemReq static diagram class/sequence unit & bazel build + FeatReq (components + units) diagram per component (runs every + CompReq → Bazel targets unit → code tests + consistency + (.trlc records) modelled after it it validates lobster- check) + against tracing + +Each step builds on the previous one, and each has an automatic check: + +1. **Requirements** — write ``AssumedSystemReq`` / ``FeatReq`` / ``CompReq`` TRLC + records and wire their Bazel targets. Traceability is type-checked by + ``trlc --verify``. → :doc:`requirements` +2. **SW Architectural Design** — draw the static PlantUML diagram naming every + component and unit, then model it 1:1 as ``dependable_element`` / ``component`` + / ``unit`` targets. The diagram is the design; the Bazel model follows it, and + the architecture-consistency check enforces the match. → :doc:`architecture` +3. **Unit Design** — add a class/sequence diagram for each unit; it is validated + against the real C++ implementation. → :doc:`unit_design` +4. **Validation** — attach unit/component/system tests and annotate them with + ``lobster-tracing`` to link test cases back to requirements. → :doc:`validation` +5. **Build** — run ``bazel build //:my_element`` to execute all checks at once and + assemble the documentation. → :doc:`build` + .. toctree:: :maxdepth: 1 diff --git a/bazel/rules/rules_score/docs/user_guide/unit_design.rst b/bazel/rules/rules_score/docs/user_guide/unit_design.rst index eb5b7664..ddb45746 100644 --- a/bazel/rules/rules_score/docs/user_guide/unit_design.rst +++ b/bazel/rules/rules_score/docs/user_guide/unit_design.rst @@ -25,6 +25,32 @@ A ``unit_design`` target is referenced by a ``unit`` target (see :doc:`architectural_design` — *Implementation Architecture in Bazel*) to attach code-level design artefacts to the unit. +Designing the internals of a unit +---------------------------------- + +Before drawing the class diagram, decide *what belongs inside the unit*. Follow +the unit criteria in :doc:`architectural_design` (*Determining Components and +Units*). At the code level, aim for: + +- **One cohesive responsibility per class** — the class diagram should read as a + small set of classes that share data and collaborate for a single purpose. If + two clusters of classes never reference each other, the unit is probably two + units. +- **A narrow, intentional interface** — expose only the methods the unit's + requirements and the internal/public API demand. Every public method becomes a + test obligation and, for public interfaces, a safety-analysis entry point. +- **Explicit ownership of resources and lifetime** — model how objects are + constructed and owned (e.g. ``unique_ptr`` composition, as ``Bar`` owns + ``Foo`` below). Unclear ownership is a frequent source of failure modes. +- **Design is the contract** — the diagram is validated against the code: + implementation-only members are allowed, but design-only members that do not + exist in the code fail the build. Model what must be reviewed and traced; leave + purely incidental helpers out. + +Keep the unit design focused on structure that a reviewer needs to reason about +the unit's correctness and testability — not an exhaustive dump of every private +helper. + ``unit_design`` — Code-Level Design Diagrams ----------------------------------------------- From f20555056823d6f3a4396ddf7c1eee91fe562da8 Mon Sep 17 00:00:00 2001 From: Jochen Hoenle Date: Wed, 29 Jul 2026 11:54:13 +0200 Subject: [PATCH 2/2] [rules scure] enable synch skill mechanism --- .github/skills/BUILD | 26 +++ BUILD | 4 + bazel/rules/rules_score/docs/index.rst | 1 + bazel/rules/rules_score/docs/skills_setup.rst | 29 +++ skills_sync/BUILD | 16 ++ skills_sync/sync_skills.bzl | 71 ++++++++ skills_sync/sync_skills.sh | 172 ++++++++++++++++++ 7 files changed, 319 insertions(+) create mode 100644 .github/skills/BUILD create mode 100644 bazel/rules/rules_score/docs/skills_setup.rst create mode 100644 skills_sync/BUILD create mode 100644 skills_sync/sync_skills.bzl create mode 100755 skills_sync/sync_skills.sh diff --git a/.github/skills/BUILD b/.github/skills/BUILD new file mode 100644 index 00000000..143109d6 --- /dev/null +++ b/.github/skills/BUILD @@ -0,0 +1,26 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +package(default_visibility = ["//visibility:public"]) + +# Only directories following the "score-*" naming convention are considered +# skills that are shared with downstream repositories via //:sync_skills. +# Skills that do not follow this convention (e.g. "rules-score") are kept +# local to this repository and are not distributed. +filegroup( + name = "skills", + srcs = glob( + ["score-*/**"], + allow_empty = True, + ), +) diff --git a/BUILD b/BUILD index 51a6cc44..1d84c2c3 100644 --- a/BUILD +++ b/BUILD @@ -38,8 +38,12 @@ copyright_checker( "plantuml", "validation", "third_party/lint", + "skills_sync", # Add other directories/files you want to check + # Note: .github/skills is intentionally excluded; those SKILL.md / + # README.md files are distributed verbatim to downstream repos via + # //:sync_skills and are not subject to this repo's copyright checker. ], config = "//cr_checker/resources:config", exclusion = "//cr_checker/resources:exclusion", diff --git a/bazel/rules/rules_score/docs/index.rst b/bazel/rules/rules_score/docs/index.rst index 4135f7fa..a5e6de94 100644 --- a/bazel/rules/rules_score/docs/index.rst +++ b/bazel/rules/rules_score/docs/index.rst @@ -30,6 +30,7 @@ safety analysis to the top-level SEooC assembly. :maxdepth: 2 :caption: Usage + skills_setup user_guide/index rule_reference diff --git a/bazel/rules/rules_score/docs/skills_setup.rst b/bazel/rules/rules_score/docs/skills_setup.rst new file mode 100644 index 00000000..a6772f3b --- /dev/null +++ b/bazel/rules/rules_score/docs/skills_setup.rst @@ -0,0 +1,29 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + +Skills Setup +============ + +``score_tooling`` ships a set of shared Copilot "skills" (``score-*`` under +``.github/skills``) that document how to use ``rules_score`` (architecture, +requirements, safety analysis, testing). Downstream repositories can pull +these into their own ``.github/skills`` directory so the same guidance is +available to Copilot locally. + +- ``bazel run //:sync_skills`` — copies score_tooling's current ``score-*`` + skills into the local ``.github/skills`` directory, overwriting outdated + copies and removing skills that score_tooling no longer ships. +- ``bazel test //:sync_skills.check`` — fails if the committed skills are + missing, outdated, or stale relative to the ``score_tooling`` version in + use. Wire this into CI so upstream skill updates are caught automatically. diff --git a/skills_sync/BUILD b/skills_sync/BUILD new file mode 100644 index 00000000..31ad48c4 --- /dev/null +++ b/skills_sync/BUILD @@ -0,0 +1,16 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +package(default_visibility = ["//visibility:public"]) + +exports_files(["sync_skills.sh"]) diff --git a/skills_sync/sync_skills.bzl b/skills_sync/sync_skills.bzl new file mode 100644 index 00000000..e01b1f6f --- /dev/null +++ b/skills_sync/sync_skills.bzl @@ -0,0 +1,71 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +"""Macro that lets downstream repositories pull score_tooling's shared skills.""" + +load("@rules_shell//shell:sh_binary.bzl", "sh_binary") +load("@rules_shell//shell:sh_test.bzl", "sh_test") + +# Files shipped by score_tooling under .github/skills. Only directories +# following the "score-*" naming convention are distributed to downstream +# repositories. +_TOOLING_SKILLS = "@score_tooling//.github/skills:skills" + +def sync_skills(name = "sync_skills"): + """Registers targets to pull and verify score_tooling's shared skills. + + Adds two targets to the calling package (expected to be the repository + root, next to .github): + + - ``: a runnable target (`bazel run //:sync_skills`) that copies + score_tooling's "score-*" skill directories into the local + `.github/skills` directory, overwriting outdated copies and removing + skills that score_tooling no longer ships. + - `.check`: a `bazel test` target that fails when the committed + `.github/skills/score-*` directories are missing, out of date, or + stale relative to the version of score_tooling currently in use. Wire + this target into CI so upstream skill updates are caught automatically. + + Note: a single target cannot serve both purposes, because `bazel run` on + an `sh_test` target still executes through Bazel's test-setup.sh wrapper, + which sets $TEST_TMPDIR the same as a real `bazel test` invocation - there + is no reliable way to distinguish "run" from "test" from inside the script. + + Args: + name: Name of the runnable sync target. Defaults to "sync_skills". + """ + repo_skill_files = native.glob( + [".github/skills/score-*/**"], + allow_empty = True, + ) + + sh_binary( + name = name, + srcs = ["@score_tooling//skills_sync:sync_skills.sh"], + args = [ + "sync", + "$(locations {})".format(_TOOLING_SKILLS), + ], + data = [_TOOLING_SKILLS], + ) + + sh_test( + name = name + ".check", + srcs = ["@score_tooling//skills_sync:sync_skills.sh"], + args = [ + "check", + "$(locations {})".format(_TOOLING_SKILLS), + "--", + ] + repo_skill_files, + data = [_TOOLING_SKILLS] + repo_skill_files, + ) diff --git a/skills_sync/sync_skills.sh b/skills_sync/sync_skills.sh new file mode 100755 index 00000000..ae63cb67 --- /dev/null +++ b/skills_sync/sync_skills.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash + +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +# +# Syncs (or checks the sync status of) the "score-*" skill directories that +# score_tooling ships under .github/skills into a downstream repository's own +# .github/skills directory. +# +# Usage: +# sync_skills.sh sync +# sync_skills.sh check -- +# +# "tooling-skill-files" are the runfiles paths of the files contained in +# @score_tooling//.github/skills:skills (i.e. the upstream, canonical copies). +# "repo-skill-files" (check mode only) are the paths, relative to the +# downstream repository root, of the files currently committed under +# .github/skills/score-*/** in that repository. + +set -euo pipefail + +SKILL_MARKER=".github/skills" + +die() { + echo "error: $*" >&2 + exit 2 +} + +# Given an absolute/runfiles path to a file inside a "*/.github/skills/..." +# tree, prints the path relative to (and including) the skill directory name, +# e.g. ".../external/score_tooling+/.github/skills/score-testing/SKILL.md" +# becomes "score-testing/SKILL.md". +relative_skill_path() { + local f="$1" + case "$f" in + *"${SKILL_MARKER}/"*) + echo "${f#*${SKILL_MARKER}/}" + ;; + *) + die "path '$f' does not contain '${SKILL_MARKER}/'" + ;; + esac +} + +cmd_sync() { + local dest_root="${BUILD_WORKSPACE_DIRECTORY:-}" + [ -n "$dest_root" ] || die "must be run via 'bazel run', BUILD_WORKSPACE_DIRECTORY is not set" + dest_root="${dest_root}/.github/skills" + mkdir -p "$dest_root" + + declare -A upstream_dirs=() + + local f rel dir_name dest + for f in "$@"; do + rel="$(relative_skill_path "$f")" + dir_name="${rel%%/*}" + upstream_dirs["$dir_name"]=1 + + dest="${dest_root}/${rel}" + mkdir -p "$(dirname "$dest")" + cp -f "$f" "$dest" + done + + # Remove skill directories that score_tooling no longer ships, so stale + # skills do not linger after an upstream removal/rename. + local d name + for d in "$dest_root"/score-*; do + [ -d "$d" ] || continue + name="$(basename "$d")" + if [ -z "${upstream_dirs[$name]:-}" ]; then + echo "Removing stale score_tooling skill: ${name}" + rm -rf "$d" + fi + done + + echo "Synced score_tooling skills: ${!upstream_dirs[*]}" +} + +cmd_check() { + local tooling_files=() + local repo_files=() + local seen_separator=0 + + local a + for a in "$@"; do + if [ "$a" = "--" ]; then + seen_separator=1 + continue + fi + if [ "$seen_separator" -eq 0 ]; then + tooling_files+=("$a") + else + repo_files+=("$a") + fi + done + + declare -A upstream_map=() + local f rel + for f in "${tooling_files[@]}"; do + rel="$(relative_skill_path "$f")" + upstream_map["$rel"]="$f" + done + + declare -A repo_map=() + for f in "${repo_files[@]}"; do + case "$f" in + "${SKILL_MARKER}/"score-*) + rel="${f#${SKILL_MARKER}/}" + repo_map["$rel"]="$f" + ;; + esac + done + + local status=0 + local up cf + for rel in "${!upstream_map[@]}"; do + up="${upstream_map[$rel]}" + cf="${repo_map[$rel]:-}" + if [ -z "$cf" ]; then + echo "MISSING: ${SKILL_MARKER}/${rel}" + status=1 + elif ! diff -q "$up" "$cf" >/dev/null 2>&1; then + echo "OUT OF DATE: ${SKILL_MARKER}/${rel}" + status=1 + fi + unset "repo_map[$rel]" + done + + for rel in "${!repo_map[@]}"; do + echo "STALE (no longer provided by score_tooling): ${SKILL_MARKER}/${rel}" + status=1 + done + + if [ "$status" -ne 0 ]; then + echo "" + echo "score_tooling skills are out of sync. Run: bazel run //:sync_skills" + exit 1 + fi + + echo "score_tooling skills are up to date." +} + +mode="${1:-}" +[ -n "$mode" ] || die "usage: sync_skills.sh ..." +shift + +# Note: $(locations label) in the "args" attribute expands to one argv entry +# per file (not a single space-joined string), so remaining args can be used +# as-is (no manual blob-splitting needed). +case "$mode" in + sync) + cmd_sync "$@" + ;; + check) + # Remaining args are the tooling files, a "--" separator, then the + # repo's own committed files (from a plain glob(), not location-expanded). + cmd_check "$@" + ;; + *) + die "unknown mode '$mode' (expected 'sync' or 'check')" + ;; +esac