Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c0a88fd
feat: implement schema validation for Sphinx-Needs using generated sc…
arnoox Feb 6, 2026
84351db
fix: update ID regex pattern to include uppercase letters - bazel run…
arnoox Feb 6, 2026
619fa50
Merge branch 'main' of https://github.com/arnoox/score-docs-as-code i…
arnoox Feb 6, 2026
dc99d47
refactor: clean up print statements in schema generation process
arnoox Feb 6, 2026
ae8ce7e
feat: refactored sn_schema for testabiblity; enhance schema validatio…
arnoox Feb 9, 2026
2a5cfd5
Added unit tests for sn_schemas
arnoox Feb 9, 2026
a805e48
feat: add integration tests for schema generation against SCORE metam…
arnoox Feb 9, 2026
4e09175
refactor: remove unused imports and clean up whitespace in test files
arnoox Feb 9, 2026
2b2f0f6
feat: update documentation and comments for clarity on schema generat…
arnoox Feb 9, 2026
e2ad71b
feat: enhance schema validation by refining network validation for ma…
arnoox Feb 9, 2026
8d90169
refactor: simplify optional links classification and improve test ass…
arnoox Feb 9, 2026
6b3a832
refactor: improve comment formatting for clarity in schema validation…
arnoox Feb 9, 2026
2be684f
feat: add logging for existing links and warnings for missing needs i…
arnoox Feb 11, 2026
02e25a2
feat: enhance README to clarify metamodel validation in IDE with ubCo…
arnoox Feb 11, 2026
1a065be
Merge branch 'main' into feat/generate-sn6-schema-validation
arnoox Feb 11, 2026
1d9119a
feat: refine regex patterns for version validation in needs_types
arnoox Feb 11, 2026
3c098db
Merge branch 'feat/generate-sn6-schema-validation' of https://github.…
arnoox Feb 11, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ user.bazelrc
# docs build artifacts
/_build*
docs/ubproject.toml
docs/schemas.json

# Vale - editorial style guide
.vale.ini
Expand Down
2 changes: 1 addition & 1 deletion docs/internals/requirements/requirements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,6 @@ Grouped Requirements
.. needextend:: c.this_doc() and type == 'tool_req' and not status
:status: valid

.. needextend:: "metamodel.yaml" in source_code_link
.. needextend:: source_code_link is not None and "metamodel.yaml" in source_code_link
:+satisfies: tool_req__docs_metamodel
:+tags: config
97 changes: 97 additions & 0 deletions src/extensions/score_metamodel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# score_metamodel

Sphinx extension that enforces the S-CORE metamodel on sphinx-needs documents.

It reads `metamodel.yaml` (the single source of truth for all need types, fields,
links, and constraints) and validates every need in the documentation against
those rules.

## What it does

1. **Registers need types** with sphinx-needs (directives like `feat_req`, `comp`,
`workflow`, etc.) including their fields, links, and extra options.
2. **Generates `schemas.json`** from the metamodel so that sphinx-needs 6 can
validate needs at parse time (required fields, regex patterns, link
constraints). Because ubCode (the VS Code extension for sphinx-needs)
evaluates these schemas during editing, **metamodel violations are shown
as diagnostics directly in the IDE** -- catching errors early with
lightweight, fast rendering, without needing a full Sphinx build.
3. **Runs post-build checks** that go beyond what JSON Schema can express
(graph traversals, prohibited words, ID format rules).

## Metamodel overview

`metamodel.yaml` defines:

| Section | Purpose |
|---|---|
| `needs_types` | All need types (e.g. `feat_req`, `comp`, `document`) with their mandatory/optional fields and links |
| `needs_types_base_options` | Global optional fields applied to every type (e.g. `source_code_link`, `testlink`) |
| `needs_extra_links` | Custom link types (e.g. `satisfies`, `implements`, `mitigated_by`) |
| `prohibited_words_checks` | Forbidden words in titles/descriptions (e.g. "shall", "must") |
| `graph_checks` | Cross-need constraints (e.g. safety level decomposition rules) |

Each need type can specify:

- **`mandatory_options`** -- fields that must be present, with a regex pattern
the value must match (e.g. `status: ^(valid|invalid)$`).
- **`optional_options`** -- fields that, if present, must match a pattern.
- **`mandatory_links`** -- links that must have at least one target. The value
is either a plain type name (`stkh_req`) or a regex (`^logic_arc_int__.+$`).
- **`optional_links`** -- links that are allowed but not required.

## Validation layers

### Schema validation (sphinx-needs >6)

`sn_schemas.py` translates the metamodel into a `schemas.json` file that
sphinx-needs evaluates at parse time. Each schema entry has:

- **`select`** -- matches needs by their `type` field.
- **`validate.local`** -- JSON Schema checking the need's own properties
(required fields, regex patterns on option values, mandatory links with
`minItems: 1`). Regex patterns on **link IDs** (e.g. checking that
`includes` entries match `^logic_arc_int(_op)*__.+$`) are not yet
validated here; the schema only enforces that at least one link exists.
ID-pattern checking is still done by the Python `validate_links()` in
`check_options.py`.
- **`validate.network`** -- validates that linked needs have the expected
`type` (e.g. `satisfies` targets must be `stkh_req`). Uses the
sphinx-needs `items.local` format so each linked need is checked
individually. Only **mandatory** links are checked here; optional link
type violations are left to the Python `validate_links()` check, which
treats them as informational (`treat_as_info=True`) rather than errors.
Fields that mix regex and plain targets (e.g.
`complies: std_wp, ^std_req__aspice_40__iic.*$`) are also excluded
because the `items` schema would incorrectly require all linked needs
to match the plain type.

### Post-build S-Core metamodel checks

Checks in `checks/` run after the Sphinx build and cover rules that
JSON Schema cannot express:

| Check | File | What it validates |
|---|---|---|
| `check_options` | `check_options.py` | Mandatory/optional field presence and patterns (legacy, overlaps with schema validation) |
| `check_extra_options` | `check_options.py` | Warns about fields not defined in the metamodel |
| `check_id_format` | `attributes_format.py` | ID structure (`<type>__<abbrev>__<element>`, part count) |
| `check_for_prohibited_words` | `attributes_format.py` | Forbidden words in titles |
| `check_metamodel_graph` | `graph_checks.py` | Cross-need constraints (e.g. ASIL_B needs must link to non-QM requirements) |
| `check_id_contains_feature` | `id_contains_feature.py` | Need IDs must contain the feature abbreviation from the file path |
| `check_standards` | `standards.py` | Standard compliance link validation |

## File layout

```
score_metamodel/
__init__.py # Sphinx extension entry point (setup, check orchestration)
metamodel.yaml # The S-CORE metamodel definition
metamodel_types.py # Type definitions (ScoreNeedType, etc.)
yaml_parser.py # Parses metamodel.yaml into MetaModelData
sn_schemas.py # Generates schemas.json for sphinx-needs 6
log.py # CheckLogger for structured warning output
external_needs.py # External needs integration
checks/ # Post-build validation checks
tests/ # Unit and integration tests
```
27 changes: 26 additions & 1 deletion src/extensions/score_metamodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import pkgutil
from collections.abc import Callable
from pathlib import Path
from typing import Any

from sphinx.application import Sphinx
from sphinx_needs import logging
Expand All @@ -31,6 +32,7 @@
from src.extensions.score_metamodel.metamodel_types import (
ScoreNeedType as ScoreNeedType,
)
from src.extensions.score_metamodel.sn_schemas import write_sn_schemas
from src.extensions.score_metamodel.yaml_parser import (
default_options as default_options,
)
Expand Down Expand Up @@ -237,10 +239,28 @@ def setup(app: Sphinx) -> dict[str, str | bool]:
# load metamodel.yaml via ruamel.yaml
metamodel = load_metamodel_data()

# Sphinx-Needs 6 requires extra options as dicts: {"name": ..., "schema": ...}
# Options WITH a schema get JSON schema validation (value must be a string).
# Options WITHOUT a schema are registered but not validated.
# non_schema_options = {"source_code_link", "testlink", "codelink"}
non_schema_options = {} # currently empty → all options get schema validation
extra_options_schema: list[dict[str, Any]] = [
{"name": opt, "schema": {"type": "string"}}
for opt in metamodel.needs_extra_options
if opt not in non_schema_options
]
extra_options_wo_schema: list[dict[str, Any]] = [
{"name": opt}
for opt in metamodel.needs_extra_options
if opt in non_schema_options
]
# extra_options = [{"name": opt} for opt in metamodel.needs_extra_options]
extra_options = extra_options_schema + extra_options_wo_schema

# Assign everything to Sphinx config
app.config.needs_types = metamodel.needs_types
app.config.needs_extra_links = metamodel.needs_extra_links
app.config.needs_extra_options = metamodel.needs_extra_options
app.config.needs_extra_options = extra_options
app.config.graph_checks = metamodel.needs_graph_check
app.config.prohibited_words_checks = metamodel.prohibited_words_checks

Expand All @@ -251,6 +271,11 @@ def setup(app: Sphinx) -> dict[str, str | bool]:
app.config.needs_reproducible_json = True
app.config.needs_json_remove_defaults = True

# Generate schemas.json from the metamodel and register it with sphinx-needs.
# This enables sphinx-needs 6 schema validation: required fields, regex
# patterns on option values, and (eventually) link target type checks.
write_sn_schemas(app, metamodel)

# sphinx-collections runs on default prio 500.
# We need to populate the sphinx-collections config before that happens.
# --> 499
Expand Down
6 changes: 6 additions & 0 deletions src/extensions/score_metamodel/metamodel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,12 @@ needs_extra_links:
partially_verifies:
incoming: partially_verified_by
outgoing: partially_verifies

# Decision Records
affects:
incoming: affected by
outgoing: affects

##############################################################
# Graph Checks
# The graph checks focus on the relation of the needs and their attributes.
Expand Down
Loading
Loading