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.
- Registers need types with sphinx-needs (directives like
feat_req,comp,workflow, etc.) including their fields, links, and extra options. - Generates
schemas.jsonfrom 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. - Runs post-build checks that go beyond what JSON Schema can express (graph traversals, prohibited words, ID format rules).
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.
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 theirtypefield.validate.local-- JSON Schema checking the need's own properties (required fields, regex patterns on option values, mandatory links withminItems: 1). Regex patterns on link IDs (e.g. checking thatincludesentries 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 Pythonvalidate_links()incheck_options.py.validate.network-- validates that linked needs have the expectedtype(e.g.satisfiestargets must bestkh_req). Uses the sphinx-needsitems.localformat so each linked need is checked individually. Only mandatory links are checked here; optional link type violations are left to the Pythonvalidate_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 theitemsschema would incorrectly require all linked needs to match the plain type.
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 |
Schema column: yes = implemented, feasible = could be added, -- = not possible.
| Rule | Schema (sn_schemas.py + sphinx-needs) |
S-Core metamodel (checks/) |
Notes |
|---|---|---|---|
| ID required | yes | -- | needs_id_required (sphinx-needs built-in) |
| ID basic regex | yes | -- | needs_id_regex (sphinx-needs built-in) |
| Dead link detection | yes | -- | allow_dead_links (sphinx-needs built-in) |
| Mandatory field presence | yes | yes | Both enforce required |
| Mandatory field regex | yes | yes | Same pattern from metamodel |
| Optional field regex | yes | yes | Schema: only if field present |
| Mandatory link presence | yes | yes | Schema: minItems: 1 in local |
| Mandatory link target type | yes | yes | Schema: validate.network |
| Mandatory link ID regex | feasible | yes | Can add items.pattern in local; TODO in code |
| Optional link target type | feasible | yes (info) | Split into separate schema with severity: "info" |
| Optional link ID regex | feasible | yes (info) | Same split-severity approach |
| Mixed regex+plain link type | -- | yes | ValidateSchemaType has no anyOf/oneOf |
| ID structure (parts count) | feasible | yes | Per-type pattern from parts field; cannot check file-path part |
| Prohibited words | feasible | yes | Negative lookahead regex on title; less precise than Python |
| Graph constraints | -- | yes | Cross-need traversals beyond JSON Schema |
| Undefined extra options | -- | yes | unevaluatedProperties would reject sphinx-needs internal fields |
ID required --
Every need directive must have a manually set ID (e.g. .. feat_req:: feat_req__my_feature__001).
Enforced by sphinx-needs' needs_id_required = True in __init__.py.
ID basic regex --
The ID must match ^[A-Za-z0-9_-]{6,} (at least 6 alphanumeric/underscore/hyphen characters).
Enforced by sphinx-needs' needs_id_regex in __init__.py. The build stops if a need
has an invalid ID.
Dead link detection --
A link like satisfies: nonexistent_need_id that points to a need that does not exist
triggers a sphinx-needs warning. Controlled per link type via allow_dead_links in
needs_extra_links.
Mandatory field presence --
A feat_req must have a status field. If it is missing, both the schema
("required": ["status"]) and the Python check flag it.
Mandatory field regex --
The status field on feat_req must match ^(valid|invalid)$. Both the schema
("pattern": "^(valid|invalid)$") and the Python check validate this. Writing
status: approved is rejected.
Optional field regex --
document has optional_options: { author: ^.*$ }. If author is present, it must
match the pattern. If absent, no error. The schema includes it in properties but
not in required.
Mandatory link presence --
feat_req has mandatory_links: { satisfies: stkh_req }. At least one target must
be provided. The schema enforces this with "satisfies": {"type": "array", "minItems": 1}
in validate.local.
Mandatory link target type --
feat_req.satisfies must point to a need of type stkh_req. The schema enforces
this with validate.network: each linked need is checked for
{"type": {"const": "stkh_req"}}. If a feat_req links to a comp via satisfies,
the schema rejects it.
Mandatory link ID regex (feasible) --
feat has mandatory_links: { includes: ^logic_arc_int(_op)*__.+$ }. The link
target IDs (strings like logic_arc_int__something) must match this regex.
Currently the schema only enforces that at least one link exists (minItems: 1),
not the ID pattern. Feasible: add "items": {"pattern": "^logic_arc_int(_op)*__.+$"}
to the local schema. There is a TODO in the code for this.
Optional link target type (feasible) --
tool_req has optional_links: { satisfies: gd_req, stkh_req }. If provided, targets
should be gd_req or stkh_req. The Python check validates this but treats violations
as informational (non-fatal). The schema currently skips this because all schema entries
use severity: "violation" and there is no way to set a different severity for one
rule within the same schema entry. Feasible: create a second schema entry for the
same need type with severity: "info" that only checks optional link targets.
Optional link ID regex (feasible) --
Same as above, but for regex-based link IDs on optional links (e.g.
optional_links: { links: ^.*$ } on tsf). Same severity-split approach would work.
Mixed regex+plain link type (not possible) --
workproduct has optional_links: { complies: std_wp, ^std_req__aspice_40__iic.*$ }.
A complies target is valid if it is either a need of type std_wp OR has an ID
matching the regex. The validate.network items schema applies to ALL linked needs
identically, so it cannot express "match type X or match regex Y".
sphinx-needs' ValidateSchemaType does not support anyOf/oneOf.
These mixed fields are validated only by the Python check.
ID structure (parts count) (feasible) --
feat_req has parts: 3, meaning its ID must have 3 segments separated by __
(e.g. feat_req__my_feature__001). The Python check (check_id_format) splits on
__ and counts parts. Feasible: generate a per-type regex like
^feat_req__[^_]+(__[^_]+){1}$ in the schema. However, the Python check also
validates that the ID contains the feature abbreviation from the file path
(check_id_contains_feature), which depends on runtime context and cannot be
expressed in a schema.
Prohibited words (feasible) --
The metamodel forbids words like "shall", "must", "will" in need titles (for
requirement types). The Python check splits the title into words and checks each one.
Feasible: add a negative lookahead regex on the title field, e.g.
^(?!.*\b(shall|must|will)\b).*$. This is less precise than the Python check
(which normalizes case, strips punctuation) but catches most violations.
Graph constraints (not possible) --
graph_checks in the metamodel define rules like "an ASIL_B need must link to at
least one non-QM requirement via satisfies". This requires traversing the need
graph across multiple levels, which is fundamentally beyond what JSON Schema can
express. Only the Python check (check_metamodel_graph) can do this.
Undefined extra options (not possible) --
The Python check (check_extra_options) warns when a need has fields not defined
in the metamodel (e.g. a typo like saftey instead of safety). In theory,
unevaluatedProperties: false could reject unknown fields. In practice, sphinx-needs
adds many internal fields to needs (e.g. docname, lineno, is_external, computed
fields from dynamic functions) that are not in the metamodel. Enabling this would
cause false positives on every need.
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