eclipse-score / eclipse-score/tooling
Migration path rst to trlc
- Dominant language
- Rust
- Stars
- 8
- Forks
- 37
- Avg merge
- 2d 13h
- Merged PRs (30d)
- 22
Description
### Epic Description
# Sums up the steps to get a working sphinx to trlc path with low maintainance effort
# Migration path: Sphinx-needs RST → TRLC, ending in one metamodel source of truth
Context: `rst_to_trlc` (eclipse-score/tooling, `bazel/rules/rules_score/src/rst_to_trlc.py`,
exposed as `//bazel/rules/rules_score:rst_to_trlc`, wrapped by the rule in
`private/rst_to_trlc.bzl`) converts Sphinx-needs requirement directives to TRLC.
RST stays the authoring format (community decision). The end state must be a single
metamodel definition, because today the metamodel exists three times and already diverges.
## Current divergence (evidence, measured 2026-08-24)
| Aspect | docs-as-code `metamodel.yaml` | `score_requirements_model.rsl` | `rst_to_trlc.py` |
|---|---|---|---|
| requirement types | 6 (`gd_req`, `stkh_req`, `feat_req`, `comp_req`, `tool_req`, `aou_req`) | 4 usable (`AssumedSystemReq`, `FeatReq`, `CompReq`, `AoU`) | 4 (`DIRECTIVE_TO_TRLC`) |
| `safety` value domain | `^(QM|ASIL_B)$` | `Asil` enum QM, A, B, C, D | maps QM, ASIL_A..D (`SAFETY_MAP`) |
| `satisfied_by` (mandatory link on `comp_req`/`feat_req`) | mandatory | not represented | dropped (not in `_ALLOWED_RST_ATTRS`) |
| `covers` (→ AoU) | optional link | `derived_from` carries AoU refs | dropped |
| `status`, `security`, `reqtype`, `valid_from` | mandatory options | not represented | dropped |
| version selector `[version==N]` | Sphinx-needs syntax | `Ref@N` | copied verbatim → invalid TRLC |
| `stkh_req` | defined, top of trace chain | no type | not convertible |
Consequences today: a `:status: invalid` requirement becomes an indistinguishable valid TRLC
record; a mandatory `satisfied_by` link is lost without a diagnostic; every `derived_from`
with a version selector produces syntactically invalid TRLC.
## Phase 0 — Measure, do not change anything (low-hanging, no process impact)
1. **Drift report tool** in tooling: reads `metamodel.yaml`, `score_requirements_model.rsl`
and the converter's tables, prints the table above. Run as a non-gating CI job.
Value: makes the problem factual and reviewable, and becomes the regression test for
every later phase.
2. **Conversion dry-run job**: run `rst_to_trlc` over all requirement RST in
score / persistency, report per file: records converted, directives skipped,
fields dropped, refs that cannot resolve. No build wiring, no gating.
Deliverable: a numbers-based problem statement for the community discussion.
Risk: none. Effort: ~1 session.
## Phase 1 — Make the converter honest (still no source or process change)
3. **Fail loud instead of skipping silently**: unknown directive with a `requirement` tag,
unknown/dropped mandatory option, or unresolvable ref ⇒ error, not silence.
(Silent data loss is the single property that would sink any ISO 26262 tool argument.)
4. **Handle `[version==N]`** → emit `Ref@N`; reject a bare ref if the model requires a version.
5. **Expose `package =`** through `score_requirements_rule` / `rst_srcs_to_trlc`
(today only the raw `rst_to_trlc` rule accepts it, so the package name silently
defaults to the CamelCased file stem, e.g. `index.rst` → `Index`).
Deliverable: converter that either produces correct TRLC or fails.
Risk: low, self-contained in tooling. Effort: ~1 session.
## Phase 2 — Pilot on one component, shadow mode
6. Wire **one** component in-build, e.g. persistency KVS:
`component_requirements(name = "comp_req", srcs = ["index.rst"], ref_package = ..., deps = [...])`.
35 `comp_req` records exist there today.
7. Run `trlc --verify` + lobster tracing on it as a **non-gating** CI job.
8. Accept that the chain is incomplete: `feat_req__persistency__*` live in
eclipse-score/score, referenced via `@score_platform//:needs_json` — see Phase 3.
Deliverable: proof the pipeline works end to end on real data, with a known-gap list.
Risk: low (non-gating). Effort: ~1 session.
## Phase 3 — Close the cross-repo chain
9. **score_platform bumps `score_tooling` 1.1.2 → current** (`rst_to_trlc` does not exist in 1.1.2).
10. Add `feature_requirements(...)` targets with public visibility per feature directory in
score_platform (no BUILD files exist under `docs/features/**` today), so the TRLC records
are exported via `TrlcProviderInfo` and consumers put them in `deps`.
11. **Add the missing `stkh_req` type to the requirements model** (a commented-out `StdReq`
stub already anticipates this) — otherwise platform's `feat_req`s derive from records
that cannot exist, and dropping those refs would silently cut off the top of the chain.
Deliverable: persistency `comp_req` → platform `feat_req` → `stkh_req` resolvable in TRLC.
Risk: medium — cross-repo version bump and a model extension needing community agreement.
Effort: ~2 sessions plus review latency.
## Phase 4 — Metamodel-driven converter (drift halved)
12. `rst_to_trlc` **reads `metamodel.yaml`** instead of its hard-coded `DIRECTIVE_TO_TRLC`,
`SAFETY_MAP`, `_ALLOWED_RST_ATTRS`, `_REF_FIELDS`. Adding a need type or tightening a
regex in docs-as-code then cannot leave the converter behind: it either flows through
or fails generation.
Blocker to resolve here: `metamodel.yaml` lives in docs-as-code, the converter and `.rsl`
in tooling, and tooling has **no** `bazel_dep` on `score_docs_as_code` (only the reverse).
Options: (a) tooling takes the dep, (b) the metamodel moves to a small shared module both
depend on. (b) is cleaner and is a prerequisite for Phase 5.
Effort: ~1 session after the dependency direction is decided.
## Phase 5 — Single point of truth: generate the `.rsl`
13. **Generate `score_requirements_model.rsl` from `metamodel.yaml`.** The YAML is already
machine-readable, schema-validated (`metamodel-schema.json`) and already drives generated
RST (`generate_metamodel_rst.py`), so it is the de-facto metamodel source.
Types, mandatory/optional options, value-domain regexes and link targets all map onto
TRLC types, fields and enums.
14. Hand-maintained `.rsl` becomes a generated artifact; the Phase 0 drift report becomes a
gating conformance test that must report zero divergence.
End state: **one** metamodel definition (`metamodel.yaml`), two generated representations
(Sphinx-needs config, TRLC `.rsl`), one generated instance representation (`.trlc` from RST).
Someone adding a need type or changing a parser rule can no longer create divergence.
## Phase 6 — Qualification argument (ISO 26262)
15. The converter sits in the safety-relevant path, so it needs tool-error detection.
Cheapest credible measure: a **diverse second extraction** from the Sphinx-needs
`needs.json` (already plumbed as `SphinxNeedsInfo.needs_json_file`) compared against the
generated TRLC — IDs, safety, version, links — failing the build on any diff.
Sphinx then sits only in the *checker*: its failure can cause a false alarm, never a
silent wrong output, so Sphinx itself does not need qualification.
16. Qualification scope is then: `rst_to_trlc` (~240 lines, stdlib only, unit-testable to
full branch coverage) + the `.rsl` generator, both with an independent checker.
## Open decisions for the community
- Dependency direction for the metamodel (tooling → docs-as-code, or a shared module).
- Whether `stkh_req` (and `gd_req` / `tool_req`) get TRLC types, or are explicitly out of scope
with a documented traceability break.
- Whether attributes the TRLC model does not represent (`status`, `security`, `reqtype`,
`valid_from`) are added to the model or explicitly declared non-safety-relevant.
- Whether the `safety` domain is `QM|ASIL_B` (metamodel) or QM..ASIL_D (`.rsl`) — one of the
two is wrong today.
### Acceptance Criteria (DoD)
Phases done
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.