-
Notifications
You must be signed in to change notification settings - Fork 24
Feat/generate ubproject and schema validation #394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 250 commits
ba3147b
0b9ad50
2bc27b4
bd979d4
04e561e
7ed2c2a
5af8620
7e6a388
ba387d7
995616e
503470c
9791db5
14793ad
bb30a98
3650fcf
e40c003
9715f86
fae7591
94bda9e
0460310
027e3b7
e1b3bd2
79c7295
d7bd238
d7df843
6a468c7
93136e0
5ea6264
180e1f7
db1682d
ab549c6
467ed9c
65ae158
316102a
1c4352e
d9df244
86b5a37
de4b821
ebebf9e
1c436f1
68fe59e
f57ba65
69ad70b
36a2252
98e7276
e5c2dc5
609d4fb
8dac10b
e866db5
2ce245b
c3065ed
a7bea32
bae447d
2d19347
5c66928
73fd2ee
e81f9e9
110032c
eaec7c6
6b181a8
bd0c101
f101ba8
3007d2b
df01d29
d7a98e3
6290e80
a2d7cad
3c7be8e
9719be9
d8aa801
055bf29
90bd22f
28ad4ed
8964246
96e5a76
446bf0e
8459a10
ec62bcc
e6bf7c2
61be512
f064cef
e0d5de6
e95e662
958177e
fd4b97c
4e272bd
66dc723
cc3b0fa
aae98ca
e48a1fc
49bde21
f69b9c0
fd90a17
014857a
cab4dfd
9f410b3
753dbe7
bb83e99
92a4806
039abec
5259093
1a7657e
0272f9e
47a80da
b3617ae
5b52558
df9dc12
b7ac1d5
e0385b0
c62f3d0
8e46554
12d6128
d4b230f
dd04c72
d3cb059
ceae78a
5f22e5f
19de31b
ee6099e
3f8a6f7
5ea4b6c
305c238
8c99c5a
afae3b3
5ec612c
2b39282
e16f781
1161235
be78ba5
5e8b013
5239062
33eda7a
723bee8
bb36f74
19e57a3
a5f3fb6
87b43e1
60db8fe
3a00e3d
8fe17a3
14d18a2
703af50
d77b662
7a80227
accad51
3ca45ea
3d4dadd
b7b082a
1931ac2
b5df7e0
c5e0c3f
38ecb3d
80ed1b5
0236364
a177ece
64df598
3b2271d
8bf3335
7c591f4
86b0145
2e7118b
c546eed
39d78d7
84de4a4
794d642
4d28db0
015e0af
50b21b3
0382665
efa8280
89dcf0f
b5023ad
fe212a0
072693f
a145da7
3be1288
4b8ddcd
a6cf1d9
4e12400
a1a3e15
5102059
5629263
534e1ef
d14b917
5008ea2
88bd7ce
e6460ed
3723042
7fbb09b
59703f5
de6667c
60382b9
1a56cfe
1ab312b
e0bdf84
8174980
374f4cd
ad53345
ce37b35
d428a31
e49778e
7330a45
3fa5b63
6ee8e86
5c2c5dd
558b210
f505139
5517b36
04c9307
22f830d
9074587
15e1779
d054d81
e33bd2f
a38e999
eac5d5e
2cc5ee8
a42dedd
29fcd07
d784f05
e0d7efd
6c23dbe
f46007a
92f3307
a1d32e6
791ec1e
3808b58
6dfdad7
1b6f083
8fce7fd
7539f11
7e193ed
8e8c30a
49bc9ca
86c06f5
1f6f681
31c472c
7220fe9
f2e8bcf
5282209
783f20c
e9b0766
dd2774f
210a550
9609af3
5f1263b
406adfd
8307f98
2344224
9e93122
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| # score_metamodel | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it makes sense to create this README here which seems to be AI slop more or less duplicating info from our actual documentation.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fair point. I will remove this. |
||
|
|
||
| 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_validity_consistency` | `check_options.py` | Validates that `valid_from` < `valid_until` for `stkh_req` and `feat_req` | | ||
| | `check_id_format` | `attributes_format.py` | ID structure (`<type>__<abbrev>__<element>`, part count) | | ||
| | `check_id_length` | `attributes_format.py` | ID must not exceed 45 characters (62 for IDs containing `example_feature`) | | ||
| | `check_for_prohibited_words` | `attributes_format.py` | Forbidden words in titles and content | | ||
| | `check_metamodel_graph` | `graph_checks.py` | Cross-need constraints (e.g. ASIL_B needs must link to non-QM requirements) | | ||
| | `id_contains_feature` | `id_contains_feature.py` | Need IDs must contain the feature abbreviation from the file path | | ||
|
|
||
| ### Coverage comparison | ||
|
|
||
| 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 | to be analyzed | 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 | to be analyzed | yes | Cross-need traversals beyond JSON Schema | | ||
| | Undefined extra options | to be analyzed | yes | `unevaluatedProperties` would reject sphinx-needs internal fields | | ||
| | ID length limit (45 chars) | feasible | yes | Can add `maxLength` to per-type ID pattern in schema | | ||
| | Validity period consistency (`valid_from` < `valid_until`) | to be analyzed | yes | Cross-field comparison beyond JSON Schema | | ||
|
|
||
| #### Rule explanations | ||
|
|
||
| **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** (to be analyzed) -- | ||
| `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 | ||
| (`id_contains_feature`), which depends on runtime context and cannot be | ||
| expressed in a schema. | ||
|
|
||
| **ID length limit (45 chars)** (feasible) -- | ||
| The Python check (`check_id_length`) rejects IDs longer than 45 characters (IDs | ||
| containing `example_feature` get an extra 17 characters to account for the | ||
| placeholder being replaced by real feature names). *Feasible*: add `"maxLength": 45` | ||
| to the per-type ID pattern in the schema. | ||
|
|
||
| **Validity period consistency** (to be analyzed) -- | ||
| `stkh_req` and `feat_req` may carry `valid_from` and `valid_until` version fields. | ||
| The Python check (`check_validity_consistency`) rejects needs where | ||
| `valid_from >= valid_until`. This requires comparing two field values against each | ||
| other, which JSON Schema cannot do (JSON Schema has no cross-field comparison | ||
| operator). Only the Python check can enforce this rule. | ||
|
|
||
| **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** (to be analyzed) -- | ||
| `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** (to be analyzed) -- | ||
| 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. | ||
|
|
||
| ## 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 | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
| from pathlib import Path | ||
|
|
||
| from sphinx.application import Sphinx | ||
| from sphinx.config import Config | ||
| from sphinx_needs import logging | ||
| from sphinx_needs.data import NeedsView, SphinxNeedsData | ||
| from sphinx_needs.need_item import NeedItem | ||
|
|
@@ -29,6 +30,7 @@ | |
| ProhibitedWordCheck as ProhibitedWordCheck, | ||
| 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, | ||
| load_metamodel_data as load_metamodel_data, | ||
|
|
@@ -248,6 +250,22 @@ def setup(app: Sphinx) -> dict[str, str | bool]: | |
| config_setdefault(app.config, "needs_reproducible_json", True) | ||
| config_setdefault(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. | ||
| # Use config-inited event to defer file writing until config is ready, | ||
| # with error handling to prevent boot crashes on file write failures. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't his be a docstring? Comment before method makes no sense.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
| def _write_schemas_with_error_handling(app: Sphinx, config: Config) -> None: | ||
| try: | ||
| write_sn_schemas(app, metamodel) | ||
| except Exception as e: | ||
| logger.warning( | ||
| f"Failed to write schemas.json: {e}. " | ||
| "Schema validation will be unavailable." | ||
| ) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AI review note: |
||
|
|
||
| _ = app.connect("config-inited", _write_schemas_with_error_handling, priority=499) | ||
|
|
||
| # sphinx-collections runs on default prio 500. | ||
| # We need to populate the sphinx-collections config before that happens. | ||
| # --> 499 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -978,6 +978,12 @@ needs_extra_links: | |
| partially_verifies: | ||
| incoming: partially_verified_by | ||
| outgoing: partially_verifies | ||
|
|
||
| # Decision Records | ||
| affects: | ||
| incoming: affected by | ||
| outgoing: affects | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is the metamodel extended here? Was that a gap before? Why didn't we find that before then?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was just reviewing that. It was a gap. Why we didnt find that ourselves is a very valid question.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In fact, this comes from here: https://github.com/eclipse-score/docs-as-code/pull/394/changes/BASE..5f1263b1fccd9acde0d608e29e672f448e53979e#diff-0ac6046ac75bc866580d21188af43573b945253e760280edbf05da368090304bR874 The introduced transformation script was not happy about not finding the definition of the optional link.
AlexanderLanin marked this conversation as resolved.
|
||
|
|
||
| ############################################################## | ||
| # Graph Checks | ||
| # The graph checks focus on the relation of the needs and their attributes. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even more files getting generated into the workspace?
This clashes with my proposal here: eclipse-score/score#2826
Technically, this is just a draft, so not really relevant for this PR though. Just want to mention it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs/needs_metamodel_generated.tomlcan definitely be moved to some build dir. I'll fix as part of this PR.docs/schemas.jsonis maybe required in the workspace. I can check if it is possible forubproject.tomlto reference to something from within the build dir.