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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/internals/requirements/requirements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,22 @@ Testing
* link the inspected artifacts via ``inspects``
* allow links to backing evidence via ``evidence``

.. tool_req:: Support machine-readable inspection reports
:id: tool_req__docs_inspection_report_need
:tags: Verification Evidence
:implemented: YES
:version: 1
:satisfies: gd_req__verification_checks
:parent_covered: NO: process wording defines inspection completeness, while the tool models an explicit expected inspection set.

Docs-as-Code shall support a machine-readable module inspection report need type.

The need type shall:

* aggregate inspection records via ``contains`` and belong to the module via ``belongs_to``
* declare the required checklist categories via ``expected_inspections``
* be gated so every expected inspection type has a contained, approved inspection record

πŸ§ͺ Tool Verification Reports
############################

Expand Down
52 changes: 52 additions & 0 deletions src/extensions/score_metamodel/checks/graph_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,58 @@ def check_metamodel_graph(
log.warning_for_need(need, msg)


@graph_check
def check_inspection_report_completeness(
app: Sphinx,
all_needs: NeedsView,
log: CheckLogger,
):
needs_dict_all = {need["id"]: need for need in all_needs.values()}
for report in all_needs.filter_is_external(False).values():
if report.get("type") != "mod_insp_report":
continue

expected_inspections = report.get("expected_inspections")
if (
not isinstance(expected_inspections, str)
or not expected_inspections.strip()
):
continue

expected = [
inspection_type.strip()
for inspection_type in expected_inspections.split(",")
if inspection_type.strip()
]
raw_contains = report.get("contains", [])
if isinstance(raw_contains, str):
contains_ids = [raw_contains]
elif isinstance(raw_contains, list | tuple | set):
contains_ids = [str(contained_id) for contained_id in raw_contains]
else:
contains_ids = []

covered = {
contained.get("inspection_type")
for contained_id in contains_ids
if (contained := needs_dict_all.get(contained_id)) is not None
and contained.get("type") == "mod_insp"
and contained.get("inspection_state") == "approved"
and contained.get("status") == "valid"
}
missing = [
inspection_type
for inspection_type in expected
if inspection_type not in covered
]
if missing:
log.warning_for_need(
report,
"Inspection report is missing approved inspection(s) for: "
+ ", ".join(missing),
)


@graph_check
def check_valid_only_links_to_valid(
app: Sphinx,
Expand Down
26 changes: 26 additions & 0 deletions src/extensions/score_metamodel/metamodel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,32 @@ needs_types:
- inspection
parts: 3

# Aggregates a module's inspection records and gates inspection completeness.
# req-Id: tool_req__docs_inspection_report_need
mod_insp_report:
title: Module Inspection Report
prefix: mod_ispr__
mandatory_options:
# req-Id: tool_req__docs_common_attr_safety
safety: ^(QM|ASIL_B)$
# req-Id: tool_req__docs_common_attr_security
security: ^(YES|NO)$
# req-Id: tool_req__docs_common_attr_status
status: ^(valid|invalid)$
# req-Id: tool_req__docs_inspection_report_need
# comma-separated subset of the three checklist categories the module must cover
expected_inspections: ^(requirements|architecture|implementation)(,(requirements|architecture|implementation))*$
mandatory_links:
# req-Id: tool_req__docs_inspection_report_need
belongs_to: mod
contains: mod_insp
optional_links:
# req-Id: tool_req__docs_inspection_report_need
evidence: ANY
tags:
- inspection
parts: 3

# https://eclipse-score.github.io/process_description/main/permalink.html?id=gd_temp__change_decision_record
dec_rec:
title: Decision Record
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
..
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

.. mod:: Inspection Report Graph Module
:id: mod__inspection_report_graph_module
:security: YES
:safety: ASIL_B
:status: valid

.. comp_req:: Inspection Report Graph Requirement
:id: comp_req__inspection_report_graph__sample
:reqtype: Functional
:security: YES
:safety: ASIL_B
:status: valid

.. mod_insp:: Approved Requirements Inspection
:id: mod_insp__inspection_report_graph__requirements
:safety: ASIL_B
:security: YES
:status: valid
:inspection_type: requirements
:inspection_state: approved
:checklist_ref: gd_chklst__req_inspection
:reviewers: reviewer_a
:belongs_to: mod__inspection_report_graph_module
:inspects: comp_req__inspection_report_graph__sample

#EXPECT[+2]: Inspection report is missing approved inspection(s) for: architecture

.. mod_insp_report:: Incomplete Inspection Report
:id: mod_ispr__inspection_report_graph__incomplete
:safety: ASIL_B
:security: YES
:status: valid
:expected_inspections: requirements,architecture
:belongs_to: mod__inspection_report_graph_module
:contains: mod_insp__inspection_report_graph__requirements

#EXPECT-NOT[+2]: Inspection report is missing approved inspection(s)

.. mod_insp_report:: Complete Inspection Report
:id: mod_ispr__inspection_report_graph__complete
:safety: ASIL_B
:security: YES
:status: valid
:expected_inspections: requirements
:belongs_to: mod__inspection_report_graph_module
:contains: mod_insp__inspection_report_graph__requirements
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
..
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

#CHECK: check_options

.. mod:: Inspection Report Module
:id: mod__inspection_report_module
:security: YES
:safety: ASIL_B
:status: valid

.. comp_req:: Inspection Report Requirement
:id: comp_req__inspection_report__sample
:reqtype: Functional
:security: YES
:safety: ASIL_B
:status: valid

.. mod_insp:: Inspection Report Record
:id: mod_insp__inspection_report__requirements
:safety: ASIL_B
:security: YES
:status: valid
:inspection_type: requirements
:inspection_state: approved
:checklist_ref: gd_chklst__req_inspection
:reviewers: reviewer_a
:belongs_to: mod__inspection_report_module
:inspects: comp_req__inspection_report__sample

#EXPECT-NOT[+2]: Inspection report is missing approved inspection(s)

.. mod_insp_report:: Inspection Report Valid
:id: mod_ispr__inspection_report__valid
:safety: ASIL_B
:security: YES
:status: valid
:expected_inspections: requirements
:belongs_to: mod__inspection_report_module
:contains: mod_insp__inspection_report__requirements

# Invalid expected_inspections value
#EXPECT[+2]: mod_ispr__inspection_report__invalid.expected_inspections (requirements,security): does not follow pattern

.. mod_insp_report:: Inspection Report Invalid Expected Inspections
:id: mod_ispr__inspection_report__invalid
:safety: ASIL_B
:security: YES
:status: invalid
:expected_inspections: requirements,security
:belongs_to: mod__inspection_report_module
:contains: mod_insp__inspection_report__requirements
70 changes: 67 additions & 3 deletions src/extensions/score_metamodel/tests/test_graph_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,26 @@

from __future__ import annotations

from typing import Any
from typing import cast

import pytest

# Adjust this import path if your project layout differs.
import score_metamodel.checks.graph_checks as graph_checks
from score_metamodel.tests import fake_check_logger, need as test_need
from sphinx_needs.config import NeedType
from sphinx_needs.data import NeedsView
from sphinx_needs.need_item import NeedItem


class DummyNeedsView:
"""Minimal NeedsView-like test double."""

def __init__(self, needs: list[dict[str, Any]]) -> None:
def __init__(self, needs: list[NeedItem]) -> None:
"""Create a view over needs represented as dict-like structures."""
self._needs = needs

def values(self) -> list[dict[str, Any]]:
def values(self) -> list[NeedItem]:
"""Return all needs."""
return self._needs

Expand Down Expand Up @@ -180,3 +182,65 @@ def test_filter_needs_by_criteria_unknown_type_logs_warning() -> None:
log.assert_warning(
"Unknown need type `unknown` in graph check.", expect_location=False
)


def test_check_inspection_report_completeness_warns_for_missing_approved_type() -> None:
"""Warn when an expected inspection type is not approved and valid."""
report = test_need(
id="mod_ispr__incomplete",
type="mod_insp_report",
expected_inspections="requirements,architecture",
contains=["mod_insp__requirements"],
)
inspection = test_need(
id="mod_insp__requirements",
type="mod_insp",
inspection_type="requirements",
inspection_state="approved",
status="valid",
)
log = fake_check_logger()

graph_checks.check_inspection_report_completeness(
None, # type: ignore[arg-type]
cast(NeedsView, DummyNeedsView([report, inspection])),
log,
)

log.assert_warning("missing approved inspection(s) for: architecture")


def test_check_inspection_report_completeness_accepts_complete_report() -> None:
"""Do not warn when all expected inspection types are approved and valid."""
report = test_need(
id="mod_ispr__complete",
type="mod_insp_report",
expected_inspections="requirements,architecture",
contains=["mod_insp__requirements", "mod_insp__architecture"],
)
requirements_inspection = test_need(
id="mod_insp__requirements",
type="mod_insp",
inspection_type="requirements",
inspection_state="approved",
status="valid",
)
architecture_inspection = test_need(
id="mod_insp__architecture",
type="mod_insp",
inspection_type="architecture",
inspection_state="approved",
status="valid",
)
log = fake_check_logger()

graph_checks.check_inspection_report_completeness(
None, # type: ignore[arg-type]
cast(
NeedsView,
DummyNeedsView([report, requirements_inspection, architecture_inspection]),
),
log,
)

log.assert_no_warnings()
Loading