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('
', maxsplit=1)[0] + assert "Violates" not in evaluation_section + assert "Safety affected" in evaluation_section + assert "SCORE TCL" in evaluation_section + assert "Capability being evaluated." in evaluation_section + assert "table.tool-qualification-report tbody a" in evaluation_section + assert "LOW" in evaluation_section + assert "First use case (" not in evaluation_section + assert "First malfunction (" not in evaluation_section + assert '
First use case
' in evaluation_section + assert ( + '" in evaluation_section + ) + assert 'href="#tool_usecase__shared_requirement_first"' in evaluation_section + assert ( + 'href="#potential_tool_malfunction__shared_requirement_first"' + in evaluation_section + ) + + matrix_section = html.split('
', maxsplit=1)[ + 1 + ] + assert "Fully verified tool requirements" in matrix_section + assert "Partially verified tool requirements" in matrix_section + assert "Unverified tool requirements" in matrix_section + assert "Tool requirements (with testlinks)" not in matrix_section + assert "Tool requirements (without testlinks)" not in matrix_section + assert matrix_section.index("First use case") < matrix_section.index( + "First malfunction" + ) + assert ( + matrix_section.count( + 'tool_req__shared_requirement' + ) + == 2 + ) + assert '
' not in html + assert '
' not in html diff --git a/src/needs_templates/tool_qualification_report.need b/src/needs_templates/tool_qualification_report.need new file mode 100644 index 000000000..4d6b275d6 --- /dev/null +++ b/src/needs_templates/tool_qualification_report.need @@ -0,0 +1,356 @@ +{# + score: render-after-needs-collection + + Reusable post-template for a ``doc_tool`` qualification report. +#} +{% set usecases = linked_needs(id, "belongs_to_back")|selectattr("type", "equalto", "tool_usecase")|list %} +{% set malfunction_scope = namespace(ids=[]) %} +{% set qualification_requirement_scope = namespace(ids=[]) %} +{% for usecase in usecases %} +{% for malfunction in linked_needs(usecase["id"], "parent_needs_back") %} +{% if malfunction["id"] not in malfunction_scope.ids %} +{% set malfunction_scope.ids = malfunction_scope.ids + [malfunction["id"]] %} +{% endif %} +{% if malfunction.get("safety_affected") == "YES" and malfunction.get("detection_sufficient") == "NO" %} +{% for requirement in linked_needs(malfunction["id"], "violates")|selectattr("type", "equalto", "tool_req")|list %} +{% if requirement["id"] not in qualification_requirement_scope.ids %} +{% set qualification_requirement_scope.ids = qualification_requirement_scope.ids + [requirement["id"]] %} +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} +{% endfor %} + +{% macro text(value, fallback="—") -%} +{{ (value|default("", true)|replace("\n", " ")|trim) or fallback }} +{%- endmacro %} + +{% macro mermaid_text(value, fallback="—") -%} +{{ ((value|default("", true)|replace("\n", " ")|trim) or fallback)|replace("\\", "\\\\")|replace('"', '\\"') }} +{%- endmacro %} + +{% macro id_filter_list(ids) -%} +[{% for need_id in ids %}"{{ need_id }}"{% if not loop.last %}, {% endif %}{% endfor %}] +{%- endmacro %} + +{% set usecase_malfunction_scope = namespace(with_malfunctions=[], without_malfunctions=[]) %} +{% for usecase in usecases %} +{% if linked_needs(usecase["id"], "parent_needs_back")|list %} +{% set usecase_malfunction_scope.with_malfunctions = usecase_malfunction_scope.with_malfunctions + [usecase["id"]] %} +{% else %} +{% set usecase_malfunction_scope.without_malfunctions = usecase_malfunction_scope.without_malfunctions + [usecase["id"]] %} +{% endif %} +{% endfor %} + +{% macro need_reference(need) -%} +{% if need.get("external_url") %}`{{ need["id"] }} <{{ need["external_url"] }}>`_{% else %}`{{ need["id"] }} <#{{ need["id"] }}>`_{% endif %} +{%- endmacro %} + +{% macro need_title_reference(need) -%} +{% if need.get("external_url") %}`{{ text(need.get("title")) }} <{{ need["external_url"] }}>`_{% else %}:need:`{{ text(need.get("title")) }} <{{ need["id"] }}>`{% endif %} +{%- endmacro %} + +{% macro need_content(need) -%} +{{ text(need.get("content", "").split("\n\n.. potential_tool_malfunction::", 1)[0]) }} +{%- endmacro %} + +{% macro need_list(needs) -%} +{%- if needs -%} +{%- for need in needs -%} +{{ "| " if loop.first else "\n | " }}{{ need_reference(need) }}{{ "," if not loop.last else "" }} +{%- endfor -%} +{%- else -%}—{%- endif -%} +{%- endmacro %} + +{% macro requirements_for(malfunction, verification) -%} +{%- set matching = namespace(items=[]) -%} +{%- for requirement in linked_needs(malfunction["id"], "violates")|selectattr("type", "equalto", "tool_req")|list -%} +{%- set fully_verifying_tests = linked_needs(requirement["id"], "fully_verifies_back")|list -%} +{%- set partially_verifying_tests = linked_needs(requirement["id"], "partially_verifies_back")|list -%} +{%- if (verification == "fully" and fully_verifying_tests) + or (verification == "partially" and partially_verifying_tests and not fully_verifying_tests) + or (verification == "unlinked" and not fully_verifying_tests and not partially_verifying_tests) -%} +{%- set matching.items = matching.items + [requirement] -%} +{%- endif -%} +{%- endfor -%} +{{- need_list(matching.items) -}} +{%- endmacro %} + +{% set summary = namespace(total=0, safety=0, detection_yes=0, detection_no=0, detection_unassessed=0, low=false, fully_covered=0) %} +{% for usecase in usecases %} +{% for malfunction in linked_needs(usecase["id"], "parent_needs_back") %} +{% set summary.total = summary.total + 1 %} +{% if malfunction.get("safety_affected") == "YES" %} +{% set summary.safety = summary.safety + 1 %} +{% if malfunction.get("detection_sufficient") == "YES" %} +{% set summary.detection_yes = summary.detection_yes + 1 %} +{% elif malfunction.get("detection_sufficient") == "NO" %} +{% set summary.detection_no = summary.detection_no + 1 %} +{% set summary.low = true %} +{% set coverage = namespace(total=0, passed=0) %} +{% for requirement in linked_needs(malfunction["id"], "violates")|selectattr("type", "equalto", "tool_req")|list %} +{% set coverage.total = coverage.total + 1 %} +{% if linked_needs(requirement["id"], "fully_verifies_back")|selectattr("result", "equalto", "passed")|list %} +{% set coverage.passed = coverage.passed + 1 %} +{% endif %} +{% endfor %} +{% if coverage.total > 0 and coverage.passed == coverage.total %} +{% set summary.fully_covered = summary.fully_covered + 1 %} +{% endif %} +{% else %} +{% set summary.detection_unassessed = summary.detection_unassessed + 1 %} +{% endif %} +{% endif %} +{% endfor %} +{% endfor %} + +Purpose and intended use +^^^^^^^^^^^^^^^^^^^^^^^^ + +*Generated from linked report records.* + +The tool is evaluated for the following intended use cases. The use case title +is the concise statement of the project context in which the tool is used; the +detailed evaluation is shown in the sections below. + +{% for usecase in usecases %} +* {{ need_title_reference(usecase) }}: {{ need_content(usecase) }} +{% endfor %} +{% if not usecases %} +* No tool use case is documented. +{% endif %} + +Conclusion +^^^^^^^^^^ + +*Generated from linked report records.* + +The generated conclusion is summarized from the owned +``potential_tool_malfunction`` records: + +* **Safety:** ``{{ summary.safety }}/{{ summary.total }}`` potential tool + malfunctions affect safety → ``safety_affected: {{ "YES" if summary.safety else "NO" }}``. +* **Detection:** ``{{ summary.detection_no }}/{{ summary.safety }}`` safety- + relevant malfunctions have insufficient detection → ``tcl: {{ "LOW" if summary.detection_no else "HIGH" }}``. +* **Qualification:** → qualification of ``{{ qualification_requirement_scope.ids|length }}`` tool + requirements is **{{ "required" if summary.detection_no else "not required" }}**. +* **Testcase coverage:** ``{{ summary.fully_covered }}/{{ summary.detection_no }}`` + qualification-relevant malfunctions are fully covered by passed, + fully-verifying testcases. + + +.. grid:: 1 2 2 2 + :gutter: 3 + + .. grid-item:: + + .. needpie:: Use case malfunction coverage + :labels: Has malfunctions, No malfunctions + :colors: #4E79A7, #BDBDBD + :legend: + + type == "tool_usecase" and id in {{ id_filter_list(usecase_malfunction_scope.with_malfunctions) }} + type == "tool_usecase" and id in {{ id_filter_list(usecase_malfunction_scope.without_malfunctions) }} + + .. grid-item:: + + .. needpie:: Safety-affected malfunctions + :labels: Safety affected, Not safety affected + :colors: #4E79A7, #BDBDBD + :legend: + + type == "potential_tool_malfunction" and id in {{ id_filter_list(malfunction_scope.ids) }} and safety_affected == "YES" + type == "potential_tool_malfunction" and id in {{ id_filter_list(malfunction_scope.ids) }} and safety_affected == "NO" + + .. grid-item:: + + .. needpie:: Detection status + :labels: Sufficient, Insufficient, Not assessed + :colors: #4E79A7, #E15759, #BDBDBD + :legend: + + type == "potential_tool_malfunction" and id in {{ id_filter_list(malfunction_scope.ids) }} and safety_affected == "YES" and detection_sufficient == "YES" + type == "potential_tool_malfunction" and id in {{ id_filter_list(malfunction_scope.ids) }} and safety_affected == "YES" and detection_sufficient == "NO" + type == "potential_tool_malfunction" and id in {{ id_filter_list(malfunction_scope.ids) }} and safety_affected == "YES" and not detection_sufficient + + .. grid-item:: + + .. needpie:: Qualification scope + :labels: tool requirements in scope, tool requirements out of scope + :colors: #4E79A7, #BDBDBD + :legend: + + type == "tool_req" and id in {{ id_filter_list(qualification_requirement_scope.ids) }} + type == "tool_req" and not (id in {{ id_filter_list(qualification_requirement_scope.ids) }}) + + .. grid-item:: + + .. needpie:: Qualification evidence + :labels: Fully verified, Partially verified, Not verified + :colors: #4E79A7, #F28E2B, #E15759 + :legend: + + type == "tool_req" and id in {{ id_filter_list(qualification_requirement_scope.ids) }} and fully_verifies_back + type == "tool_req" and id in {{ id_filter_list(qualification_requirement_scope.ids) }} and partially_verifies_back and not fully_verifies_back + type == "tool_req" and id in {{ id_filter_list(qualification_requirement_scope.ids) }} and not fully_verifies_back and not partially_verifies_back + +Tool verification graph +^^^^^^^^^^^^^^^^^^^^^^^ + +*Generated from linked report records.* + +The box labels show the Need type. Colors distinguish the report, tool +requirements, use cases, and potential malfunctions. + +.. mermaid:: + + --- + config: + layout: elk + securityLevel: loose + --- + flowchart LR + classDef report fill:#D5E8D4,stroke:#82B366,color:#000 + classDef requirement fill:#DAE8FC,stroke:#6C8EBF,color:#000 + classDef usecase fill:#E1D5E7,stroke:#9673A6,color:#000 + classDef malfunction fill:#F8CECC,stroke:#B85450,color:#000 + + report["Tool Verification Report (doc_tool)"] + requirements["Tool requirements (tool_req)"] + class report report + class requirements requirement + click report href "#{{ id }}" + click requirements href "#tool-qualification-matrix" +{% for usecase in usecases %} +{% set usecase_alias = "usecase_" ~ loop.index %} + {{ usecase_alias }}["{{ mermaid_text(usecase.get("title")) }} (tool_usecase)"] + class {{ usecase_alias }} usecase + report --> {{ usecase_alias }} + click {{ usecase_alias }} href "#{{ usecase["id"] }}" +{% for malfunction in linked_needs(usecase["id"], "parent_needs_back") %} +{% set malfunction_alias = usecase_alias ~ "_malfunction_" ~ loop.index %} + {{ malfunction_alias }}["{{ mermaid_text(malfunction.get("title")) }} (potential_tool_malfunction)"] + class {{ malfunction_alias }} malfunction + {{ usecase_alias }} --> {{ malfunction_alias }} + click {{ malfunction_alias }} href "#{{ malfunction["id"] }}" +{% if malfunction.get("safety_affected") == "YES" and malfunction.get("detection_sufficient") == "NO" %} + {{ malfunction_alias }} -->|violates| requirements +{% endif %} +{% endfor %} +{% endfor %} +{% if not usecases %} + no_usecases["No tool use cases (tool_usecase)"] + class no_usecases usecase + report --> no_usecases +{% endif %} + +Evaluation overview +^^^^^^^^^^^^^^^^^^^ + +*Generated from linked report records.* + +.. raw:: html + + + +.. list-table:: Evaluation overview + :header-rows: 1 + :class: evaluation-overview tool-qualification-report + :widths: 20 25 12 20 13 10 + + * - **Use case** + *Capability being evaluated.* + - **Potential malfunction** + *Failure mode if the capability is wrong.* + - **Safety affected** + *Can this affect safety?* + - **Safety measures** + *Measures used during normal operation.* + - **Detection sufficient** + *Detected before the output is relied on?* + - **SCORE TCL** + *Per-malfunction classification.* +{% for usecase in usecases %} +{% for malfunction in linked_needs(usecase["id"], "parent_needs_back") %} + * - | {{ text(usecase.get("title")) }} + | {{ need_reference(usecase) }} + - | {{ text(malfunction.get("title")) }} + | {{ need_reference(malfunction) }} + - {{ text(malfunction.get("safety_affected")) }} + - {{ text(malfunction.get("safety_measures")) }} + - {{ text(malfunction.get("detection_sufficient")) }} + - {{ "LOW" if malfunction.get("safety_affected") == "YES" and malfunction.get("detection_sufficient") == "NO" else "HIGH" }} +{% endfor %} +{% endfor %} +{% if not usecases %} + * - No tool use cases. + - — + - — + - — + - — + - — +{% endif %} + +Tool qualification matrix +^^^^^^^^^^^^^^^^^^^^^^^^^ + +*Generated from linked report records.* + +The qualification matrix contains only safety-relevant malfunctions whose +detection is insufficient. Every such malfunction must violate at least one +``tool_req``. Requirements are grouped by their strongest available testcase +link: full verification, partial verification without full verification, or no +verification link. + +.. list-table:: Tool qualification matrix + :header-rows: 1 + :class: tool-qualification-report + :widths: 20 25 18 18 19 + + * - Use case + - Potential malfunction + - Fully verified tool requirements + - Partially verified tool requirements + - Unverified tool requirements +{% for usecase in usecases %} +{% set malfunctions = linked_needs(usecase["id"], "parent_needs_back")|selectattr("safety_affected", "equalto", "YES")|selectattr("detection_sufficient", "equalto", "NO")|list %} +{% for malfunction in malfunctions %} + * - | {{ text(usecase.get("title")) }} + | {{ need_reference(usecase) }} + - | {{ text(malfunction.get("title")) }} + | {{ need_reference(malfunction) }} + - {{ requirements_for(malfunction, "fully") }} + - {{ requirements_for(malfunction, "partially") }} + - {{ requirements_for(malfunction, "unlinked") }} +{% endfor %} +{% endfor %} +{% if not summary.low %} + * - No qualification-relevant malfunction. + - — + - — + - — + - — +{% endif %} diff --git a/src/tests/docs_bzl/scenarios/basic_docs/_expected/needs_json/needs.json b/src/tests/docs_bzl/scenarios/basic_docs/_expected/needs_json/needs.json index ecdce6758..21da9f994 100644 --- a/src/tests/docs_bzl/scenarios/basic_docs/_expected/needs_json/needs.json +++ b/src/tests/docs_bzl/scenarios/basic_docs/_expected/needs_json/needs.json @@ -347,6 +347,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -928,6 +937,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1054,6 +1081,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/data_bundle_needs/needs.json b/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/data_bundle_needs/needs.json index 14e5dc98e..bdf1ac9f7 100644 --- a/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/data_bundle_needs/needs.json +++ b/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/data_bundle_needs/needs.json @@ -347,6 +347,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -928,6 +937,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1054,6 +1081,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/isolated_source_bundle_needs/needs.json b/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/isolated_source_bundle_needs/needs.json index a52093111..67de60841 100644 --- a/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/isolated_source_bundle_needs/needs.json +++ b/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/isolated_source_bundle_needs/needs.json @@ -347,6 +347,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -928,6 +937,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1054,6 +1081,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/needs_json/needs.json b/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/needs_json/needs.json index 4574b9de2..18d2634ed 100644 --- a/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/needs_json/needs.json +++ b/src/tests/docs_bzl/scenarios/data_files_runfiles/_expected/needs_json/needs.json @@ -347,6 +347,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -928,6 +937,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1054,6 +1081,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/nested_bundles/_expected/needs_json/needs.json b/src/tests/docs_bzl/scenarios/nested_bundles/_expected/needs_json/needs.json index de0bc517d..c208399a2 100644 --- a/src/tests/docs_bzl/scenarios/nested_bundles/_expected/needs_json/needs.json +++ b/src/tests/docs_bzl/scenarios/nested_bundles/_expected/needs_json/needs.json @@ -385,6 +385,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -966,6 +975,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1092,6 +1119,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/reference_integration/_expected/needs_local.json b/src/tests/docs_bzl/scenarios/reference_integration/_expected/needs_local.json index 02721d195..c6d85ba90 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/_expected/needs_local.json +++ b/src/tests/docs_bzl/scenarios/reference_integration/_expected/needs_local.json @@ -347,6 +347,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -928,6 +937,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1054,6 +1081,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/_expected/needs_json/needs.json b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/_expected/needs_json/needs.json index c8d98514a..2e74540e7 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/_expected/needs_json/needs.json +++ b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/_expected/needs_json/needs.json @@ -387,6 +387,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -968,6 +977,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1094,6 +1121,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/_expected/needs_local.json b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/_expected/needs_local.json index 05663e04b..a8f5fe131 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/_expected/needs_local.json +++ b/src/tests/docs_bzl/scenarios/reference_integration/legacy_module/docs/components/component/_expected/needs_local.json @@ -387,6 +387,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -968,6 +977,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1094,6 +1121,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/reference_integration/modern_module/_expected/needs_json/needs.json b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/_expected/needs_json/needs.json index 987c3946a..e4e68c11f 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/modern_module/_expected/needs_json/needs.json +++ b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/_expected/needs_json/needs.json @@ -476,6 +476,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -1057,6 +1066,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1183,6 +1210,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/unlinked_component/_expected/needs_local.json b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/unlinked_component/_expected/needs_local.json index f681703b6..d337f1858 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/unlinked_component/_expected/needs_local.json +++ b/src/tests/docs_bzl/scenarios/reference_integration/modern_module/docs/components/unlinked_component/_expected/needs_local.json @@ -384,6 +384,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -965,6 +974,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1091,6 +1118,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/reference_integration/score_platform/_expected/needs_json/needs.json b/src/tests/docs_bzl/scenarios/reference_integration/score_platform/_expected/needs_json/needs.json index c2d08a44c..959e0d7b6 100644 --- a/src/tests/docs_bzl/scenarios/reference_integration/score_platform/_expected/needs_json/needs.json +++ b/src/tests/docs_bzl/scenarios/reference_integration/score_platform/_expected/needs_json/needs.json @@ -391,6 +391,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -972,6 +981,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1098,6 +1125,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/subdirectory_bundle/consumer/_expected/needs_json/needs.json b/src/tests/docs_bzl/scenarios/subdirectory_bundle/consumer/_expected/needs_json/needs.json index 7aa97354f..80d88c51f 100644 --- a/src/tests/docs_bzl/scenarios/subdirectory_bundle/consumer/_expected/needs_json/needs.json +++ b/src/tests/docs_bzl/scenarios/subdirectory_bundle/consumer/_expected/needs_json/needs.json @@ -393,6 +393,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -974,6 +983,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1100,6 +1127,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config", diff --git a/src/tests/docs_bzl/scenarios/subdirectory_bundle/producer/_expected/needs_json/needs.json b/src/tests/docs_bzl/scenarios/subdirectory_bundle/producer/_expected/needs_json/needs.json index f3800a69e..478f9cb58 100644 --- a/src/tests/docs_bzl/scenarios/subdirectory_bundle/producer/_expected/needs_json/needs.json +++ b/src/tests/docs_bzl/scenarios/subdirectory_bundle/producer/_expected/needs_json/needs.json @@ -378,6 +378,15 @@ }, "type": "array" }, + "detection_sufficient": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "docname": { "default": null, "description": "Name of the document where the need is defined (None if external).", @@ -959,6 +968,24 @@ "null" ] }, + "realized_by": { + "default": [], + "description": "Link field", + "field_type": "links", + "items": { + "type": "string" + }, + "type": "array" + }, + "realized_by_back": { + "default": [], + "description": "Backlink field", + "field_type": "backlinks", + "items": { + "type": "string" + }, + "type": "array" + }, "realizes": { "default": [], "description": "Link field", @@ -1085,6 +1112,15 @@ "null" ] }, + "safety_measures": { + "default": "", + "description": "Added by needs_fields config", + "field_type": "extra", + "type": [ + "string", + "null" + ] + }, "safety_relevant": { "default": "", "description": "Added by needs_fields config",