From a0f72fc39bc6288e8315376f0835a5c5e6659b38 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sat, 27 Jun 2026 09:26:01 +0200 Subject: [PATCH] feat(rts): extend Reference Transition Scenarios to multi-regulation (CRA + MaschinenVO) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roadmap item 2: the RTS now pin MaschinenVO + convergence Expected Outcomes, so the convergence USP is a living regression, not just a one-off section. - RTS-003 (machine + ISMS, networked): full multi-regulation archetype — maschinenvo expected_delta + convergence expected_multi_target (links TP-ISO27001-CRA-MaschinenVO-v1). Generator runs the convergence pattern through RS-005: 4/4 machine-safety delta MISSING + 4/4 expected multi-target caps converge. PASS. - RTS-001 (component): MaschinenVO modeled as `uncertain` (a pure component is usually not a machine; deciding question is_safety_component) — engine must never assert it applies. Honest, parallel to the Data-Act handling. - RTS-002 (machine, QMS-only): MaschinenVO `applies` (is_machine) but LOW convergence — no ISMS means the cyber side is entirely delta, so few caps are shared. The honest contrast that the convergence USP rewards companies who already run an ISMS. - generator: per-RTS maschinenvo/convergence Soll-Ist checks; convergence pattern run once and reused. Data Act stays `uncertain` everywhere, never asserted. All 3 RTS PASS. 18 tests (transition+company), mypy --strict clean, check-loc 0. Non-runtime (knowledge + reference harness) -> no deploy (ADR-001). Co-Authored-By: Claude Opus 4.7 --- .../RTS-001.yaml | 10 ++- .../RTS-002.yaml | 12 +++- .../RTS-003.yaml | 24 +++++++- .../reference_scenarios/generate.py | 61 ++++++++++++++++--- .../reference_scenario_suite_v1.md | 10 ++- 5 files changed, 102 insertions(+), 15 deletions(-) diff --git a/backend-compliance/knowledge/reference_transition_scenarios/RTS-001.yaml b/backend-compliance/knowledge/reference_transition_scenarios/RTS-001.yaml index ee8d1cca..a80c6cff 100644 --- a/backend-compliance/knowledge/reference_transition_scenarios/RTS-001.yaml +++ b/backend-compliance/knowledge/reference_transition_scenarios/RTS-001.yaml @@ -25,7 +25,7 @@ transition_goal: pattern: TP-ISO27001-CRA-v1 # executed through RS-005 below - target: MaschinenVO pattern: null - note: pattern_pending # no MaschinenVO pattern yet + note: applicability_uncertain # a pure component is usually NOT a machine -> see expected_outcome.maschinenvo expected_outcome: cra: @@ -47,6 +47,14 @@ expected_outcome: - exploited_vuln_and_incident_reporting - product_cyber_risk_assessment - ce_conformity_assessment_and_technical_documentation + maschinenvo: + expectation: uncertain # a pure component is usually NOT a machine -> NOT asserted + deciding_questions: [is_machine, is_safety_component, partly_completed_machinery] + rationale: > + The Machinery Regulation applies to machines, safety components and partly completed machinery. + A pure embedded component is usually out of scope, but a SAFETY component is not — so applicability + is itself a deciding question. The engine must ask (is_safety_component?), not assert gilt/gilt-nicht. + Contrast RTS-003, where is_machine: true makes MaschinenVO a settled second target with real convergence. data_act: expectation: uncertain # NEVER a fixed gilt/gilt-nicht deciding_questions: [generates_usage_data, connected_product, data_act_scope] diff --git a/backend-compliance/knowledge/reference_transition_scenarios/RTS-002.yaml b/backend-compliance/knowledge/reference_transition_scenarios/RTS-002.yaml index d1736019..8b2e1822 100644 --- a/backend-compliance/knowledge/reference_transition_scenarios/RTS-002.yaml +++ b/backend-compliance/knowledge/reference_transition_scenarios/RTS-002.yaml @@ -22,7 +22,7 @@ transition_goal: pattern: TP-ISO9001-CRA-v1 - target: MaschinenVO pattern: null - note: pattern_pending + note: applies_machine_safety # is_machine: true -> settled second target (machine safety side) expected_outcome: cra: @@ -44,6 +44,16 @@ expected_outcome: - exploited_vuln_and_incident_reporting - ce_conformity_assessment_and_technical_documentation expected_delta_much_larger_than: RTS-001 # regression: ISO9001 leaves more open than ISO27001 + maschinenvo: + expectation: applies # is_machine: true -> settled (not uncertain like RTS-001's component) + expected_delta_at_least: + - machine_safety_risk_assessment + - mechanical_safety_and_guards + - operating_instructions_and_safety_information + low_convergence_note: > + Unlike RTS-003, a QMS-only builder gets almost NO CRA<->MaschinenVO convergence: with no ISMS the + cyber side is entirely in the delta, so few capabilities are shared between the two regulations. + The convergence USP rewards companies that ALREADY have an ISMS — that is the honest contrast. data_act: expectation: uncertain deciding_questions: [connected_product, generates_usage_data, data_act_scope] diff --git a/backend-compliance/knowledge/reference_transition_scenarios/RTS-003.yaml b/backend-compliance/knowledge/reference_transition_scenarios/RTS-003.yaml index 629eb578..ac844945 100644 --- a/backend-compliance/knowledge/reference_transition_scenarios/RTS-003.yaml +++ b/backend-compliance/knowledge/reference_transition_scenarios/RTS-003.yaml @@ -21,8 +21,8 @@ transition_goal: - target: CRA pattern: TP-ISO27001-CRA-v1 - target: MaschinenVO - pattern: null - note: pattern_pending + convergence_pattern: TP-ISO27001-CRA-MaschinenVO-v1 # multi-target pattern now exists + note: covered_by_convergence_pattern - target: DataAct pattern: null note: uncertain_hypothesis # NOT asserted — see expected_outcome.data_act @@ -44,6 +44,26 @@ expected_outcome: - exploited_vuln_and_incident_reporting - product_cyber_risk_assessment - ce_conformity_assessment_and_technical_documentation + maschinenvo: + # The machine is in scope of the Machinery Regulation (is_machine: true) -> a real second target. + convergence_pattern: TP-ISO27001-CRA-MaschinenVO-v1 + expected_delta_at_least: + - machine_safety_risk_assessment # mechanical safety, ISO 12100 + - mechanical_safety_and_guards + - operating_instructions_and_safety_information + - protection_against_corruption_of_safety_functions # Annex III 1.1.9 = the cyber-safety bridge + convergence: + # The USP: capabilities that satisfy CRA AND MaschinenVO at once (covers_targets [CRA, MaschinenVO]). + convergence_pattern: TP-ISO27001-CRA-MaschinenVO-v1 + targets: [CRA, MaschinenVO] + expected_multi_target_at_least: + - product_cyber_risk_assessment + - protection_against_corruption_of_safety_functions + - secure_signed_update_distribution + - ce_conformity_assessment_and_technical_documentation + rationale: > + ONE capability covers requirements in BOTH regulations — the convergence finding. The engine must + surface these as shared, so the customer sees "N of M new measures satisfy CRA and MaschinenVO at once". data_act: expectation: uncertain # the core correction: a connected machine MAY fall under the Data Act deciding_questions: [generates_usage_data, connected_product, data_act_scope] diff --git a/backend-compliance/reference_scenarios/generate.py b/backend-compliance/reference_scenarios/generate.py index 95b7f7cc..497d420b 100644 --- a/backend-compliance/reference_scenarios/generate.py +++ b/backend-compliance/reference_scenarios/generate.py @@ -295,6 +295,27 @@ for _pf in sorted(os.listdir(_pat_d2)): with open(os.path.join(_pat_d2, _pf), encoding="utf-8") as _h: _pp = yaml.safe_load(_h) _pat_by_id[_pp["id"]] = _pp +# Convergence pattern (multi-target) — run once through RS-005; reused by RTS-003 + the convergence section. +_CONV_ID = "TP-ISO27001-CRA-MaschinenVO-v1" +_MV_IDS = {"MaschinenVO", "MachineryRegulation", "Maschinenverordnung"} +CP = _pat_by_id.get(_CONV_ID) +_conv = None +_cp_missing = set() # type: ignore[var-annotated] +_all_t = [] # type: ignore[var-annotated] +_delta_t = {} # type: ignore[var-annotated] +if CP: + _all_t = sorted({t for it in CP["likely_covered"] + CP["delta_requirements"] for t in it.get("covers_targets", [])}) + _delta_t = {d["capability"]: d.get("covers_targets", []) for d in CP["delta_requirements"]} + _conv = regulatory_convergence(_delta_t, _all_t) + _cp_have = [a["capability"] for a in CP["likely_covered"]] + _cp_map = {"ISO27001": CapabilityMappingEntry(capability_ids=_cp_have, confidence=Confidence.MEDIUM)} + _cp_prof = build_company_profile( + CompanyContext(company_id="conv", certifications=[Certification(certification_id="ISO27001")]), _cp_map) + _cp_reqs = [TargetRequirement(capability_id=a["capability"]) for a in CP["likely_covered"]] + _cp_reqs += [TargetRequirement(capability_id=d["capability"], question_intent=d.get("needed_information", "verify_existence")) + for d in CP["delta_requirements"]] + _cp_a = assess_transition(TransitionContext(company_id="conv", target=TransitionGoal(target_id="CRA+MaschinenVO")), _cp_reqs, _cp_prof) + _cp_missing = {c.capability_id for c in _cp_a.coverage if c.status == CoverageStatus.MISSING} _rts_rows: List[Row] = [] for _rf in sorted(f for f in os.listdir(_rts_dir) if f.startswith("RTS-") and f.endswith(".yaml")): with open(os.path.join(_rts_dir, _rf), encoding="utf-8") as _f: @@ -334,9 +355,38 @@ for _rf in sorted(f for f in os.listdir(_rts_dir) if f.startswith("RTS-") and f. w("- Expected Delta erfüllt: **%s** (%d/%d Soll-Delta in der Ist-Lücke)" % ("ja" if _delta_ok else "NEIN", len(_exp_delta & _actual_missing), len(_exp_delta))) w("- Expected likely_covered erfüllt: **%s**" % ("ja" if _cov_ok else "NEIN")) w("- Data Act: Engine sagt **%s** (Soll: uncertain; nie asserted) → %s" % (_da, "ok" if _da_ok else "FEHLER (asserted!)")) + # MaschinenVO — multi-regulation target. uncertain (component) vs applies (machine); convergence only with an ISMS. + _mv = RTS["expected_outcome"].get("maschinenvo") + _mv_ok = True + _mv_tag = "—" + if _mv and _mv.get("expectation") == "uncertain": + _mv_ok = not (_appl & _MV_IDS) # a component: never wrongly asserted as in-scope + _mv_tag = "uncertain(ok)" if _mv_ok else "uncertain(FEHLER:asserted)" + w("- MaschinenVO: Soll **uncertain** (Komponente, deciding: is_safety_component) → Engine asserted nicht: %s" % ("ok" if _mv_ok else "FEHLER")) + elif _mv: # expectation: applies (is_machine: true) + _mv_exp = set(_mv.get("expected_delta_at_least", [])) + if _mv.get("convergence_pattern") == _CONV_ID and CP: + _mv_ok = _mv_exp <= _cp_missing + _mv_tag = "applies %d/%d Safety-Delta" % (len(_mv_exp & _cp_missing), len(_mv_exp)) + w("- MaschinenVO **gilt** (is_machine): %d/%d Safety-Delta in der Ist-Lücke (Convergence-Pattern) → %s" % (len(_mv_exp & _cp_missing), len(_mv_exp), "ok" if _mv_ok else "NEIN")) + else: + _mv_ok = bool(_mv_exp) + _mv_tag = "applies (geringe Konvergenz, kein ISMS)" + w("- MaschinenVO **gilt** (is_machine): Safety-Delta %s — **geringe Konvergenz ohne ISMS** (RS-004 reg-map-Gate offen)" % (", ".join(sorted(_mv_exp)) or "—")) + # Convergence — the USP: capabilities covering CRA AND MaschinenVO at once. + _cv = RTS["expected_outcome"].get("convergence") + _cv_ok = True + if _cv and _conv: + _cv_exp = set(_cv.get("expected_multi_target_at_least", [])) + _cv_hit = _cv_exp & set(_conv.multi_target_capabilities) + _cv_ok = _cv_exp <= set(_conv.multi_target_capabilities) + w("- Konvergenz CRA∩MaschinenVO: %d/%d erwartete Multi-Target-Caps → %s (%s)" % (len(_cv_hit), len(_cv_exp), "ok" if _cv_ok else "NEIN", _conv.headline)) + _ok = _ok and _mv_ok and _cv_ok w("") - _rts_rows.append(("%s (%s→CRA)" % (RTS["id"], _src), "PASS" if _ok else "PARTIAL", - "%d/%d Delta-Soll · likely_covered %s · DataAct=%s" % (len(_exp_delta & _actual_missing), len(_exp_delta), "ok" if _cov_ok else "NEIN", _da))) + _rts_rows.append(("%s (%s→CRA%s)" % (RTS["id"], _src, "+MaschVO" if _mv else ""), "PASS" if _ok else "PARTIAL", + "%d/%d Delta-Soll · likely_covered %s · DataAct=%s · MaschVO=%s%s" % ( + len(_exp_delta & _actual_missing), len(_exp_delta), "ok" if _cov_ok else "NEIN", _da, _mv_tag, + " · Konvergenz ok" if (_cv and _cv_ok) else ""))) coverage_table(_rts_rows) # ── Regulatory Convergence — CRA + MaschinenVO (the multi-regulation USP) ─── @@ -344,12 +394,7 @@ w("## Regulatory Convergence — CRA + MaschinenVO (Cross-Regulation Capability w("") w("_Der USP: welche Capability deckt MEHRERE Regelwerke gleichzeitig? (Convergence Pattern, RTS-003-Archetyp.)_") w("") -_cp_path = os.path.join(_pat_dir, "transition_pattern_iso27001_to_cra_maschinenvo_v1.yaml") -with open(_cp_path, encoding="utf-8") as _f: - CP = yaml.safe_load(_f) -_all_t = sorted({t for it in CP["likely_covered"] + CP["delta_requirements"] for t in it.get("covers_targets", [])}) -_delta_t = {d["capability"]: d.get("covers_targets", []) for d in CP["delta_requirements"]} -_conv = regulatory_convergence(_delta_t, _all_t) +assert _conv is not None and CP is not None # precomputed before the RTS loop (convergence pattern on disk) w("**Cross-Regulation Capability Mapping (Delta):** %s" % _conv.headline) w("") w("**Konvergenz — diese neuen Maßnahmen decken BEIDE Regelwerke gleichzeitig:**") diff --git a/backend-compliance/reference_scenarios/reference_scenario_suite_v1.md b/backend-compliance/reference_scenarios/reference_scenario_suite_v1.md index d3af5a6f..5e52372c 100644 --- a/backend-compliance/reference_scenarios/reference_scenario_suite_v1.md +++ b/backend-compliance/reference_scenarios/reference_scenario_suite_v1.md @@ -185,26 +185,30 @@ _Anonymisierte Archetypen (KEINE Firmennamen). Jeder RTS pinnt ein Expected Outc - Expected Delta erfüllt: **ja** (7/7 Soll-Delta in der Ist-Lücke) - Expected likely_covered erfüllt: **ja** - Data Act: Engine sagt **uncertain** (Soll: uncertain; nie asserted) → ok +- MaschinenVO: Soll **uncertain** (Komponente, deciding: is_safety_component) → Engine asserted nicht: ok **RTS-002** — Classic machine builder with only a QMS — precision systems, CE products, no ISMS > Start ISO9001 → CRA. 13 zu klären, 0 bereits abgedeckt, 3 vermutlich vorhanden, 10 fehlt, 0 n/a, 0 nicht im Korpus. - Expected Delta erfüllt: **ja** (9/9 Soll-Delta in der Ist-Lücke) - Expected likely_covered erfüllt: **ja** - Data Act: Engine sagt **uncertain** (Soll: uncertain; nie asserted) → ok +- MaschinenVO **gilt** (is_machine): Safety-Delta machine_safety_risk_assessment, mechanical_safety_and_guards, operating_instructions_and_safety_information — **geringe Konvergenz ohne ISMS** (RS-004 reg-map-Gate offen) **RTS-003** — Machine builder with an ISMS and networked products — connected machines that may generate usage data > Start ISO27001 → CRA. 17 zu klären, 0 bereits abgedeckt, 8 vermutlich vorhanden, 9 fehlt, 0 n/a, 0 nicht im Korpus. - Expected Delta erfüllt: **ja** (7/7 Soll-Delta in der Ist-Lücke) - Expected likely_covered erfüllt: **ja** - Data Act: Engine sagt **uncertain** (Soll: uncertain; nie asserted) → ok +- MaschinenVO **gilt** (is_machine): 4/4 Safety-Delta in der Ist-Lücke (Convergence-Pattern) → ok +- Konvergenz CRA∩MaschinenVO: 4/4 erwartete Multi-Target-Caps → ok (4 von 12 Capabilities decken >= 2 Regelwerke gleichzeitig ab (CRA + MaschinenVO).) **Architecture Coverage** | Layer | Status | Hinweis | |---|---|---| -| RTS-001 (TISAX→CRA) | **PASS** | 7/7 Delta-Soll · likely_covered ok · DataAct=uncertain | -| RTS-002 (ISO9001→CRA) | **PASS** | 9/9 Delta-Soll · likely_covered ok · DataAct=uncertain | -| RTS-003 (ISO27001→CRA) | **PASS** | 7/7 Delta-Soll · likely_covered ok · DataAct=uncertain | +| RTS-001 (TISAX→CRA+MaschVO) | **PASS** | 7/7 Delta-Soll · likely_covered ok · DataAct=uncertain · MaschVO=uncertain(ok) | +| RTS-002 (ISO9001→CRA+MaschVO) | **PASS** | 9/9 Delta-Soll · likely_covered ok · DataAct=uncertain · MaschVO=applies (geringe Konvergenz, kein ISMS) | +| RTS-003 (ISO27001→CRA+MaschVO) | **PASS** | 7/7 Delta-Soll · likely_covered ok · DataAct=uncertain · MaschVO=applies 4/4 Safety-Delta · Konvergenz ok | ## Regulatory Convergence — CRA + MaschinenVO (Cross-Regulation Capability Mapping)