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 |
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)
- 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.
- 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)
- 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.)
- Handle
[version==N] → emit Ref@N; reject a bare ref if the model requires a version.
- 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
- 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.
- Run
trlc --verify + lobster tracing on it as a non-gating CI job.
- 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
- score_platform bumps
score_tooling 1.1.2 → current (rst_to_trlc does not exist in 1.1.2).
- 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.
- Add the missing
stkh_req type to the requirements model (a commented-out StdReq
stub already anticipates this) — otherwise platform's feat_reqs 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)
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
- 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.
- 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)
- 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.
- 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
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 inprivate/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)
metamodel.yamlscore_requirements_model.rslrst_to_trlc.pygd_req,stkh_req,feat_req,comp_req,tool_req,aou_req)AssumedSystemReq,FeatReq,CompReq,AoU)DIRECTIVE_TO_TRLC)safetyvalue domainAsilenum QM, A, B, C, Dsatisfied_by(mandatory link oncomp_req/feat_req)_ALLOWED_RST_ATTRS)covers(→ AoU)derived_fromcarries AoU refsstatus,security,reqtype,valid_from[version==N]Ref@Nstkh_reqConsequences today: a
:status: invalidrequirement becomes an indistinguishable valid TRLCrecord; a mandatory
satisfied_bylink is lost without a diagnostic; everyderived_fromwith a version selector produces syntactically invalid TRLC.
Phase 0 — Measure, do not change anything (low-hanging, no process impact)
metamodel.yaml,score_requirements_model.rsland 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.
rst_to_trlcover all requirement RST inscore / 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)
requirementtag,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.)
[version==N]→ emitRef@N; reject a bare ref if the model requires a version.package =throughscore_requirements_rule/rst_srcs_to_trlc(today only the raw
rst_to_trlcrule accepts it, so the package name silentlydefaults 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
component_requirements(name = "comp_req", srcs = ["index.rst"], ref_package = ..., deps = [...]).35
comp_reqrecords exist there today.trlc --verify+ lobster tracing on it as a non-gating CI job.feat_req__persistency__*live ineclipse-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
score_tooling1.1.2 → current (rst_to_trlcdoes not exist in 1.1.2).feature_requirements(...)targets with public visibility per feature directory inscore_platform (no BUILD files exist under
docs/features/**today), so the TRLC recordsare exported via
TrlcProviderInfoand consumers put them indeps.stkh_reqtype to the requirements model (a commented-outStdReqstub already anticipates this) — otherwise platform's
feat_reqs derive from recordsthat cannot exist, and dropping those refs would silently cut off the top of the chain.
Deliverable: persistency
comp_req→ platformfeat_req→stkh_reqresolvable 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)
rst_to_trlcreadsmetamodel.yamlinstead of its hard-codedDIRECTIVE_TO_TRLC,SAFETY_MAP,_ALLOWED_RST_ATTRS,_REF_FIELDS. Adding a need type or tightening aregex in docs-as-code then cannot leave the converter behind: it either flows through
or fails generation.
Blocker to resolve here:
metamodel.yamllives in docs-as-code, the converter and.rslin tooling, and tooling has no
bazel_deponscore_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
.rslscore_requirements_model.rslfrommetamodel.yaml. The YAML is alreadymachine-readable, schema-validated (
metamodel-schema.json) and already drives generatedRST (
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.
.rslbecomes a generated artifact; the Phase 0 drift report becomes agating 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 (.trlcfrom RST).Someone adding a need type or changing a parser rule can no longer create divergence.
Phase 6 — Qualification argument (ISO 26262)
Cheapest credible measure: a diverse second extraction from the Sphinx-needs
needs.json(already plumbed asSphinxNeedsInfo.needs_json_file) compared against thegenerated 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.
rst_to_trlc(~240 lines, stdlib only, unit-testable tofull branch coverage) + the
.rslgenerator, both with an independent checker.Open decisions for the community
stkh_req(andgd_req/tool_req) get TRLC types, or are explicitly out of scopewith a documented traceability break.
status,security,reqtype,valid_from) are added to the model or explicitly declared non-safety-relevant.safetydomain isQM|ASIL_B(metamodel) or QM..ASIL_D (.rsl) — one of thetwo is wrong today.
Acceptance Criteria (DoD)
Phases done