diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst
index 8f78a58cf..5d35f18fb 100644
--- a/docs/how-to/index.rst
+++ b/docs/how-to/index.rst
@@ -31,5 +31,6 @@ Here you find practical guides on how to use docs-as-code.
dashboards_and_quality_gates
source_to_doc_links
test_to_doc_links
+ perform_tool_verification
generated_docs
add_extensions
diff --git a/docs/how-to/perform_tool_verification.md b/docs/how-to/perform_tool_verification.md
new file mode 100644
index 000000000..dd476f553
--- /dev/null
+++ b/docs/how-to/perform_tool_verification.md
@@ -0,0 +1,1239 @@
+
+
+# How to Apply the SCORE Tool Management Process
+
+This guide explains how to execute the
+[Eclipse SCORE Tool Management Process](https://eclipse-score.github.io/process_description/main/process_areas/tool_management/tool_management_workflow.html)
+using SCORE Docs-as-Code.
+
+The goal is **not** to reproduce ISO 26262 or define a separate qualification
+process. SCORE already defines the process. This guide explains how to represent
+and execute that process with the available S-CORE Docs-as-Code model.
+
+```{note}
+This guide focuses on the safety-related evaluation and qualification aspects
+of SCORE Tool Management. Security evaluation is outside this guide. Use the
+[SCORE Security Analysis Guideline](https://eclipse-score.github.io/process_description/main/process_areas/security_analysis/guidance/security_analysis_guideline.html)
+and applicable work products to identify relevant assets, threats, and impacts.
+For example, data integrity may matter for a tool use case while data
+availability does not; reflect that result in the TVR.
+```
+
+---
+
+## Mental model
+
+The important distinction is between:
+
+- **Tool Verification Report (TVR, `doc_tool`)** — records the SCORE
+ tool-management state and the overall evaluation result.
+- **Tool requirements (`tool_req`)** — capabilities or behaviours provided by
+ the tool, independent of whether a particular project use case uses them.
+- **Tool use cases (`tool_usecase`)** — the usage context in which the project
+ relies on the tool.
+- **Potential tool malfunctions (`potential_tool_malfunction`)** — ways in which
+ the expected tool behaviour can fail.
+- **Safety measures** — measures active during intended tool usage that prevent
+ or detect a potential malfunction.
+- **Testcases (`testcase`)** — verification evidence for requirements. For tool
+ qualification, these verify the relevant `tool_req` needs.
+
+```{mermaid}
+flowchart LR
+ UC["tool_usecase
Tool Use Case"]
+ PM["potential_tool_malfunction
Potential Tool Malfunction"]
+
+ UP["upstream requirements
(stkh_req, gd_req, feat_req, comp_req)"]
+ TR["tool_req
Tool Requirement"]
+ TC["testcase
Qualification Evidence"]
+
+ PM -->|"nested under
(parent_needs)"| UC
+ PM -->|"violates"| TR
+
+ TR -->|"satisfies
(optional)"| UP
+ TC -->|"fully_verifies /
partially_verifies"| TR
+
+ style UC fill:#E1D5E7
+ style PM fill:#F8CECC
+ style UP fill:#DAE8FC
+ style TR fill:#F5F5F5
+```
+
+Two analysis perspectives answer different questions:
+
+| Perspective | Question |
+|---|---|
+| **Evaluation / classification** | How can our intended use of the tool fail, and would the intended usage detect or prevent that failure? |
+| **Qualification** | Do we have sufficient evidence that the specific tool version satisfies the tool requirements we rely on? |
+
+Qualification evidence and detection sufficiency are separate decisions; see
+[Qualification is not a detection measure](#qualification-is-not-a-detection-measure).
+
+---
+
+## External vs. self-developed tools
+
+The evaluation is based on **how the project uses the tool**, not on who
+developed it.
+
+Use cases, potential malfunctions, safety impact, and detection are therefore
+evaluated the same way for external and self-developed tools.
+
+The differences matter mainly when defining requirements and qualification
+evidence.
+
+| Topic | External tool | Self-developed tool |
+|---|---|---|
+| Version | Pin the exact external version and relevant configuration | Identify the exact internal release/version |
+| `tool_req` | Select the tool capabilities relevant to the evaluated use cases | Reuse existing tool requirements where suitable |
+| Implementation | Usually treated largely as a black box | Architecture and implementation may be available |
+| Existing tests | Vendor/upstream tests can be supporting information | Existing development tests may already provide qualification evidence |
+| Qualification | Usually validate our `tool_req` against the configured external tool | Reuse suitable requirements-based tests where possible |
+| Corrective action | Upgrade, configure, constrain usage, add detection, or replace | The tool implementation itself can also be changed |
+
+Do not duplicate requirements or tests solely because a self-developed tool is
+now managed through the SCORE Tool Management process.
+
+---
+
+## SCORE workflow at a glance
+
+```{mermaid}
+flowchart LR
+ A["Create Tool Verification Report"] --> B["status = draft"]
+ B --> C["Evaluate Tool"]
+ C --> D["status = evaluated"]
+ D --> E{"Qualification required?"}
+
+ E -->|"No"| H["Review / approve TVR"]
+ E -->|"Yes"| F["Qualify Tool"]
+ F --> G["status = qualified"]
+ G --> H
+
+ H --> I["status = released"]
+
+ H -. unsuccessful .-> J["status = rejected"]
+```
+
+The remainder of this guide follows these steps.
+
+---
+
+## Step 1 — Create the Tool Verification Report
+
+### What to model
+
+The SCORE Tool Verification Report (TVR) is represented by `doc_tool`.
+
+The TVR uses the following relevant attributes:
+
+- `status`: `draft | evaluated | qualified | released | rejected`
+- `security_affected`: `YES | NO`
+- `tool_version`: optional
+
+The generated TVR report additionally shows:
+
+- `safety_affected`, derived from the owned malfunctions
+- SCORE Tool Confidence Level (SCORE TCL), derived from the owned malfunctions
+
+The report author does not provide these summary values.
+
+Record the tool's scope and purpose, relevant configuration, environment, constraints,
+inputs, outputs, and available documentation when creating the TVR. Inputs and outputs
+define the usage boundary and are needed to identify potential malfunctions.
+
+The introduction should make the purpose and intended use visible before the
+detailed evaluation. Model each intended use as a `tool_usecase`; the generated
+report places an overview of those use cases before the evaluation result.
+
+```{mermaid}
+flowchart LR
+ P["Tool purpose and scope"] --> U["Intended use cases
(tool_usecase)"]
+ U --> M["Potential malfunctions"]
+ M --> Q["Qualification of relevant
(tool_req) capabilities"]
+```
+
+Example structure:
+
+````markdown
+```{doc_tool} S-CORE Docs-as-Code Tool Verification Report
+:id: doc_tool__s_core_docs_as_code
+:status: draft
+:security_affected: NO
+:tool_version:
+
+Describe the tool, its purpose and intended use, relevant configuration,
+environment, constraints, inputs, outputs, and available documentation.
+```
+````
+
+```{note}
+The report author provides the tool identity, status, and security relevance.
+The safety summary and SCORE TCL are derived from the owned malfunction graph.
+```
+
+### External tool
+
+Identify the exact version and configuration that is actually used.
+
+Evaluate the exact tool instance used by the project, including its version,
+plugins, configuration, and usage restrictions.
+
+### Self-developed tool
+
+Reference the project's normal version, release, or source-revision
+identification. For example, a Git commit can identify the evaluated tool when
+the relevant source and build context are clear. The TVR must make clear which
+specific tool version or revision the evaluation applies to.
+
+```{note}
+The current SCORE tool-management model assumes one active TVR per tool. It
+does not currently support maintaining multiple parallel TVRs for different
+versions of the same tool, nor does it scope qualification testcases to a
+particular TVR/version. This limitation must be addressed before that usage
+pattern is introduced.
+```
+
+---
+
+## Step 2 — Define how the project uses the tool
+
+SCORE evaluates the tool in the context of its **use cases**.
+
+A `tool_usecase` captures:
+
+> **What are we relying on this tool to do in our development process?**
+
+Represent a `tool_usecase` with the following relationships:
+
+```yaml
+tool_usecase:
+ mandatory_links:
+ belongs_to: doc_tool
+```
+
+The normal relationship is therefore:
+
+```{mermaid}
+flowchart LR
+ UP["upstream requirements
(stkh_req, gd_req, comp_req, feat_req)"]
+ DT["doc_tool"]
+ UC["tool_usecase"]
+ TR["tool_req"]
+
+ UC -->|"belongs_to
(mandatory)"| DT
+ TR -->|"satisfies
(optional)"| UP
+
+ style UP fill:#DAE8FC
+ style UC fill:#E1D5E7
+ style TR fill:#F5F5F5
+```
+
+Tool requirements may satisfy upstream requirements, including `stkh_req`,
+`gd_req`, `feat_req`, and `comp_req`. The relevant upstream requirements should
+be linked through `tool_req.satisfies` when relating a tool capability to an
+upstream need.
+
+### How to model `tool_usecase`
+
+In the structured verification workflow, an `evaluated`, `qualified`, or
+`released` TVR owns one or more `tool_usecase` records. A use case records the
+context in which the project relies on the tool. The use case itself has no
+direct requirement links; the relevant `tool_req` needs are associated through
+the nested malfunctions, and upstream requirements are linked from those
+`tool_req` needs. Existing TVRs without structured use cases remain accepted
+for compatibility, but the workflow checker does not derive summary values for
+them.
+
+For example:
+
+- upstream requirement (e.g. `stkh_req`, `gd_req`, `feat_req`, `comp_req`): the
+ system-development process must provide valid requirements traceability,
+- tool use case: validate requirement traceability during documentation builds,
+- tool requirement: unresolved requirement links must be reported.
+
+If an existing requirement already expresses the usage context exactly, avoid
+inventing additional behaviour in the use case. Keep it as a thin
+grouping/context element. The relevant `tool_req` needs remain linked through
+the nested malfunctions.
+
+```{important}
+Write each `potential_tool_malfunction` nested inside its `tool_usecase`. The
+nested structure creates the `parent_needs` relationship automatically; do not
+add a separate `parent_needs` option. Therefore, once you model malfunctions,
+place them under a `tool_usecase` even if that use case merely provides context
+for an already well-scoped requirement.
+```
+
+### Tool model example
+
+````markdown
+```{tool_usecase} Validate requirement traceability during documentation builds
+:id: tool_usecase__docs_as_code__traceability
+:belongs_to: doc_tool__s_core_docs_as_code
+
+The project relies on the documentation tooling to identify invalid or
+unresolved requirement links before documentation is accepted.
+```
+````
+
+A good use case describes project usage, not an implementation detail.
+
+**Avoid:**
+
+```text
+Call Python function resolve_links().
+```
+
+**Prefer:**
+
+```text
+Validate requirement links during the documentation build.
+```
+
+### External tool
+
+Define the `tool_req` needs from the tool capability being evaluated, not by
+importing the vendor's complete specification.
+
+Example:
+
+```text
+Tool use case:
+Validate requirement traceability during documentation builds.
+
+Tool requirement:
+The tool shall report unresolved requirement links.
+```
+
+### Self-developed tool
+
+Prefer reusing existing `tool_req` needs in the nested malfunctions.
+
+Do not create separate "qualification requirements" when the normal tool
+requirements already describe the capability relevant to the project use case.
+
+---
+
+## Step 3 — Identify potential tool malfunctions
+
+For every tool use case, identify relevant ways in which the expected tool
+behaviour could fail.
+
+Represent a `potential_tool_malfunction` with the following options and links:
+
+```yaml
+potential_tool_malfunction:
+ mandatory_options:
+ safety_affected: "^(YES|NO)$"
+ optional_options:
+ detection_sufficient: "^(YES|NO)$"
+ safety_measures: ^.+$
+ mandatory_links:
+ # Established by nesting the malfunction inside its tool use case.
+ parent_needs: tool_usecase
+ violates: tool_req
+```
+
+The relationship is:
+
+```{mermaid}
+flowchart LR
+ UC["tool_usecase"]
+ PM["potential_tool_malfunction"]
+ UP["upstream requirement"]
+ TR["tool_req"]
+
+ PM -->|"nested under
(parent_needs)"| UC
+ PM -->|"violates"| TR
+ TR -->|"satisfies"| UP
+
+ style UC fill:#E1D5E7
+ style PM fill:#F8CECC
+ style UP fill:#DAE8FC
+ style TR fill:#F5F5F5
+```
+
+### What is a useful malfunction?
+
+Describe **incorrect behaviour relevant to the use case**.
+
+**Too generic:**
+
+```text
+The tool crashes.
+The tool has a bug.
+The tool produces a wrong result.
+```
+
+**Better:**
+
+```text
+An unresolved requirement link is accepted as valid.
+A valid requirement is omitted from generated documentation.
+Generated source code does not represent the configured input model correctly.
+```
+
+A useful question is:
+
+> **What could the tool do incorrectly, or fail to do, such that one of the
+> requirements we rely on is violated?**
+
+### `violates`
+
+Use `violates` for requirements whose expected behaviour is directly broken by
+the malfunction.
+
+Typical targets are:
+
+- `tool_req`
+
+### Tool model example
+
+`````markdown
+::::{tool_usecase} Validate requirement traceability during documentation builds
+:id: tool_usecase__docs_as_code__traceability
+:belongs_to: doc_tool__s_core_docs_as_code
+
+The project relies on the documentation tool to detect invalid traceability.
+
+:::{potential_tool_malfunction} Unresolved requirement link is accepted as valid
+:id: potential_tool_malfunction__docs_as_code__unresolved_link_accepted
+:violates:
+ tool_req__docs_as_code__unresolved_links
+:safety_affected: YES
+:detection_sufficient: NO
+
+An unresolved requirement link is accepted as valid and the documentation build
+does not report the problem.
+:::
+::::
+`````
+
+There is no conceptual difference between external and self-developed tools in
+this step. Malfunctions are derived from the intended usage and the tool
+capabilities relevant to that usage.
+
+`detection_sufficient` is only required in case of `safety_affected: YES`. A
+safety-relevant malfunction with `detection_sufficient: YES` must also document a
+non-empty `safety_measures` value. The malfunction body remains the place for the
+human-readable reasoning that explains why the measure is sufficient or insufficient.
+
+The same `tool_req` may be relevant to multiple use cases;
+evaluate its potential malfunctions separately for each usage context.
+
+---
+
+## Step 4 — Evaluate safety impact and detection
+
+For each `potential_tool_malfunction`, answer two questions.
+
+### Does the malfunction affect safety?
+
+Use:
+
+```text
+:safety_affected: YES
+```
+
+if the malfunction can:
+
+- introduce an error into safety-related work, or
+- prevent an existing error from being detected.
+
+Use:
+
+```text
+:safety_affected: NO
+```
+
+if the malfunction has no relevant safety impact in this usage context.
+
+This is a property of the **usage context and malfunction**, not a generic label
+for the tool.
+
+The same tool can therefore have safety-relevant and non-safety-relevant use
+cases.
+
+---
+
+### Is detection sufficient?
+
+`detection_sufficient` asks:
+
+> **If this malfunction occurs during the intended use of the tool, do the
+> measures that are part of that intended usage prevent or detect it with
+> sufficient confidence?**
+
+These measures can be:
+
+- internal to the tool,
+- provided by another tool,
+- part of the surrounding workflow or process.
+
+Examples include:
+
+- generated source is compiled before integration,
+- an independent consistency checker validates generated output,
+- a review compares the generated artifact against its source,
+- the tool performs an internal integrity check and rejects inconsistent input,
+- a downstream tool independently validates critical information.
+
+Document the relevant measure using `safety_measures`.
+
+`````markdown
+::::{tool_usecase} Generate production source from the approved model
+:id: tool_usecase__generator__generate_source
+:belongs_to: doc_tool__s_core_docs_as_code
+
+:::{potential_tool_malfunction} Generated source contains invalid syntax
+:id: potential_tool_malfunction__generator__invalid_syntax
+:violates: tool_req__generator__valid_source
+:safety_affected: YES
+:detection_sufficient: YES
+:safety_measures: Generated source is compiled before it can be integrated.
+
+The generator may emit syntactically invalid source code. The mandatory
+downstream compilation detects this malfunction before the output can be used.
+:::
+::::
+`````
+
+The important point is that the measure is part of the **defined intended
+usage**.
+
+It does not matter whether the measure existed before the TVR was written or
+was introduced as a result of the evaluation. If it becomes a mandatory part of
+the intended usage, evaluate the malfunction with that usage concept.
+
+---
+
+## Step 5 — Determine classification and qualification need
+
+For the SCORE safety evaluation, the simplified decision is:
+
+```{mermaid}
+flowchart TD
+ A{"safety_affected?"}
+ A -->|"NO"| B["HIGH confidence
No qualification required"]
+ A -->|"YES"| C{"detection_sufficient?"}
+ C -->|"YES"| B
+ C -->|"NO"| D["LOW confidence
Qualification required"]
+```
+
+| `safety_affected` | `detection_sufficient` | TVR SCORE TCL | Qualification |
+|---|---|---|---|
+| `NO` | not relevant | `HIGH` | not required |
+| `YES` | `YES` | `HIGH` | not required |
+| `YES` | `NO` | `LOW` | required |
+
+The workflow automatically calculates the TVR summary from the owned
+malfunction graph:
+
+1. It follows `doc_tool` -> `tool_usecase` through the mandatory `belongs_to`
+ link.
+2. It follows each use case -> nested `potential_tool_malfunction` needs
+ through the generated `parent_needs` relationship.
+3. It reports safety affected as `YES` if any owned malfunction is safety
+ affected; otherwise it reports `NO`.
+4. It reports SCORE TCL as `LOW` if any owned malfunction is safety affected
+ and has `detection_sufficient: NO`; otherwise it reports `HIGH`.
+
+The generated report exposes the classification result as `HIGH` or `LOW`. In
+this model, `HIGH` means no qualification is required and `LOW` means
+qualification is required. A `LOW` result identifies the evidence that must be
+provided; it does not mean that qualification has already happened. The
+evaluation result and `detection_sufficient` remain unchanged by qualification.
+
+After the evaluation is complete, the generated report exposes the derived
+summary. No manual `safety_affected` or `tcl` values are required on the
+`doc_tool`.
+
+```text
+:status: evaluated
+SCORE TCL: HIGH
+Qualification required: NO
+```
+
+or:
+
+```text
+:status: evaluated
+SCORE TCL: LOW
+Qualification required: YES
+```
+
+For a `LOW` result, the generated qualification view identifies the required
+tool requirements and their current evidence status.
+
+### Multiple use cases and malfunctions
+
+The overall TVR result must reflect the relevant worst case.
+
+If any safety-relevant malfunction has insufficient detection, the tool cannot
+be treated as `HIGH` merely because the other use cases are well protected.
+
+---
+
+## Step 5a — Improve the usage concept instead of qualifying
+
+A `LOW` evaluation does not mean qualification is the only possible response.
+
+You may change the intended usage by adding an appropriate detection or
+prevention measure.
+
+```{mermaid}
+flowchart LR
+ subgraph Before
+ A1["Generator"] --> A2["Generated output"]
+ A2 --> A3["Used directly"]
+ end
+
+ subgraph After
+ B1["Generator"] --> B2["Generated output"]
+ B2 --> B3["Independent check"]
+ B3 --> B4["Accepted output"]
+ end
+```
+
+Before:
+
+```text
+:safety_affected: YES
+:detection_sufficient: NO
+```
+
+An **independent check** is a separate opportunity to prevent or detect the
+failure; it may be a downstream tool, compiler, consistency check, or review and
+does not require a different organisation.
+
+After the independent check becomes a mandatory part of intended usage:
+
+```text
+:safety_affected: YES
+:safety_measures: Independent checker validates every generated artifact.
+:detection_sufficient: YES
+```
+
+This requires **re-evaluating the affected malfunction because the usage concept
+changed**.
+
+The check must be mandatory for every applicable artifact or execution and gate
+acceptance. Verify this through workflow or test-campaign evidence, such as a
+CI gate, configured checker, or recorded review, and describe it in
+`safety_measures`. An occasional or merely proposed check is insufficient.
+
+It is not a reclassification caused by qualification.
+
+---
+
+## Step 6 — Qualify the tool if required
+
+Qualification is an evidence activity required when the evaluation results in
+insufficient confidence and the project does not resolve that through a changed
+usage concept. It does not happen merely because the qualification view is
+generated.
+
+SCORE uses validation of the software tool as the qualification approach.
+
+The qualification view starts from `tool_req`:
+
+```{mermaid}
+flowchart LR
+ TR["tool_req"]
+ TC["testcase"]
+ RESULT["Test result / evidence"]
+
+ TC -->|"fully_verifies or
partially_verifies"| TR
+ TC --> RESULT
+
+ style TR fill:#F5F5F5
+```
+
+In the current S-CORE Docs-as-Code model, test execution results are represented
+as `testcase` needs. Their normal links to requirements are:
+
+- `fully_verifies`
+- `partially_verifies`
+
+For each qualification-relevant `tool_req`, qualification requires at least one
+recorded testcase with `result: passed` and a `fully_verifies` link to that
+requirement. A `partially_verifies` link is useful supplemental traceability,
+but does not complete qualification by itself.
+
+### Qualification is not a detection measure
+
+Compare the two roles:
+
+```{mermaid}
+flowchart TB
+ subgraph Usage["Detection during intended tool usage"]
+ U1["Tool execution"] --> U2["Output"]
+ U2 --> U3["Independent / internal check"]
+ U3 --> U4["Output accepted"]
+ end
+
+ subgraph Qualification["Qualification evidence"]
+ Q1["Specific tool version"] --> Q2["Requirements-based testcase"]
+ Q2 --> Q3["Evidence that tool_req is satisfied"]
+ end
+```
+
+A qualification testcase answers:
+
+> Does this tool version satisfy the requirement?
+
+A safety measure used for `detection_sufficient` answers:
+
+> If the malfunction happens during real intended usage, will it be prevented or
+> detected before its result is relied upon?
+
+In other words, a qualification test checks whether the tool version satisfies
+a `tool_req`. `detection_sufficient` checks whether the measures used during
+normal tool usage will detect or prevent a malfunction before its output is
+relied upon. Passing a qualification test therefore does **not** by itself
+justify setting `detection_sufficient: YES`.
+
+The same technical check can only count for both if it actually performs both
+roles.
+
+---
+
+### External tool qualification
+
+External tools are commonly validated largely as black boxes.
+
+Example requirement:
+
+````markdown
+```{tool_req} Report unresolved requirement links
+:id: tool_req__docs_as_code__unresolved_links
+
+The tool shall report an unresolved requirement link as an error.
+```
+````
+
+This is an example of a requirements-based qualification test; other campaign
+tests need not be requirements-based:
+
+1. create input containing a known unresolved link,
+2. execute the configured tool version with the relevant configured invocation,
+3. verify that the expected error is reported.
+
+The campaign shall record the exact tool version, relevant configuration,
+environment and dependencies, invocation, input, expected and actual results,
+and outcome.
+
+The resulting SCORE `testcase` must link to the tool requirement via
+`fully_verifies` for the requirement to count as qualified. A
+`partially_verifies` link may be recorded as supplemental evidence, but does not
+complete qualification by itself.
+
+Vendor documentation, release notes, and **upstream tests**—tests maintained
+by the vendor or originating project—can support the argument. They remain
+supporting information unless accepted under the recorded conditions and traced
+to the relevant `tool_req`.
+
+---
+
+### Self-developed tool qualification
+
+For a self-developed tool, do not automatically create a separate qualification
+test suite.
+
+If the normal development process already produces testcases that:
+
+- verify the relevant `tool_req` needs,
+- execute against the tool version being qualified,
+- provide a recorded `result: passed`, and
+- link to each relevant requirement with `fully_verifies`,
+
+reuse those testcases as qualification evidence.
+
+Prefer:
+
+```{mermaid}
+flowchart LR
+ TR["Existing tool_req"] --> IMPL["Tool implementation"]
+ TC["Existing testcase"] -->|"fully_verifies / partially_verifies"| TR
+ TC --> E["Existing test evidence"]
+```
+
+over duplicating the same requirement and test solely for qualification.
+
+Additional qualification tests are needed only where the existing evidence is
+insufficient.
+
+---
+
+## Step 7 — Update the Tool Verification Report
+
+If no qualification was required, the evaluated report can proceed to review.
+
+If qualification was required and every relevant `tool_req` has the required
+passed `fully_verifies` evidence, set the report status to `qualified`.
+
+The evaluation result itself does **not** automatically change.
+
+Changing the status does not create qualification evidence; it records that the
+required evidence has already been provided.
+
+This is a valid final state before release; the generated summary remains LOW:
+
+```text
+:status: qualified
+```
+
+`LOW` records why qualification was necessary.
+
+`qualified` records that the additional qualification evidence has been
+provided.
+
+Qualification does not turn:
+
+```text
+:detection_sufficient: NO
+```
+
+into:
+
+```text
+:detection_sufficient: YES
+```
+
+unless the intended usage was also changed by introducing a new mandatory
+detection mechanism.
+
+---
+
+## Step 8 — Review and release the Tool Verification Report
+
+Review the completed TVR against the SCORE Tool Verification Report Review
+Checklist.
+
+The review shall describe, where applicable:
+
+- unique tool identification,
+- exact tool version and the relevant test-campaign execution conditions,
+- purpose and tool use cases,
+- inputs and outputs,
+- configuration,
+- environment and limitations,
+- documentation,
+- usage constraints,
+- potential malfunctions,
+- safety impact,
+- safety measures,
+- error detection,
+- overall confidence,
+- qualification evidence where required.
+
+After successful approval:
+
+```text
+:status: released
+```
+
+If verification is unsuccessful:
+
+```text
+:status: rejected
+```
+
+See:
+
+- [SCORE Tool Management Workflow](https://eclipse-score.github.io/process_description/main/process_areas/tool_management/tool_management_workflow.html)
+- [SCORE Tool Verification Report Template](https://eclipse-score.github.io/process_description/main/folder_templates/tools/tool_verification_report_template.html)
+- [SCORE Tool Management Process Requirements](https://eclipse-score.github.io/process_description/main/process_areas/tool_management/guidance/tool_management_reqs.html)
+- [SCORE Tool Verification Report Review Checklist](https://eclipse-score.github.io/process_description/main/process_areas/tool_management/guidance/tool_management_checklist.html)
+
+---
+
+## Complete example — external tool
+
+Assume an external documentation tool is used to validate requirement
+traceability.
+
+### Upstream requirement
+
+An upstream requirement is represented by a concrete SCORE requirement type, such
+as `stkh_req`, `gd_req`, `feat_req`, or `comp_req`. This example uses the
+existing `stkh_req` type:
+
+````markdown
+```{stkh_req} Provide valid verification traceability
+:id: stkh_req__docs_as_code__traceability
+:reqtype: Process
+:safety: ASIL_B
+:security: NO
+:status: valid
+:rationale: Invalid traceability can hide missing verification.
+:valid_from: v1.0
+
+Safety-related requirements shall have valid verification traceability.
+```
+````
+
+### Tool requirement
+
+````markdown
+```{tool_req} Report unresolved requirement links
+:id: tool_req__docs_as_code__unresolved_links
+:satisfies: stkh_req__docs_as_code__traceability
+
+The tool shall report unresolved requirement links as errors.
+```
+````
+
+### Tool use case and potential malfunction
+
+`````markdown
+::::{tool_usecase} Validate requirement traceability during documentation builds
+:id: tool_usecase__docs_as_code__traceability
+:belongs_to: doc_tool__s_core_docs_as_code
+
+The project relies on the documentation tool to detect invalid traceability
+before generated documentation is accepted.
+
+:::{potential_tool_malfunction} Unresolved requirement link is accepted as valid
+:id: potential_tool_malfunction__docs_as_code__unresolved_link_accepted
+:violates:
+ tool_req__docs_as_code__unresolved_links
+:safety_affected: YES
+:detection_sufficient: NO
+
+An unresolved requirement link is accepted as valid and no error is reported.
+:::
+::::
+`````
+
+The resulting model is:
+
+```{mermaid}
+flowchart LR
+ UP["stkh_req__docs_as_code__traceability"]
+ UC["tool_usecase__docs_as_code__traceability"]
+ TR["tool_req__docs_as_code__unresolved_links"]
+ PM["potential_tool_malfunction__docs_as_code__
unresolved_link_accepted"]
+
+ PM -->|"nested under
(parent_needs)"| UC
+ PM -->|"violates"| TR
+ TR -->|"satisfies"| UP
+
+ style UP fill:#DAE8FC
+ style UC fill:#E1D5E7
+ style TR fill:#F5F5F5
+ style PM fill:#F8CECC
+```
+
+Because the malfunction is safety relevant and detection is insufficient:
+
+```text
+SCORE TCL = LOW
+qualification required = YES
+```
+
+Qualification then validates
+`tool_req__docs_as_code__unresolved_links`.
+
+The resulting test execution must produce a `testcase` that links to the tool
+requirement using the normal SCORE verification links, for example
+`fully_verifies`.
+
+The TVR remains `LOW`, but after successful qualification its lifecycle can
+progress:
+
+```{mermaid}
+flowchart LR
+ A["evaluated
SCORE TCL = LOW"] --> B["qualification tests pass"]
+ B --> C["qualified
SCORE TCL = LOW"]
+ C --> D["released
SCORE TCL = LOW"]
+```
+
+---
+
+## Complete example — self-developed tool
+
+Assume the project develops its own model-to-code generator.
+
+### Existing requirements
+
+````markdown
+```{stkh_req} Generate software from the approved model
+:id: stkh_req__generator__approved_model
+:reqtype: Functional
+:safety: ASIL_B
+:security: NO
+:status: valid
+:rationale: Production code must represent the approved behavioural model.
+:valid_from: v1.0
+
+Software source shall be generated from the approved model.
+```
+````
+
+````markdown
+```{tool_req} Preserve configured state transitions
+:id: tool_req__generator__state_transitions
+:satisfies: stkh_req__generator__approved_model
+
+The generator shall preserve configured state transitions in generated source
+code.
+```
+````
+
+### Tool use case and potential malfunction
+
+`````markdown
+::::{tool_usecase} Generate production source from the approved model
+:id: tool_usecase__generator__generate_source
+:belongs_to: doc_tool__s_core_docs_as_code
+
+The project relies on the generator to transform the approved model into
+production source code.
+
+:::{potential_tool_malfunction} State transition is omitted from generated source
+:id: potential_tool_malfunction__generator__missing_transition
+:violates:
+ tool_req__generator__state_transitions
+:safety_affected: YES
+:detection_sufficient: NO
+
+A state transition present in the approved input model is omitted from the
+generated source code.
+:::
+::::
+`````
+
+Result:
+
+```text
+SCORE TCL = LOW
+qualification required = YES
+```
+
+If an existing requirements-based test already verifies
+`tool_req__generator__state_transitions` for the released generator version,
+reuse its generated `testcase` and test evidence for qualification.
+
+Do not create another requirement and another test solely to label them
+"qualification".
+
+---
+
+## When to evaluate the tool again
+
+Re-evaluate affected malfunctions when information used by the evaluation
+changes.
+
+Typical triggers include:
+
+- a new or changed tool use case,
+- new or changed upstream requirements (`stkh_req`, `gd_req`, `feat_req`, or `comp_req`),
+- new or changed tool requirements,
+- a changed tool version,
+- a changed relevant configuration,
+- a changed integration/environment,
+- a newly identified potential malfunction,
+- a new, changed, or removed safety measure,
+- a changed surrounding workflow/toolchain,
+- evidence that a previously sufficient detection measure is no longer
+ sufficient.
+
+Do **not** re-evaluate merely because qualification tests passed.
+
+Qualification adds evidence about the tool. It does not by itself change the
+intended usage or the detection measures.
+
+```{mermaid}
+flowchart TD
+ A{"What changed?"}
+
+ A -->|"Tool version / use case / requirement /
workflow / safety measure"| B["Re-evaluate affected malfunctions"]
+ A -->|"Only qualification tests executed"| C["No re-evaluation required"]
+
+ B --> D["Update TVR evaluation if result changed"]
+```
+
+---
+
+## Common mistakes
+
+### Treating `tool_usecase` as a new requirement level
+
+Do not invent new normative behaviour in `tool_usecase`.
+
+A `tool_usecase` describes how the project uses the tool. A `tool_req` describes
+a capability or behaviour provided by the tool, regardless of whether the project
+uses it in a particular use case. The use case provides context and grouping
+without introducing an additional normative requirement level.
+
+For example, "validate requirement traceability during
+documentation builds" is a use case; "the tool shall report unresolved links"
+is the tool requirement.
+
+Another example is the pair of use cases "Produce the platform components binaries"
+and "Create unit test binaries", which both map to the tool requirement
+"the compiler shall create an executable from C++ source code".
+
+The use case adds context, not another requirement level.
+
+```{mermaid}
+flowchart LR
+ UP["Upstream requirement"]
+ UC["Usage context"]
+ TR["Concrete tool behaviour"]
+
+ TR -->|"satisfies"| UP
+```
+
+---
+
+### Using generic malfunctions
+
+Avoid:
+
+```text
+The tool crashes.
+The tool has a bug.
+The result is wrong.
+```
+
+Prefer:
+
+```text
+A missing link is accepted as valid.
+A required artifact is omitted from generated output.
+The generated value differs from the configured source value.
+```
+
+---
+
+### Treating every existing test as a safety measure
+
+A test executed during development or release validation is not automatically a
+measure for `detection_sufficient`.
+
+Development and qualification tests provide evidence about a tool version;
+detection sufficiency requires a mechanism that runs during intended use and
+prevents or detects the malfunction before the result is relied upon. Therefore,
+adding a campaign test does not by itself make detection sufficient.
+
+Ask:
+
+> If the malfunction occurs during actual intended tool usage, does this
+> mechanism prevent or detect it before the result is relied upon?
+
+If not, it is not sufficient justification for:
+
+```text
+:detection_sufficient: YES
+```
+
+---
+
+### Treating qualification as reclassification
+
+Qualification may leave the evaluation unchanged:
+
+```text
+:safety_affected: YES
+:detection_sufficient: NO
+:status: qualified
+```
+
+This is not contradictory.
+
+`LOW` describes the evaluation.
+
+`qualified` describes the additional evidence.
+
+---
+
+### Duplicating external specifications
+
+Do not copy an external tool's complete specification into `tool_req`.
+
+Do not copy almost the entire external C&Q or qualification specification from
+the Internet either; capture only the capabilities and assumptions relevant to
+the evaluated use cases.
+
+Model the tool capability being evaluated; describe the project context in the
+corresponding `tool_usecase`.
+
+---
+
+### Duplicating self-developed tests
+
+Do not build a parallel qualification-test world when existing SCORE testcases
+already verify the relevant `tool_req` needs with suitable evidence.
+
+---
+
+## Quick reference
+
+```{mermaid}
+flowchart TD
+ A["1. Identify exact tool version / configuration"]
+ B["2. Create doc_tool
status = draft"]
+ C["3. Define tool_usecase"]
+ D["4. Link available tool_req
to upstream requirements"]
+ E["5. Identify potential_tool_malfunction"]
+ F["6. Read generated TVR summary"]
+ G["7. Document safety_measures
and detection_sufficient"]
+ H["8. Read generated SCORE TCL"]
+ I["status = evaluated"]
+ J{"SCORE TCL = LOW?"}
+ K["Verify relevant tool_req
with testcase evidence"]
+ L["status = qualified"]
+ M["Review / approve TVR"]
+ N["status = released"]
+
+ A --> B --> C --> D --> E --> F --> G --> H --> I --> J
+ J -->|"No"| M
+ J -->|"Yes"| K --> L --> M
+ M --> N
+```
+
+In short:
+
+```text
+tool_usecase
+ -> defines the context in which we rely on the tool
+
+potential_tool_malfunction
+ -> describes how that reliance can fail
+
+safety_affected
+ -> says whether the failure matters for safety
+
+safety_measures + detection_sufficient
+ -> describe whether intended usage prevents/detects the failure
+
+LOW
+ -> qualification is required
+
+testcase -> fully_verifies/partially_verifies -> tool_req
+ -> provides qualification evidence
+
+qualified
+ -> qualification evidence exists; it does not change the generated SCORE TCL
+```
+
+## SCORE references
+
+- [Tool Management Workflow](https://eclipse-score.github.io/process_description/main/process_areas/tool_management/tool_management_workflow.html)
+- [Tool Verification Report Template](https://eclipse-score.github.io/process_description/main/folder_templates/tools/tool_verification_report_template.html)
+- [Tool Management Process Requirements](https://eclipse-score.github.io/process_description/main/process_areas/tool_management/guidance/tool_management_reqs.html)
+- [Tool Verification Report Review Checklist](https://eclipse-score.github.io/process_description/main/process_areas/tool_management/guidance/tool_management_checklist.html)
diff --git a/docs/internals/requirements/index.rst b/docs/internals/requirements/index.rst
index 01ccf2655..7db237e7e 100644
--- a/docs/internals/requirements/index.rst
+++ b/docs/internals/requirements/index.rst
@@ -32,6 +32,9 @@ Pages
itself, including test results and testcase metadata.
- ``requirement_coverage`` shows per-requirement test and code linkage,
using the same metrics as CI quality gates.
+- ``tool_verification`` is the authoritative Tool Verification Report, including
+ the owned use cases, potential malfunctions, qualification evidence and
+ lifecycle state.
.. toctree::
:maxdepth: 1
@@ -41,3 +44,4 @@ Pages
requirements
requirement_coverage
tooling_verification
+ tool_verification
diff --git a/docs/internals/requirements/requirements.rst b/docs/internals/requirements/requirements.rst
index 4cc55f5ab..a2947951e 100644
--- a/docs/internals/requirements/requirements.rst
+++ b/docs/internals/requirements/requirements.rst
@@ -884,11 +884,11 @@ Testing
:parent_covered: YES
:satisfies: gd_req__tool_attr_safety_affected[version==1], gd_req__tool_check_mandatory[version==1]
- Docs-as-Code shall enforce that every Tool Verification Report (`doc_tool`) includes a
- ``safety_affected`` attribute with one of the following values:
+ Docs-as-Code shall derive the Tool Verification Report safety classification
+ from its owned potential tool malfunctions:
- * YES
- * NO
+ * ``YES`` if at least one owned malfunction is safety affected
+ * ``NO`` otherwise
.. tool_req:: Enforce security classification
:id: tool_req__docs_tvr_security
@@ -941,11 +941,11 @@ Testing
:satisfies: gd_req__tool_attr_tcl[version==1]
:parent_covered: YES
- Docs-as-Code shall enforce that every Tool Verification Report (`doc_tool`) includes a
- `tcl` attribute with one of the following values:
+ Docs-as-Code shall derive the Tool Confidence Level of a Tool Verification
+ Report from its owned potential tool malfunctions:
- * LOW
- * HIGH
+ * ``LOW`` if an owned safety-relevant malfunction has insufficient detection
+ * ``HIGH`` otherwise
⚙️ Process / Other
###################
diff --git a/docs/internals/requirements/tool_verification.rst b/docs/internals/requirements/tool_verification.rst
new file mode 100644
index 000000000..cc588cd5e
--- /dev/null
+++ b/docs/internals/requirements/tool_verification.rst
@@ -0,0 +1,419 @@
+..
+ # *******************************************************************************
+ # Copyright (c) 2025 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
+ # *******************************************************************************
+
+Doc-as-Code Tool Verification Report
+====================================
+
+This page is the authoritative Tool Verification Report for the S-CORE Docs-as-Code tool. It records the classification of intended usage, potential malfunctions, safety impact and detection, together with qualification evidence and lifecycle state.
+
+Introduction
+------------
+
+Scope and purpose
+~~~~~~~~~~~~~~~~~
+
+The S-CORE Docs-as-Code tool (Bazel module ``score_docs_as_code``) builds HTML
+documentation from RST/Markdown sources, including process descriptions,
+requirements, and traceability data. It validates the sources with the S-CORE
+extensions and metamodel.
+
+Inputs and outputs
+~~~~~~~~~~~~~~~~~~
+
+* **Inputs:** RST/Markdown sources, Sphinx configuration, the S-CORE metamodel,
+ Bazel build files, source-code links, and test results.
+* **Outputs:** HTML documentation, traceability data (``needs.json``), and
+ coverage and linkage statistics (``metrics.json``).
+
+.. mermaid::
+
+ flowchart LR
+ sources["RST/Markdown sources"] --> tool["S-CORE Docs-as-Code"]
+ config["Configuration and metamodel"] --> tool
+ links["Source-code links"] --> tool
+ tests["Test results"] --> tool
+ tool --> html["HTML documentation"]
+ tool --> needs["needs.json"]
+ tool --> metrics["metrics.json"]
+
+Available information
+~~~~~~~~~~~~~~~~~~~~~
+
+* Repository: https://github.com/eclipse-score/docs-as-code
+* Documentation: https://eclipse-score.github.io/docs-as-code/v8.1.2/
+* Bazel module: ``score_docs_as_code``
+
+Installation and integration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The tool is consumed as a Bazel module and integrated through the repository's
+documentation build targets. The evaluated configuration is the one defined by
+the repository's ``MODULE.bazel``, ``BUILD`` files, Sphinx configuration, and
+S-CORE metamodel. The relevant checks are run through ``//:docs_check`` and
+the associated documentation and traceability targets.
+
+Environment
+~~~~~~~~~~~
+
+The evaluation runs in the repository's supported Bazel environment on Linux,
+using the configured Sphinx, Python, and diagram-generation toolchains.
+
+Report record
+-------------
+
+.. doc_tool:: Doc-as-Code
+ :id: doc_tool__score_docs_as_code
+ :status: evaluated
+ :version: 3
+ :tool_version: v8.1.2
+ :security_affected: YES
+ :realizes: wp__tool_verification_report[version==1]
+ :post_template: tool_qualification_report
+
+ Evaluates the S-CORE Docs-as-Code tool for building and checking
+ documentation and traceability data from RST/Markdown sources.
+
+Details
+-------
+
+The safety evaluation uses the following shared facts:
+
+* **Build/CI behavior:** Builds run with ``-W``; any warning trips CI. The
+ safety-relevant danger is the *silent* failure — a missing warning or a wrong
+ output published undetected. A loud CI abort is safe: no wrong output enters
+ the baseline.
+* **PR Review:** Repository contents are the source of truth and every change
+ is reviewed by a committer (:need:`rl__committer`,
+ :need:`doc_concept__wp_inspections`). Still, for silent wrong outputs the
+ gated CI stays green.
+* **Derived-view:** The rendered HTML output is a derived view; the
+ authoritative safety artifacts are mostly the source-controlled work
+ products. There are two exceptions, the architecture views and backlinks.
+ Rendering/preview defects affect reviewer convenience, not safety evidence.
+
+Each of the following tool capabilities is evaluated as an intended use case
+with its corresponding potential malfunction.
+
+.. tool_usecase:: Enforce document types and attributes
+ :id: tool_usecase__docs_as_code__metamodel
+ :belongs_to: doc_tool__score_docs_as_code
+ :version: 1
+
+ Enforce document types and mandatory attributes such as id, status,
+ security, safety, and realizes. See, for example,
+ :need:`gd_req__doc_attr_status`, :need:`gd_req__req_attr_uid`,
+ :need:`gd_req__req_attr_safety`, :need:`gd_req__arch_attr_safety`,
+ and :need:`gd_req__req_check_mandatory`.
+
+ .. potential_tool_malfunction:: Invalid document passes metamodel checks
+ :id: potential_tool_malfunction__docs_as_code__m1
+ :safety_affected: YES
+ :detection_sufficient: NO
+ :safety_measures: PR review
+ :violates:
+ tool_req__docs_doc_generic_mandatory,
+ tool_req__docs_common_attr_id,
+ tool_req__docs_common_attr_id_scheme,
+ tool_req__docs_common_attr_status,
+ tool_req__docs_common_attr_description,
+ tool_req__docs_common_attr_title,
+ tool_req__docs_common_attr_desc_wording,
+ tool_req__docs_common_attr_security,
+ tool_req__docs_common_attr_safety,
+ tool_req__docs_common_attr_version,
+ tool_req__docs_common_attr_suspicious,
+ tool_req__docs_req_attr_rationale,
+ tool_req__docs_req_attr_reqtype,
+ tool_req__docs_req_attr_testcov,
+ tool_req__docs_req_attr_validity_correctness,
+ tool_req__docs_req_attr_validity_consistency,
+ tool_req__arch_linkage_safety,
+ tool_req__arch_consistency_interf,
+ tool_req__docs_tvr_safety,
+ tool_req__docs_tvr_security,
+ tool_req__docs_tvr_status,
+ tool_req__docs_tvr_version,
+ tool_req__docs_tvr_confidence_level,
+ tool_req__docs_saf_attrs_mitigated_by,
+ tool_req__docs_saf_attrs_mitigation_issue,
+ tool_req__docs_saf_attrs_sufficient,
+ tool_req__docs_saf_attrs_sufficient_check,
+ tool_req__docs_saf_attrs_content,
+ tool_req__docs_saf_attrs_violates,
+ tool_req__docs_saf_attrs_mandatory,
+ tool_req__docs_saf_attr_fmea_fault_id,
+ tool_req__docs_saf_attr_fmea_failure_effect,
+ tool_req__docs_sec_attr_stride_threat_id,
+ tool_req__docs_sec_attrs_mandatory
+ :version: 1
+
+ **Silent false-negative:** a too-permissive ``metamodel.yaml`` regex is
+ accepted without a guard, or a check bug skips a case.
+
+ Impact on safety: yes.
+ Impact safety measures available: yes: PR review.
+ Impact safety detection sufficient: no: Qualify metamodel enforcement.
+ Further additional safety measure required: yes (qualification).
+ Confidence (automatic calculation): low.
+
+.. tool_usecase:: Enforce safety-critical links
+ :id: tool_usecase__docs_as_code__safety_links
+ :belongs_to: doc_tool__score_docs_as_code
+ :version: 1
+
+ Enforce that links between safety-relevant needs preserve the required
+ safety relationships. See :need:`gd_req__req_linkage_safety`.
+
+ .. potential_tool_malfunction:: Unsafe link passes validation
+ :id: potential_tool_malfunction__docs_as_code__m2
+ :safety_affected: YES
+ :detection_sufficient: NO
+ :safety_measures: PR review
+ :violates:
+ tool_req__docs_common_attr_safety_link_check,
+ tool_req__docs_req_arch_link_safety_to_arch,
+ tool_req__docs_req_link_satisfies_allowed,
+ tool_req__docs_req_link_covers_aou,
+ tool_req__docs_arch_link_fulfils,
+ tool_req__docs_arch_link_fulfils_aou,
+ tool_req__docs_arch_link_aou_check,
+ tool_req__docs_arch_link_safety_to_req,
+ tool_req__docs_arch_link_security
+ :version: 1
+
+ **Silent false-negative:** links are allowed which cannot be safe
+ derivations.
+
+ Impact on safety: yes.
+ Impact safety measures available: yes: PR review.
+ Impact safety detection sufficient: no: Qualify graph checks.
+ Further additional safety measure required: yes (qualification).
+
+ The clearest gap is ``satisfied_by`` (and arguably ``covers``), which
+ carry the same "target at least as safe" obligation as the checked
+ ``fulfils``/``implements`` yet are unconstrained.
+
+ Confidence (automatic calculation): low.
+
+.. tool_usecase:: Calculate requirement coverage
+ :id: tool_usecase__docs_as_code__coverage
+ :belongs_to: doc_tool__score_docs_as_code
+ :version: 1
+
+ Count, per requirement type, the requirements carrying a ``testlink`` and
+ compute link-coverage percentages. See
+ :need:`gd_req__verification_reporting`.
+
+ .. potential_tool_malfunction:: Requirement coverage is wrong
+ :id: potential_tool_malfunction__docs_as_code__m3
+ :safety_affected: YES
+ :detection_sufficient: NO
+ :violates:
+ tool_req__docs_test_linkage_metrics,
+ tool_req__docs_verification_report_need
+ :version: 1
+
+ **Silent wrong-output:** a coverage statistic is computed incorrectly.
+
+ Impact on safety: yes.
+ Impact safety measures available: no.
+ Impact safety detection sufficient: no: Qualify coverage statistics.
+ Further additional safety measure required: yes (qualification).
+ Confidence (automatic calculation): low.
+
+.. tool_usecase:: Generate architecture diagrams
+ :id: tool_usecase__docs_as_code__architecture
+ :belongs_to: doc_tool__score_docs_as_code
+ :version: 1
+
+ Generate architecture diagrams. See :need:`gd_req__arch_viewpoints`.
+
+ .. potential_tool_malfunction:: Architecture diagram is wrong
+ :id: potential_tool_malfunction__docs_as_code__m4
+ :safety_affected: YES
+ :detection_sufficient: YES
+ :safety_measures: PR review includes architecture inspection
+ :violates: tool_req__docs_arch_views
+ :version: 1
+
+ **Silent wrong-output:** a diagram misrepresents the architecture.
+
+ Impact on safety: yes.
+ Impact safety measures available: yes: PR review includes architecture
+ inspection.
+ Impact safety detection sufficient: yes.
+ Further additional safety measure required: no.
+ Confidence (automatic calculation): high.
+
+.. tool_usecase:: Resolve testcase verification links
+ :id: tool_usecase__docs_as_code__test_linkage
+ :belongs_to: doc_tool__score_docs_as_code
+ :version: 1
+
+ For each ``testcase`` need, resolve its
+ ``partially_verifies``/``fully_verifies`` references against the needs set.
+ See :need:`gd_req__req_attr_testlink` and
+ :need:`gd_req__verification_reporting`.
+
+ .. potential_tool_malfunction:: Requirement appears tested when it is not
+ :id: potential_tool_malfunction__docs_as_code__m5
+ :safety_affected: YES
+ :detection_sufficient: NO
+ :violates:
+ tool_req__docs_test_link_testcase,
+ tool_req__docs_test_linkage_metrics,
+ tool_req__docs_verification_report_need,
+ tool_req__docs_test_metadata_mandatory_1,
+ tool_req__docs_test_metadata_mandatory_2,
+ tool_req__docs_test_metadata_link_levels
+ :version: 1
+
+ **Silent wrong-output:** the safety case believes the requirement is
+ tested where it is not.
+
+ Impact on safety: yes.
+ Impact safety measures available: no.
+ Impact safety detection sufficient: no: Qualify linkage statistics.
+ Further additional safety measure required: yes (qualification).
+ Confidence (automatic calculation): low.
+
+.. tool_usecase:: Validate testcase references
+ :id: tool_usecase__docs_as_code__test_refs
+ :belongs_to: doc_tool__score_docs_as_code
+ :version: 1
+
+ Check that test references are present and point to the intended
+ requirements. See :need:`gd_req__req_attr_testlink`.
+
+ .. potential_tool_malfunction:: Test reference is missing or outdated
+ :id: potential_tool_malfunction__docs_as_code__m6
+ :safety_affected: YES
+ :detection_sufficient: NO
+ :safety_measures: PR review
+ :violates: tool_req__docs_test_link_testcase
+ :version: 1
+
+ **Silent wrong-output:** a test references an outdated or missing
+ requirement.
+
+ Impact on safety: yes.
+ Impact safety measures available: yes: PR review.
+ Impact safety detection sufficient: no: Qualify test reference check.
+ Further additional safety measure required: yes (qualification).
+ Confidence (automatic calculation): low.
+
+.. tool_usecase:: List assumptions of use
+ :id: tool_usecase__docs_as_code__assumptions
+ :belongs_to: doc_tool__score_docs_as_code
+ :version: 1
+
+ Use ``needtable`` to communicate safety-critical assumptions of use in
+ safety manuals. See :need:`gd_guidl__saf_man` and
+ :need:`wp__platform_safety_manual`.
+
+ .. potential_tool_malfunction:: Assumption of use is missing or wrong
+ :id: potential_tool_malfunction__docs_as_code__m7
+ :safety_affected: YES
+ :detection_sufficient: NO
+ :safety_measures: PR review
+ :violates:
+ tool_req__docs_req_types,
+ tool_req__docs_req_link_covers_aou,
+ tool_req__docs_arch_link_fulfils_aou
+ :version: 1
+
+ **Silent wrong-output:** ``aou_req`` items might be missing or wrong.
+
+ Impact on safety: yes.
+ Impact safety measures available: yes: PR review.
+ Impact safety detection sufficient: no: Qualify ``needtable``.
+ Further additional safety measure required: yes (qualification).
+ Confidence (automatic calculation): low.
+
+.. tool_usecase:: Generate traceability backlinks
+ :id: tool_usecase__docs_as_code__backlinks
+ :belongs_to: doc_tool__score_docs_as_code
+ :version: 1
+
+ Generate correct backlinks for links between Needs items to provide
+ bi-directional traceability. See :need:`doc_concept__general_traceability`.
+
+ .. potential_tool_malfunction:: Backlink is wrong or missing
+ :id: potential_tool_malfunction__docs_as_code__m8
+ :safety_affected: YES
+ :detection_sufficient: NO
+ :violates: tool_req__docs_req_link_satisfies_allowed
+ :version: 1
+
+ **Silent wrong-output:** generated backlinks are wrong or missing.
+
+ Impact on safety: yes.
+ Impact safety measures available: no.
+ Impact safety detection sufficient: no: Qualify backlinks in HTML.
+ Further additional safety measure required: yes (qualification).
+ Confidence (automatic calculation): low.
+
+.. tool_usecase:: Generate HTML documentation
+ :id: tool_usecase__docs_as_code__generation
+ :belongs_to: doc_tool__score_docs_as_code
+ :version: 1
+
+ Generate complete and correct HTML apart from the aspects covered by the
+ other use cases. See :need:`gd_req__doc_attributes_manual` and
+ :need:`gd_req__doc_attr_status`.
+
+ .. potential_tool_malfunction:: HTML output is incomplete or wrong
+ :id: potential_tool_malfunction__docs_as_code__m9
+ :safety_affected: NO
+ :violates: tool_req__docs_doc_types
+ :version: 1
+
+ **Wrong output:** incomplete, outdated, or mis-rendered HTML.
+
+ Impact on safety: no: rendered-view defects affect reviewer convenience,
+ not safety evidence.
+ Impact safety measures available: no.
+ Impact safety detection sufficient: not applicable for a non-safety
+ malfunction.
+ Further additional safety measure required: no.
+ Confidence (automatic calculation): high.
+
+Security evaluation
+-------------------
+The threat model reduces to a single class: **source tampering**. The tool has
+no runtime attack surface — it is a build-time Sphinx extension reading
+source-controlled inputs and writing generated output.
+
+.. list-table:: S-CORE Docs-as-Code security evaluation
+ :header-rows: 1
+ :widths: 1 2 8 2 6 4 2
+
+ * - Threat identification
+ - Use case description
+ - Threats
+ - Impact on security?
+ - Impact security measures available?
+ - Impact security detection sufficient?
+ - Further additional security measure required?
+ * - T1
+ - | **Source tampering** — applies to all tool use cases.
+ | See :need:`gd_req__req_attr_security`, :need:`gd_req__arch_attr_security`, :need:`gd_req__req_linkage`,
+ | :need:`gd_req__req_traceability`, :need:`gd_req__arch_linkage_security_trace`.
+ - | An attacker with write access tampers with sources, configuration, or
+ | extension code to weaken/disable security checks or inject misleading
+ | content into published output.
+ - yes
+ - yes: PR review.
+ - yes
+ - no
diff --git a/src/extensions/score_metamodel/checks/tool_qualification.py b/src/extensions/score_metamodel/checks/tool_qualification.py
new file mode 100644
index 000000000..618b5268d
--- /dev/null
+++ b/src/extensions/score_metamodel/checks/tool_qualification.py
@@ -0,0 +1,324 @@
+# *******************************************************************************
+# 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 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# SPDX-License-Identifier: Apache-2.0
+# *******************************************************************************
+"""Validation for the structured SCORE tool-management workflow.
+
+The metamodel declares the data shape. This module validates the conditional
+meaning of that data and derives the TVR safety/TCL values from the owned
+use-case and malfunction graph.
+"""
+
+from collections.abc import Iterable
+from typing import Any
+
+from score_metamodel import CheckLogger, graph_check, local_check
+from sphinx.application import Sphinx
+from sphinx_needs.data import NeedsView
+from sphinx_needs.need_item import NeedItem
+
+
+def _link_values(need: NeedItem, link_name: str) -> list[str]:
+ """Return an outgoing link field as plain IDs."""
+ if link_name not in need.iter_links_keys():
+ return []
+ return [str(item) for item in need.get_links(link_name, as_str=True)]
+
+
+def _base_id(need_id: str) -> str:
+ """Remove an optional Sphinx-Needs version filter from a link ID."""
+ return need_id.split("[", 1)[0]
+
+
+def _need_index(needs: Iterable[NeedItem]) -> dict[str, NeedItem]:
+ """Index needs by stored IDs and IDs without a version selector."""
+ index: dict[str, NeedItem] = {}
+ for need in needs:
+ index[need["id"]] = need
+ index.setdefault(_base_id(need["id"]), need)
+ return index
+
+
+def _linked_targets(
+ source: NeedItem, link_name: str, index: dict[str, NeedItem]
+) -> list[NeedItem]:
+ """Resolve outgoing links from a Need, ignoring unknown targets."""
+ targets: list[NeedItem] = []
+ for target_id in _link_values(source, link_name):
+ target = index.get(target_id) or index.get(_base_id(target_id))
+ if target is not None:
+ targets.append(target)
+ return targets
+
+
+def _owned_usecases(
+ doc_tool: NeedItem, all_needs: Iterable[NeedItem]
+) -> list[NeedItem]:
+ """Return use cases explicitly owned by a Tool Verification Report."""
+ return [
+ need
+ for need in all_needs
+ if need["type"] == "tool_usecase"
+ and doc_tool["id"]
+ in {_base_id(link) for link in _link_values(need, "belongs_to")}
+ ]
+
+
+def _malfunctions_for_usecase(
+ usecase: NeedItem, all_needs: Iterable[NeedItem]
+) -> list[NeedItem]:
+ """Return malfunctions whose explicit parent is the given use case."""
+ usecase_id = _base_id(usecase["id"])
+ return [
+ need
+ for need in all_needs
+ if need["type"] == "potential_tool_malfunction"
+ and usecase_id
+ in {_base_id(link) for link in _link_values(need, "parent_needs")}
+ ]
+
+
+def _malfunctions_for_report(
+ doc_tool: NeedItem, all_needs: Iterable[NeedItem]
+) -> list[NeedItem]:
+ """Return all malfunctions belonging to a Tool Verification Report."""
+ usecases = _owned_usecases(doc_tool, all_needs)
+ found: list[NeedItem] = []
+ found_ids: set[str] = set()
+ for usecase in usecases:
+ for malfunction in _malfunctions_for_usecase(usecase, all_needs):
+ if malfunction["id"] not in found_ids:
+ found.append(malfunction)
+ found_ids.add(malfunction["id"])
+ return found
+
+
+def derive_tvr_values(malfunctions: Iterable[NeedItem]) -> tuple[str, str]:
+ """Derive TVR safety relevance and TCL from malfunction evaluations."""
+ malfunction_list = list(malfunctions)
+ safety_affected = (
+ "YES"
+ if any(need.get("safety_affected") == "YES" for need in malfunction_list)
+ else "NO"
+ )
+ tcl = (
+ "LOW"
+ if any(
+ need.get("safety_affected") == "YES"
+ and need.get("detection_sufficient") == "NO"
+ for need in malfunction_list
+ )
+ else "HIGH"
+ )
+ return safety_affected, tcl
+
+
+def _qualification_requirements(
+ malfunctions: Iterable[NeedItem], index: dict[str, NeedItem]
+) -> list[NeedItem]:
+ """Return unique tool requirements violated by LOW-confidence malfunctions."""
+ requirements: list[NeedItem] = []
+ requirement_ids: set[str] = set()
+ for malfunction in malfunctions:
+ if not (
+ malfunction.get("safety_affected") == "YES"
+ and malfunction.get("detection_sufficient") == "NO"
+ ):
+ continue
+ for requirement in _linked_targets(malfunction, "violates", index):
+ if (
+ requirement["type"] == "tool_req"
+ and requirement["id"] not in requirement_ids
+ ):
+ requirements.append(requirement)
+ requirement_ids.add(requirement["id"])
+ return requirements
+
+
+def _successful_full_testcase(
+ requirement: NeedItem, all_needs: Iterable[NeedItem]
+) -> bool:
+ """Return whether a passed testcase fully verifies the requirement.
+
+ Existing SCORE coverage distinguishes full and partial verification. A
+ qualification claim needs complete evidence for the tool requirement, so
+ partial links alone do not satisfy this workflow check. The current model
+ assumes one active TVR per tool; testcases are not yet linked to a specific
+ TVR or tool version, so parallel version-specific TVRs are not supported.
+ """
+ requirement_id = _base_id(requirement["id"])
+ for testcase in all_needs:
+ if testcase["type"] != "testcase" or testcase.get("result") != "passed":
+ continue
+ fully_verified = {
+ _base_id(link) for link in _link_values(testcase, "fully_verifies")
+ }
+ if requirement_id in fully_verified:
+ return True
+ return False
+
+
+def _check_low_malfunction_links(
+ needs: Iterable[NeedItem], index: dict[str, NeedItem], log: CheckLogger
+) -> None:
+ """Require qualification targets for every LOW-confidence malfunction."""
+ for malfunction in (
+ need for need in needs if need["type"] == "potential_tool_malfunction"
+ ):
+ is_low = (
+ malfunction.get("safety_affected") == "YES"
+ and malfunction.get("detection_sufficient") == "NO"
+ )
+ if not is_low:
+ continue
+
+ has_tool_requirement = any(
+ target["type"] == "tool_req"
+ for target in _linked_targets(malfunction, "violates", index)
+ )
+ if not has_tool_requirement:
+ log.warning_for_need(
+ malfunction,
+ "LOW-confidence malfunctions must violate at least one `tool_req` "
+ "so that qualification evidence can be produced.",
+ category="tool-qualification",
+ )
+
+
+def _validate_report_status(
+ doc_tool: NeedItem,
+ status: Any,
+ expected_tcl: str,
+ malfunctions: list[NeedItem],
+ index: dict[str, NeedItem],
+ needs: list[NeedItem],
+ log: CheckLogger,
+) -> None:
+ """Validate qualification and release requirements for a TVR status."""
+ if status not in ("qualified", "released"):
+ return
+ if status == "qualified" and expected_tcl != "LOW":
+ log.warning_for_need(
+ doc_tool,
+ f"`status: {status}` is only valid for a TVR with generated "
+ "SCORE TCL LOW; "
+ "HIGH-confidence reports do not require qualification.",
+ category="tool-qualification",
+ )
+ return
+ if expected_tcl == "HIGH":
+ # A HIGH-confidence report can progress directly from evaluated to
+ # released. Qualification is only a workflow state for LOW TCL.
+ return
+
+ requirements = _qualification_requirements(malfunctions, index)
+ for requirement in requirements:
+ if _successful_full_testcase(requirement, needs):
+ continue
+ log.warning_for_need(
+ doc_tool,
+ f"qualification evidence is incomplete: `{requirement['id']}` "
+ "needs a passed testcase with a `fully_verifies` link.",
+ category="tool-qualification",
+ )
+
+
+@local_check
+def check_tool_malfunction_evaluation(
+ _: Sphinx, need: NeedItem, log: CheckLogger
+) -> None:
+ """Validate conditional malfunction options.
+
+ Detection is only meaningful for a safety-relevant malfunction. For a
+ safety-relevant malfunction, a positive detection claim also needs the
+ free-text safety measure that explains what detects or prevents it.
+ """
+ if need["type"] != "potential_tool_malfunction":
+ return
+
+ safety_affected = need.get("safety_affected")
+ detection_sufficient = need.get("detection_sufficient")
+ safety_measures = need.get("safety_measures")
+
+ if safety_affected == "YES" and detection_sufficient not in ("YES", "NO"):
+ log.warning_for_need(
+ need,
+ "safety-relevant malfunctions must define `detection_sufficient` "
+ "as YES or NO.",
+ category="tool-qualification",
+ )
+ elif (
+ safety_affected == "YES"
+ and detection_sufficient == "YES"
+ and not safety_measures
+ ):
+ log.warning_for_need(
+ need,
+ "`detection_sufficient: YES` requires a non-empty `safety_measures` value.",
+ category="tool-qualification",
+ )
+ elif safety_affected == "NO" and detection_sufficient:
+ log.warning_for_need(
+ need,
+ "non-safety malfunctions must not define `detection_sufficient`.",
+ category="tool-qualification",
+ )
+
+
+@graph_check
+def check_tool_qualification_workflow(
+ _: Sphinx, all_needs: NeedsView, log: CheckLogger
+) -> None:
+ """Validate ownership, qualification prerequisites, and TVR workflow states."""
+ needs = list(
+ all_needs.filter_types(
+ [
+ "doc_tool",
+ "tool_usecase",
+ "potential_tool_malfunction",
+ "tool_req",
+ "testcase",
+ ]
+ ).values()
+ )
+ index = _need_index(needs)
+
+ _check_low_malfunction_links(needs, index, log)
+
+ for doc_tool in (need for need in needs if need["type"] == "doc_tool"):
+ status = doc_tool.get("status")
+ if status in ("draft", "rejected"):
+ continue
+
+ usecases = _owned_usecases(doc_tool, needs)
+ if not usecases:
+ # Reports without structured use cases are legacy records. Leave
+ # their manually supplied summary fields unchanged until they are
+ # migrated to the structured workflow.
+ continue
+
+ malfunctions = _malfunctions_for_report(doc_tool, needs)
+ safety_affected, expected_tcl = derive_tvr_values(malfunctions)
+ # These summary values are properties of the report's owned
+ # malfunction graph. Store them on the report so exported Needs and
+ # report consumers see the same classification that the workflow
+ # validation uses; authors only provide the malfunction-level inputs.
+ doc_tool["safety_affected"] = safety_affected
+ doc_tool["tcl"] = expected_tcl
+ _validate_report_status(
+ doc_tool,
+ status,
+ expected_tcl,
+ malfunctions,
+ index,
+ needs,
+ log,
+ )
diff --git a/src/extensions/score_metamodel/metamodel.yaml b/src/extensions/score_metamodel/metamodel.yaml
index 2a88de42d..fa318b3d1 100644
--- a/src/extensions/score_metamodel/metamodel.yaml
+++ b/src/extensions/score_metamodel/metamodel.yaml
@@ -247,13 +247,16 @@ needs_types:
mandatory_options:
# req-Id: tool_req__docs_tvr_status
status: ^(draft|evaluated|qualified|released|rejected)$
- # req-Id: tool_req__docs_tvr_safety
- safety_affected: "^(YES|NO)$"
# req-Id: tool_req__docs_tvr_security
security_affected: "^(YES|NO)$"
+ optional_options:
+ # These summary fields are rendered from the owned malfunction graph by
+ # the structured TVR workflow. They are optional so process templates
+ # can declare their output shape; report authors must not duplicate them.
+ # req-Id: tool_req__docs_tvr_safety
+ safety_affected: "^(YES|NO)$"
# req-Id: tool_req__docs_tvr_confidence_level
tcl: "^(LOW|HIGH)$"
- optional_options:
author: ^.*$
approver: ^.*$
reviewer: ^.*$
@@ -264,6 +267,43 @@ needs_types:
realizes: workproduct
parts: 2
+ # Tool qualification reports keep use cases top-level for readable rendering.
+ # Their malfunctions are nested below them and use parent_needs as their
+ # semantic relationship.
+ # Structured tool qualification reports use tool_usecase as the context
+ # anchor for their evaluation. Legacy reports without use cases remain
+ # accepted for compatibility and are left unchanged by the workflow check.
+ # Tool requirements are linked through malfunctions; upstream requirements
+ # are linked from those tool requirements.
+ tool_usecase:
+ title: Tool Use Case
+ description: |
+ Defines the usage context in which the tool is evaluated.
+ A tool use case does not introduce additional requirements.
+ Where applicable, its nested malfunctions provide context for the tool
+ requirements relevant to a particular way the project relies on the tool.
+ Those tool requirements may satisfy upstream requirements.
+ color: "#E1D5E7"
+ mandatory_links:
+ belongs_to: doc_tool
+ parts: 3
+
+ # A malfunction describes a violation of one or more requirements. Safety
+ # measures are deliberately kept as free text on the malfunction because
+ # they are report evidence, not independently traceable model elements.
+ potential_tool_malfunction:
+ title: Potential Tool Malfunction
+ color: "#F8CECC"
+ mandatory_options:
+ safety_affected: "^(YES|NO)$"
+ optional_options:
+ detection_sufficient: "^(YES|NO)$"
+ safety_measures: ^.+$
+ mandatory_links:
+ parent_needs: tool_usecase
+ violates: tool_req
+ parts: 3
+
# Requirements
# req-Id: tool_req__docs_req_types
stkh_req:
@@ -1066,6 +1106,17 @@ needs_extra_links:
incoming: realized by
outgoing: realizes
+ realized_by:
+ incoming: realized by
+ outgoing: realizes
+
+ # Sphinx-Needs stores nested Need relationships under parent_needs. Use
+ # report-oriented labels so generated links describe the containment rather
+ # than exposing the implementation-oriented word "parent".
+ parent_needs:
+ incoming: contains
+ outgoing: contained by
+
# Decision Record
affects:
incoming: affected by
diff --git a/src/extensions/score_metamodel/tests/__init__.py b/src/extensions/score_metamodel/tests/__init__.py
index 21307ca43..d436ad88b 100644
--- a/src/extensions/score_metamodel/tests/__init__.py
+++ b/src/extensions/score_metamodel/tests/__init__.py
@@ -110,12 +110,6 @@ def assert_info(self, expected_substring: str, expect_location: bool = True):
def need(**kwargs: Any) -> NeedItem:
"""Convenience function to create a NeedItem object with some defaults."""
- # Extract links (any list field that's not a core field)
- link_keys = {
- "links",
- }
- links = {k: kwargs.pop(k, []) for k in list(link_keys) if k in kwargs}
-
# Set defaults for core fields
kwargs.setdefault("id", "test_need")
kwargs.setdefault("type", "requirement")
@@ -154,7 +148,21 @@ def need(**kwargs: Any) -> NeedItem:
"content",
"pre_content",
"post_content",
+ "post_template",
+ }
+
+ # Test fixtures represent link values as lists. Store those values in the
+ # same NeedItem.links field used by Sphinx-Needs; scalar non-core values
+ # remain regular extras. Link names are configured dynamically by
+ # Sphinx-Needs, so keeping a second global list here would be brittle.
+ link_keys = {
+ key
+ for key, value in kwargs.items()
+ if key not in core_keys
+ and key not in source_content_keys
+ and isinstance(value, list)
}
+ links = {key: kwargs.pop(key) for key in link_keys}
# Extract extras (any remaining kwargs not in core or source/content)
extras = {
@@ -176,6 +184,7 @@ def need(**kwargs: Any) -> NeedItem:
content=kwargs.get("content", ""),
pre_content=kwargs.get("pre_content"),
post_content=kwargs.get("post_content"),
+ post_template=kwargs.get("post_template"),
)
return NeedItem(
diff --git a/src/extensions/score_metamodel/tests/rst/architecture/architecture_tests.rst b/src/extensions/score_metamodel/tests/rst/architecture/architecture_tests.rst
index 47b5e97f3..f7d79c308 100644
--- a/src/extensions/score_metamodel/tests/rst/architecture/architecture_tests.rst
+++ b/src/extensions/score_metamodel/tests/rst/architecture/architecture_tests.rst
@@ -15,6 +15,7 @@
.. test_metadata:: Test Architecture Needs
:id: test_metadata__architecture
+ :fully_verifies_list: potential_tool_malfunction__docs_as_code__m4
:partially_verifies_list: tool_req__docs_arch_types
:test_type: requirements_based
:derivation_technique: requirements_based
diff --git a/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst b/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst
index 112bb702c..5e66f5b16 100644
--- a/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst
+++ b/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst
@@ -14,6 +14,7 @@
.. test_metadata::
:id: test_metadata__metamodel_graph_checks
+ :fully_verifies_list: potential_tool_malfunction__docs_as_code__m2
:partially_verifies_list: tool_req__docs_common_attr_safety_link_check
:test_type: requirements_based
:derivation_technique: requirements_based
@@ -78,3 +79,33 @@
:status: valid
:derived_from: feat_req__parent0__abcd
:expect: unknown outgoing link
+
+
+.. doc_tool:: Nested tool qualification report
+ :id: doc_tool__nested_qualification
+ :status: evaluated
+ :security_affected: NO
+ :version: 1
+
+.. tool_usecase:: Nested tool use case
+ :id: tool_usecase__blabla__nested
+ :belongs_to: doc_tool__nested_qualification
+ :version: 1
+
+ A use case contains its tool malfunctions as nested needs.
+
+ .. potential_tool_malfunction:: Nested tool malfunction
+ :id: potential_tool_malfunction__blabla__nested
+ :safety_affected: YES
+ :detection_sufficient: NO
+ :safety_measures: Review the generated result before release.
+ :violates: tool_req__test__qualification
+ :version: 1
+
+ The generated result may contain a silent error.
+
+.. tool_req:: Test tool requirement
+ :id: tool_req__test__qualification
+ :version: 1
+
+ The test fixture uses this requirement as the target of tool qualification links.
diff --git a/src/extensions/score_metamodel/tests/rst/graph/test_tool_qualification.rst b/src/extensions/score_metamodel/tests/rst/graph/test_tool_qualification.rst
new file mode 100644
index 000000000..664093b5a
--- /dev/null
+++ b/src/extensions/score_metamodel/tests/rst/graph/test_tool_qualification.rst
@@ -0,0 +1,83 @@
+..
+ # *******************************************************************************
+ # 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 2.0 which is available at
+ # https://www.apache.org/licenses/LICENSE-2.0
+ #
+ # SPDX-License-Identifier: Apache-2.0
+ # *******************************************************************************
+
+.. test_metadata::
+ :id: test_metadata__tool_qualification
+ :test_type: requirements_based
+ :derivation_technique: requirements_based
+
+ Exercises the conditional local validation of tool malfunctions.
+
+.. doc_tool:: Draft tool verification report
+ :id: doc_tool__tool_qualification_checks
+ :status: draft
+ :security_affected: NO
+ :version: 1
+
+.. tool_req:: Tool qualification check requirement
+ :id: tool_req__tool_qualification_checks
+ :version: 1
+
+.. tool_usecase:: Tool qualification check use case
+ :id: tool_usecase__tool_qualification_checks
+ :belongs_to: doc_tool__tool_qualification_checks
+ :version: 1
+
+.. tool_usecase:: Tool usage context without a pre-existing requirement
+ :id: tool_usecase__tool_qualification_unmapped
+ :belongs_to: doc_tool__tool_qualification_checks
+ :version: 1
+
+.. potential_tool_malfunction:: Missing detection value
+ :id: potential_tool_malfunction__tool_qualification_missing_detection
+ :parent_needs: tool_usecase__tool_qualification_checks
+ :violates: tool_req__tool_qualification_checks
+ :safety_affected: YES
+ :version: 1
+ :expect: safety-relevant malfunctions must define
+
+.. potential_tool_malfunction:: Positive detection without measure
+ :id: potential_tool_malfunction__tool_qualification_missing_measure
+ :parent_needs: tool_usecase__tool_qualification_checks
+ :violates: tool_req__tool_qualification_checks
+ :safety_affected: YES
+ :detection_sufficient: YES
+ :version: 1
+ :expect: non-empty `safety_measures`
+
+.. potential_tool_malfunction:: Insufficient detection
+ :id: potential_tool_malfunction__tool_qualification_insufficient_detection
+ :parent_needs: tool_usecase__tool_qualification_checks
+ :violates: tool_req__tool_qualification_checks
+ :safety_affected: YES
+ :detection_sufficient: NO
+ :version: 1
+ :expect_not: safety-relevant malfunctions must define
+
+.. potential_tool_malfunction:: Non-safety malfunction
+ :id: potential_tool_malfunction__tool_qualification_non_safety
+ :parent_needs: tool_usecase__tool_qualification_checks
+ :violates: tool_req__tool_qualification_checks
+ :safety_affected: NO
+ :version: 1
+ :expect_not: non-safety malfunctions must not define
+
+.. potential_tool_malfunction:: Meaningless detection value
+ :id: potential_tool_malfunction__tool_qualification_meaningless_detection
+ :parent_needs: tool_usecase__tool_qualification_checks
+ :violates: tool_req__tool_qualification_checks
+ :safety_affected: NO
+ :detection_sufficient: YES
+ :version: 1
+ :expect: non-safety malfunctions must not define
diff --git a/src/extensions/score_metamodel/tests/rst/options/test_doc_tool_attrs.rst b/src/extensions/score_metamodel/tests/rst/options/test_doc_tool_attrs.rst
index 1cd74cd29..a6f51ba8c 100644
--- a/src/extensions/score_metamodel/tests/rst/options/test_doc_tool_attrs.rst
+++ b/src/extensions/score_metamodel/tests/rst/options/test_doc_tool_attrs.rst
@@ -13,17 +13,17 @@
# *******************************************************************************
-.. test_metadata:: Mandatory Document and TVR Attributes
+.. test_metadata:: Document and TVR Attribute Validation
:id: test_metadata__doc_tool_attrs
:fully_verifies_list: tool_req__docs_doc_generic_mandatory,tool_req__docs_tvr_safety,tool_req__docs_tvr_security,tool_req__docs_tvr_status,tool_req__docs_tvr_confidence_level
:partially_verifies_list: tool_req__docs_tvr_version
:test_type: requirements_based
:derivation_technique: requirements_based
- Tests the mandatory attributes of Generic Documents (document)
- and Tool Verification Reports (doc_tool) as defined in the metamodel.
- Every mandatory attribute is exercised with an accepted value, an
- unaccepted value, and an omitted value.
+ Tests the mandatory attributes of Generic Documents (document) and Tool
+ Verification Reports (doc_tool), plus validation of TVR summary attribute
+ values, as defined in the metamodel. Every mandatory attribute is
+ exercised with an accepted value, an unaccepted value, and an omitted value.
The tool_version attribute is not enforced yet for migration purposes.
@@ -159,7 +159,14 @@
:tcl: MEDIUM
:expect: doc_tool__attr_bad_tcl.tcl (MEDIUM): does not follow pattern
+.. doc_tool:: TVR Without Tool Version During Migration
+ :id: doc_tool__attr_missing_tool_version
+ :version: 1
+ :status: evaluated
+ :security_affected: NO
+ :expect_not: is missing required attribute: `tool_version`
+
.. doc_tool:: TVR With Missing Mandatory Attributes
:id: doc_tool__attr_missing
:version: 1
- :expect: doc_tool__attr_missing: is missing required attribute: `status`., doc_tool__attr_missing: is missing required attribute: `safety_affected`., doc_tool__attr_missing: is missing required attribute: `security_affected`., doc_tool__attr_missing: is missing required attribute: `tcl`.
+ :expect: doc_tool__attr_missing: is missing required attribute: `status`., doc_tool__attr_missing: is missing required attribute: `security_affected`.
diff --git a/src/extensions/score_metamodel/tests/test_metamodel__init__.py b/src/extensions/score_metamodel/tests/test_metamodel__init__.py
index f7d36eae0..ba4de2659 100644
--- a/src/extensions/score_metamodel/tests/test_metamodel__init__.py
+++ b/src/extensions/score_metamodel/tests/test_metamodel__init__.py
@@ -122,7 +122,7 @@ def test_custom_values_override_defaults(self):
assert n["tags"] == ["tag1", "tag2"]
def test_link_fields_extracted(self):
- """Verify link fields are extracted and accessible via .get()."""
+ """Verify link fields use NeedItem's link storage and accessors."""
n = need(
complies=["std_req_1", "std_req_2"],
input=["wp_input_1"],
@@ -136,6 +136,7 @@ def test_link_fields_extracted(self):
assert n.get("output", []) == ["wp_output_1", "wp_output_2"]
assert n.get("contains", []) == ["item_1"]
assert n.get("satisfies", []) == ["req_1"]
+ assert n.get_links("satisfies", as_str=True) == ["req_1"]
def test_extra_fields_in_extras(self):
"""Verify extra fields (not core, not links) go into extras."""
diff --git a/src/extensions/score_metamodel/tests/test_metamodel_load.py b/src/extensions/score_metamodel/tests/test_metamodel_load.py
index 9b621e3ea..651fd28a7 100644
--- a/src/extensions/score_metamodel/tests/test_metamodel_load.py
+++ b/src/extensions/score_metamodel/tests/test_metamodel_load.py
@@ -14,6 +14,7 @@
from unittest.mock import mock_open, patch
import pytest
+from attribute_plugin import add_test_properties # type: ignore[import-untyped]
from score_metamodel import ProhibitedWordCheck, load_metamodel_data
MODEL_DIR = Path(__file__).absolute().parent / "model"
@@ -188,6 +189,11 @@ def test_sphinx_needs_builtin_links_are_accepted(tmp_path: Path):
}
+@add_test_properties(
+ fully_verifies=["potential_tool_malfunction__docs_as_code__m1"],
+ test_type="requirements-based",
+ derivation_technique="requirements-analysis",
+)
def test_all_undeclared_links_are_reported_at_once(tmp_path: Path):
"""Every offending link is listed, so one run shows all the work to do."""
model = _write_model(
@@ -205,6 +211,52 @@ def test_all_undeclared_links_are_reported_at_once(tmp_path: Path):
assert "ghost_two" in message
+@add_test_properties(
+ fully_verifies=["potential_tool_malfunction__docs_as_code__m1"],
+ test_type="requirements-based",
+ derivation_technique="requirements-analysis",
+)
def test_shipped_metamodel_declares_every_link_it_uses():
"""The metamodel shipped with this extension must satisfy the check."""
load_metamodel_data()
+
+
+@add_test_properties(
+ fully_verifies=["potential_tool_malfunction__docs_as_code__m1"],
+ test_type="requirements-based",
+ derivation_technique="requirements-analysis",
+)
+def test_tool_qualification_types_model_nested_traceability():
+ """Tool malfunctions are nested under use cases and carry safety text."""
+ result = load_metamodel_data()
+ types = {need_type["directive"]: need_type for need_type in result.needs_types}
+
+ report_options = types["doc_tool"]
+ assert "safety_affected" not in report_options["mandatory_options"]
+ assert report_options["optional_options"]["safety_affected"] == "^(YES|NO)$"
+ assert "tcl" not in report_options["mandatory_options"]
+ assert report_options["optional_options"]["tcl"] == "^(LOW|HIGH)$"
+
+ assert types["tool_usecase"]["mandatory_links_str"] == {
+ "belongs_to": "doc_tool",
+ }
+ assert types["tool_usecase"]["optional_links_str"] == {}
+ assert types["potential_tool_malfunction"]["mandatory_links_str"] == {
+ "parent_needs": "tool_usecase",
+ "violates": "tool_req",
+ }
+ malfunction_options = types["potential_tool_malfunction"]
+ assert malfunction_options["mandatory_options"]["safety_affected"] == "^(YES|NO)$"
+ assert "detection_sufficient" not in malfunction_options["mandatory_options"]
+ assert (
+ malfunction_options["optional_options"]["detection_sufficient"] == "^(YES|NO)$"
+ )
+ assert malfunction_options["optional_options"]["safety_measures"] == "^.+$"
+ assert (
+ types["potential_tool_malfunction"]["optional_options"]["safety_measures"]
+ == "^.+$"
+ )
+ assert result.needs_links["parent_needs"] == {
+ "incoming": "contains",
+ "outgoing": "contained by",
+ }
diff --git a/src/extensions/score_metamodel/tests/test_tool_qualification.py b/src/extensions/score_metamodel/tests/test_tool_qualification.py
new file mode 100644
index 000000000..974712b04
--- /dev/null
+++ b/src/extensions/score_metamodel/tests/test_tool_qualification.py
@@ -0,0 +1,356 @@
+# *******************************************************************************
+# 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 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# SPDX-License-Identifier: Apache-2.0
+# *******************************************************************************
+"""Tests for the structured SCORE tool-management workflow checks."""
+
+from typing import Any
+from unittest.mock import MagicMock
+
+import pytest
+from sphinx_needs.need_item import NeedItem
+
+from src.extensions.score_metamodel.checks.tool_qualification import (
+ check_tool_malfunction_evaluation,
+ check_tool_qualification_workflow,
+ derive_tvr_values,
+)
+from src.extensions.score_metamodel.tests import fake_check_logger, need
+
+
+def _run_local(malfunction: NeedItem, logger: Any = None) -> Any:
+ logger = logger or fake_check_logger()
+ check_tool_malfunction_evaluation(MagicMock(), malfunction, logger)
+ return logger
+
+
+def _graph_needs(*needs: NeedItem) -> MagicMock:
+ all_needs = MagicMock()
+ all_needs.values.return_value = list(needs)
+ all_needs.filter_types.return_value = all_needs
+ return all_needs
+
+
+def _low_report(
+ *,
+ status: str = "evaluated",
+ testcase: NeedItem | None = None,
+ violated: list[str] | None = None,
+) -> MagicMock:
+ report = need(
+ id="doc_tool__test_report",
+ type="doc_tool",
+ status=status,
+ safety_affected="",
+ tcl="",
+ )
+ usecase = need(
+ id="tool_usecase__test_report__context",
+ type="tool_usecase",
+ belongs_to=["doc_tool__test_report"],
+ )
+ requirement = need(
+ id="tool_req__test_report__requirement",
+ type="tool_req",
+ )
+ malfunction = need(
+ id="potential_tool_malfunction__test_report__failure",
+ type="potential_tool_malfunction",
+ safety_affected="YES",
+ detection_sufficient="NO",
+ parent_needs=["tool_usecase__test_report__context"],
+ violates=violated or ["tool_req__test_report__requirement"],
+ )
+ needs = [report, usecase, requirement, malfunction]
+ if testcase is not None:
+ needs.append(testcase)
+ return _graph_needs(*needs)
+
+
+@pytest.mark.parametrize(
+ ("safety_affected", "detection_sufficient", "safety_measures", "message"),
+ [
+ ("YES", None, None, "safety-relevant malfunctions"),
+ ("YES", "YES", "", "non-empty `safety_measures`"),
+ ],
+)
+def test_safety_malfunction_requires_conditional_evaluation_data(
+ safety_affected: str,
+ detection_sufficient: str | None,
+ safety_measures: str | None,
+ message: str,
+):
+ """Safety-relevant malfunctions require detection and a positive measure."""
+ logger = _run_local(
+ need(
+ id="potential_tool_malfunction__local_invalid",
+ type="potential_tool_malfunction",
+ safety_affected=safety_affected,
+ detection_sufficient=detection_sufficient,
+ safety_measures=safety_measures,
+ )
+ )
+
+ logger.assert_warning(message)
+
+
+def test_positive_detection_with_measure_is_valid():
+ """A safety-relevant malfunction may be HIGH when its measure is documented."""
+ logger = _run_local(
+ need(
+ id="potential_tool_malfunction__local_valid",
+ type="potential_tool_malfunction",
+ safety_affected="YES",
+ detection_sufficient="YES",
+ safety_measures="Independent review of the generated result.",
+ )
+ )
+
+ logger.assert_no_warnings()
+
+
+def test_safety_malfunction_with_insufficient_detection_needs_no_measure():
+ """LOW evaluation is valid without a measure because it requires qualification."""
+ logger = _run_local(
+ need(
+ id="potential_tool_malfunction__local_low",
+ type="potential_tool_malfunction",
+ safety_affected="YES",
+ detection_sufficient="NO",
+ )
+ )
+
+ logger.assert_no_warnings()
+
+
+def test_non_safety_malfunction_does_not_need_detection():
+ """Non-safety malfunctions omit detection because it is not meaningful there."""
+ logger = _run_local(
+ need(
+ id="potential_tool_malfunction__local_non_safety",
+ type="potential_tool_malfunction",
+ safety_affected="NO",
+ )
+ )
+
+ logger.assert_no_warnings()
+
+
+def test_non_safety_malfunction_rejects_meaningless_detection():
+ """The local check rejects detection data that has no safety meaning."""
+ logger = _run_local(
+ need(
+ id="potential_tool_malfunction__local_meaningless_detection",
+ type="potential_tool_malfunction",
+ safety_affected="NO",
+ detection_sufficient="YES",
+ )
+ )
+
+ logger.assert_warning("must not define `detection_sufficient`")
+
+
+def test_low_malfunction_with_tool_requirement_is_valid():
+ """A LOW malfunction is qualification-ready when it violates a tool requirement."""
+ logger = fake_check_logger()
+ check_tool_qualification_workflow(MagicMock(), _low_report(), logger)
+
+ logger.assert_no_warnings()
+
+
+def test_report_summary_is_derived_when_summary_fields_are_omitted():
+ """A TVR stores summary values derived from its malfunction graph."""
+ all_needs = _low_report()
+ logger = fake_check_logger()
+
+ check_tool_qualification_workflow(MagicMock(), all_needs, logger)
+
+ logger.assert_no_warnings()
+ report = all_needs.values.return_value[0]
+ assert report["safety_affected"] == "YES"
+ assert report["tcl"] == "LOW"
+
+
+def test_report_without_usecase_is_left_unchanged():
+ """Legacy reports without use cases remain compatible with the workflow check."""
+ report = need(
+ id="doc_tool__report_without_usecase",
+ type="doc_tool",
+ status="evaluated",
+ safety_affected="",
+ tcl="",
+ )
+ logger = fake_check_logger()
+
+ check_tool_qualification_workflow(MagicMock(), _graph_needs(report), logger)
+
+ logger.assert_no_warnings()
+ assert report["safety_affected"] == ""
+ assert report["tcl"] == ""
+
+
+def test_low_malfunction_with_only_upstream_requirement_is_invalid():
+ """An upstream requirement alone cannot receive tool qualification evidence."""
+ upstream = need(id="stkh_req__test_report__requirement", type="stkh_req")
+ all_needs = _low_report(violated=["stkh_req__test_report__requirement"])
+ all_needs.values.return_value.append(upstream)
+ logger = fake_check_logger()
+
+ check_tool_qualification_workflow(MagicMock(), all_needs, logger)
+
+ logger.assert_warning("must violate at least one `tool_req`")
+
+
+def test_mixed_use_cases_derive_low_from_one_low_malfunction():
+ """One LOW malfunction keeps a report LOW even when another use case is HIGH."""
+ all_needs = _low_report()
+ high_usecase = need(
+ id="tool_usecase__test_report__high_context",
+ type="tool_usecase",
+ belongs_to=["doc_tool__test_report"],
+ )
+ high_malfunction = need(
+ id="potential_tool_malfunction__test_report__high_failure",
+ type="potential_tool_malfunction",
+ safety_affected="YES",
+ detection_sufficient="YES",
+ safety_measures="Independent review.",
+ parent_needs=[high_usecase["id"]],
+ )
+ all_needs.values.return_value.extend([high_usecase, high_malfunction])
+ logger = fake_check_logger()
+
+ check_tool_qualification_workflow(MagicMock(), all_needs, logger)
+
+ logger.assert_no_warnings()
+
+
+def test_qualified_low_report_requires_successful_full_evidence():
+ """A qualified LOW report needs passed full-verification evidence."""
+ all_needs = _low_report(status="qualified")
+ logger = fake_check_logger()
+
+ check_tool_qualification_workflow(MagicMock(), all_needs, logger)
+
+ logger.assert_warning("qualification evidence is incomplete")
+
+
+def test_qualified_low_report_with_passed_full_evidence_is_valid():
+ """A passed testcase with full verification completes LOW qualification."""
+ testcase = need(
+ id="testcase__test_report__qualification",
+ type="testcase",
+ result="passed",
+ fully_verifies=["tool_req__test_report__requirement"],
+ )
+ all_needs = _low_report(status="qualified", testcase=testcase)
+ logger = fake_check_logger()
+
+ check_tool_qualification_workflow(MagicMock(), all_needs, logger)
+
+ logger.assert_no_warnings()
+
+
+def test_qualified_low_report_requires_evidence_for_each_requirement():
+ """Qualification evidence is required for every LOW tool requirement."""
+ testcase = need(
+ id="testcase__test_report__first_requirement",
+ type="testcase",
+ result="passed",
+ fully_verifies=["tool_req__test_report__requirement"],
+ )
+ second_requirement = need(
+ id="tool_req__test_report__second_requirement",
+ type="tool_req",
+ )
+ second_malfunction = need(
+ id="potential_tool_malfunction__test_report__second_failure",
+ type="potential_tool_malfunction",
+ safety_affected="YES",
+ detection_sufficient="NO",
+ parent_needs=["tool_usecase__test_report__context"],
+ violates=[second_requirement["id"]],
+ )
+ all_needs = _low_report(status="qualified", testcase=testcase)
+ all_needs.values.return_value.extend([second_requirement, second_malfunction])
+ logger = fake_check_logger()
+
+ check_tool_qualification_workflow(MagicMock(), all_needs, logger)
+
+ logger.assert_warning("tool_req__test_report__second_requirement")
+
+
+def test_partial_evidence_alone_does_not_complete_low_qualification():
+ """Partial verification is traceability evidence, not complete qualification."""
+ testcase = need(
+ id="testcase__test_report__partial_qualification",
+ type="testcase",
+ result="passed",
+ partially_verifies=["tool_req__test_report__requirement"],
+ )
+ all_needs = _low_report(status="qualified", testcase=testcase)
+ logger = fake_check_logger()
+
+ check_tool_qualification_workflow(MagicMock(), all_needs, logger)
+
+ logger.assert_warning("qualification evidence is incomplete")
+
+
+def test_released_low_report_also_requires_successful_full_evidence():
+ """Release cannot bypass qualification for a LOW report."""
+ all_needs = _low_report(status="released")
+ logger = fake_check_logger()
+
+ check_tool_qualification_workflow(MagicMock(), all_needs, logger)
+
+ logger.assert_warning("qualification evidence is incomplete")
+
+
+def test_high_report_does_not_require_qualification():
+ """HIGH reports may be evaluated or released without qualification tests."""
+ report = need(
+ id="doc_tool__high_report",
+ type="doc_tool",
+ status="released",
+ safety_affected="",
+ tcl="",
+ )
+ usecase = need(
+ id="tool_usecase__high_report__context",
+ type="tool_usecase",
+ belongs_to=[report["id"]],
+ )
+ malfunction = need(
+ id="potential_tool_malfunction__high_report__failure",
+ type="potential_tool_malfunction",
+ safety_affected="NO",
+ parent_needs=[usecase["id"]],
+ )
+ logger = fake_check_logger()
+
+ check_tool_qualification_workflow(
+ MagicMock(), _graph_needs(report, usecase, malfunction), logger
+ )
+
+ logger.assert_no_warnings()
+
+
+def test_qualification_does_not_reclassify_detection_or_tcl():
+ """Successful qualification leaves the malfunction and derived TCL unchanged."""
+ malfunction = need(
+ id="potential_tool_malfunction__qualification_is_not_classification",
+ type="potential_tool_malfunction",
+ safety_affected="YES",
+ detection_sufficient="NO",
+ )
+
+ assert derive_tvr_values([malfunction]) == ("YES", "LOW")
+ assert malfunction["detection_sufficient"] == "NO"
diff --git a/src/extensions/score_metrics/tests/test_traceability_metrics.py b/src/extensions/score_metrics/tests/test_traceability_metrics.py
index ac34eb628..a283f0dbd 100644
--- a/src/extensions/score_metrics/tests/test_traceability_metrics.py
+++ b/src/extensions/score_metrics/tests/test_traceability_metrics.py
@@ -153,7 +153,10 @@ def test_safe_percent_zero(value1: int, value2: int, expected: float) -> None:
@add_test_properties(
- partially_verifies=["tool_req__docs_test_linkage_metrics"],
+ partially_verifies=[
+ "tool_req__docs_test_linkage_metrics",
+ "potential_tool_malfunction__docs_as_code__m3",
+ ],
test_type="requirements-based",
derivation_technique="requirements-analysis",
)
@@ -231,7 +234,10 @@ def test_calculate_requirement_metrics_non_fully_linked_2() -> None:
@add_test_properties(
- partially_verifies=["tool_req__docs_test_linkage_metrics"],
+ partially_verifies=[
+ "tool_req__docs_test_linkage_metrics",
+ "potential_tool_malfunction__docs_as_code__m3",
+ ],
test_type="interface-test",
derivation_technique="design-analysis",
)
diff --git a/src/extensions/score_source_code_linker/tests/test_source_code_link_integration.py b/src/extensions/score_source_code_linker/tests/test_source_code_link_integration.py
index de7383f62..209834dc6 100644
--- a/src/extensions/score_source_code_linker/tests/test_source_code_link_integration.py
+++ b/src/extensions/score_source_code_linker/tests/test_source_code_link_integration.py
@@ -23,6 +23,7 @@
from sphinx.testing.util import SphinxTestApp
from sphinx_needs.data import SphinxNeedsData
+from score_pytest.attribute_plugin import add_test_properties
from src.extensions.score_source_code_linker.helpers import get_github_link
from src.extensions.score_source_code_linker.needlinks import NeedLink
from src.extensions.score_source_code_linker.repo_source_links import RepoInfo
@@ -514,6 +515,14 @@ def compare_grouped_json_files(file1: Path, golden_file: Path):
)
+@add_test_properties(
+ fully_verifies=[
+ "potential_tool_malfunction__docs_as_code__m8",
+ "potential_tool_malfunction__docs_as_code__m9",
+ ],
+ test_type="requirements-based",
+ derivation_technique="requirements-analysis",
+)
def test_source_link_integration_ok(
sphinx_app_setup: Callable[[], SphinxTestApp],
example_source_link_text_all_ok: dict[str, list[NeedLink]],
diff --git a/src/extensions/score_source_code_linker/tests/test_testlink.py b/src/extensions/score_source_code_linker/tests/test_testlink.py
index 9d3e8ecab..5114d0dde 100644
--- a/src/extensions/score_source_code_linker/tests/test_testlink.py
+++ b/src/extensions/score_source_code_linker/tests/test_testlink.py
@@ -99,6 +99,10 @@ def test_clean_text_removes_ansi_and_html_unescapes():
@add_test_properties(
partially_verifies=["tool_req__docs_test_link_testcase"],
+ fully_verifies=[
+ "potential_tool_malfunction__docs_as_code__m5",
+ "potential_tool_malfunction__docs_as_code__m6",
+ ],
test_type="requirements-based",
derivation_technique="requirements-analysis",
)
diff --git a/src/extensions/score_source_code_linker/tests/test_xml_parser.py b/src/extensions/score_source_code_linker/tests/test_xml_parser.py
index 86db75245..7aa1a362c 100644
--- a/src/extensions/score_source_code_linker/tests/test_xml_parser.py
+++ b/src/extensions/score_source_code_linker/tests/test_xml_parser.py
@@ -277,6 +277,10 @@ def test_parse_properties():
@add_test_properties(
partially_verifies=["tool_req__docs_test_link_testcase"],
+ fully_verifies=[
+ "potential_tool_malfunction__docs_as_code__m5",
+ "potential_tool_malfunction__docs_as_code__m6",
+ ],
test_type="requirements-based",
derivation_technique="requirements-analysis",
)
diff --git a/src/extensions/score_sphinx_needs_templates/BUILD b/src/extensions/score_sphinx_needs_templates/BUILD
index 21ebcfa31..2bc323480 100644
--- a/src/extensions/score_sphinx_needs_templates/BUILD
+++ b/src/extensions/score_sphinx_needs_templates/BUILD
@@ -32,7 +32,13 @@ py_library(
score_pytest(
name = "unit_tests",
+ # The template regression test builds a temporary Sphinx HTML project,
+ # so it needs more time than a pure Python unit test.
+ size = "medium",
srcs = ["tests/test_needs_templates.py"],
- deps = [":score_sphinx_needs_templates"],
+ deps = [
+ ":score_sphinx_needs_templates",
+ "//src/extensions/score_metamodel:score_metamodel",
+ ],
pytest_config = "//:pyproject.toml",
)
diff --git a/src/extensions/score_sphinx_needs_templates/tests/test_needs_templates.py b/src/extensions/score_sphinx_needs_templates/tests/test_needs_templates.py
index 85224bf13..3c477507b 100644
--- a/src/extensions/score_sphinx_needs_templates/tests/test_needs_templates.py
+++ b/src/extensions/score_sphinx_needs_templates/tests/test_needs_templates.py
@@ -13,9 +13,11 @@
"""Tests for graph traversal helpers used by Sphinx-Needs templates."""
from collections.abc import Iterable
+from pathlib import Path
import pytest
import score_sphinx_needs_templates as templates
+from sphinx.testing.util import SphinxTestApp
class FakeLink:
@@ -169,3 +171,143 @@ def test_any_req_in_report_version_false_if_no_requirement_matches() -> None:
b["valid_from"] = "v3.0"
assert any_req_in_report_version([a, b], "v1.0") is False
+
+
+def test_tool_qualification_matrix_groups_shared_requirements(
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """Render shared tool requirements in the qualification matrix.
+
+ A tool requirement is reusable across project use cases. This fixture models
+ two use cases, each with a different LOW-confidence malfunction, while both
+ malfunctions violate the same tool requirement. The matrix must expose the
+ three verification categories and place the unlinked requirement in the
+ corresponding column for each malfunction.
+
+ This is an integration test because the category assignment and table
+ structure are produced by the Jinja post-template.
+ """
+ monkeypatch.setenv("BUILD_WORKSPACE_DIRECTORY", str(tmp_path))
+ (tmp_path / "conf.py").write_text(
+ """
+extensions = ["sphinx_needs", "score_sphinx_needs_templates", "score_metamodel"]
+master_doc = "index"
+needs_id_regex = r"^[a-zA-Z0-9_]+$"
+""",
+ encoding="utf-8",
+ )
+ (tmp_path / "index.rst").write_text(
+ """
+.. doc_tool:: Shared requirement report
+ :id: doc_tool__shared_requirement_report
+ :status: evaluated
+ :security_affected: NO
+ :tool_version: v1
+ :post_template: tool_qualification_report
+
+.. gd_req:: Shared process requirement
+ :id: gd_req__shared_requirement
+
+ The process shall preserve shared qualification evidence.
+
+.. tool_req:: Shared tool requirement
+ :id: tool_req__shared_requirement
+ :satisfies: gd_req__shared_requirement
+
+ The tool shall satisfy the shared capability.
+
+.. tool_usecase:: First use case
+ :id: tool_usecase__shared_requirement_first
+ :belongs_to: doc_tool__shared_requirement_report
+
+ The tool is used to prepare the first project artifact.
+
+ .. potential_tool_malfunction:: First malfunction
+ :id: potential_tool_malfunction__shared_requirement_first
+ :violates: tool_req__shared_requirement
+ :safety_affected: YES
+ :detection_sufficient: NO
+ :safety_measures: Independent review
+
+.. tool_usecase:: Second use case
+ :id: tool_usecase__shared_requirement_second
+ :belongs_to: doc_tool__shared_requirement_report
+
+ The tool is used to prepare the second project artifact.
+
+ .. potential_tool_malfunction:: Second malfunction
+ :id: potential_tool_malfunction__shared_requirement_second
+ :violates: tool_req__shared_requirement
+ :safety_affected: YES
+ :detection_sufficient: NO
+ :safety_measures: Independent review
+""",
+ encoding="utf-8",
+ )
+
+ app = SphinxTestApp(
+ srcdir=tmp_path,
+ outdir=tmp_path / "_build",
+ buildername="html",
+ freshenv=True,
+ )
+ try:
+ app.build(force_all=True)
+ html = (app.outdir / "index.html").read_text(encoding="utf-8")
+ finally:
+ app.cleanup()
+
+ purpose_section = html.split('', maxsplit=1)[
+ 1
+ ].split('', maxsplit=1)[0]
+ assert "', maxsplit=1)[
+ 1
+ ].split('