a98076196b
The mature step after Medical is not the next domain but understanding WHY the registry converges.
Three derived views over existing data (no ML, no new architecture):
1. Why converge? — a domain matrix per cross-domain MCAP + a curated REASON (the moat: not "MCAP-X
exists" but "why MCAP-X must exist": software product / supply chain / product operation /
universal process).
2. Capability Families — ~75 MCAPs collapse to ~15 curated families (knowledge/capability_families/
families.yaml), each with the reason it is universal or domain-specific.
3. Core vs Domain — a COMPUTED property (not a new class): Core recurs across >=2 independent domains
AND source types; Domain stays in one. Medical made it obvious (new medical caps are nearly all
Domain; update/SBOM/access/logging are Core).
Non-runtime -> no deploy. 4 tests pass, check-loc 0.
129 lines
7.3 KiB
Python
129 lines
7.3 KiB
Python
# ruff: noqa
|
||
# mypy: ignore-errors
|
||
"""Capability Convergence Explanation — WHY do these capabilities converge? (Phase Ω, understand the core)
|
||
|
||
Medical proved the registry converges. The mature next step is NOT the next domain but understanding WHY:
|
||
not "which MCAPs?" but "why do different worlds keep needing the SAME ones?". Three derived views over
|
||
the existing data (no new runtime, no new architecture):
|
||
1. Why converge? — a domain matrix per core MCAP + a curated REASON (the moat: why it must exist)
|
||
2. Capability Families — the ~60 MCAPs reduce to a small set of families, each with a reason
|
||
3. Core vs Domain — a COMPUTED property (not stored): Core recurs across independent domains+types;
|
||
Domain stays within one. The split that Medical made obvious.
|
||
|
||
Non-runtime -> no deploy.
|
||
Run: cd backend-compliance && PYTHONPATH=. python3 reference_scenarios/capability_convergence_explanation.py
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import yaml
|
||
|
||
OUT = []
|
||
|
||
|
||
def w(s=""):
|
||
OUT.append(s)
|
||
|
||
|
||
_HERE = os.path.dirname(__file__)
|
||
_TP = os.path.join(_HERE, "..", "knowledge", "transition_patterns")
|
||
FAM = yaml.safe_load(open(os.path.join(_HERE, "..", "knowledge", "capability_families", "families.yaml"), encoding="utf-8"))["families"]
|
||
|
||
PATTERN_META = {
|
||
"transition_pattern_iso27001_to_cra_maschinenvo_v1.yaml": ("industrial_automation", "regulation", ["CRA", "MaschinenVO"]),
|
||
"transition_pattern_iso27001_to_cra_v1.yaml": ("industrial_automation", "regulation", ["CRA"]),
|
||
"transition_pattern_iso9001_to_cra_v1.yaml": ("industrial_automation", "regulation", ["CRA"]),
|
||
"transition_pattern_isms_to_tisax_v1.yaml": ("automotive", "certification", ["TISAX"]),
|
||
"transition_pattern_iso14001_to_environmental_v1.yaml": ("environmental", "regulation", ["ENV"]),
|
||
"transition_pattern_iso13485_to_medical_v1.yaml": ("medical", "regulation", ["MDR"]),
|
||
}
|
||
|
||
idx = {}
|
||
|
||
|
||
def _e(c):
|
||
return idx.setdefault(c, {"domains": set(), "types": set(), "sources": set()})
|
||
|
||
|
||
for fname, (domain, ttype, default_sources) in PATTERN_META.items():
|
||
p = yaml.safe_load(open(os.path.join(_TP, fname), encoding="utf-8"))
|
||
cert = (p.get("transition_goal", {}).get("from", {}) or {}).get("standard", fname)
|
||
for a in p.get("likely_covered", []):
|
||
e = _e(a["capability"]); e["domains"].add(domain); e["types"].add("certification"); e["sources"].add(cert)
|
||
for d in p.get("delta_requirements", []):
|
||
e = _e(d["capability"]); e["domains"].add(domain); e["types"].add(ttype); e["sources"] |= set(d.get("covers_targets") or default_sources)
|
||
|
||
A = yaml.safe_load(open(os.path.join(_HERE, "..", "knowledge", "domains", "automotive", "source_capabilities.yaml"), encoding="utf-8"))
|
||
for s in A["sources"]:
|
||
for cap in s["requires"]:
|
||
e = _e(cap); e["domains"].add("automotive"); e["types"].add(s["type"]); e["sources"].add(s["id"])
|
||
|
||
|
||
def family_of(cap):
|
||
toks = set(cap.split("_"))
|
||
for f in FAM: # first family (core first) sharing a token wins
|
||
if toks & set(f["tokens"]):
|
||
return f
|
||
return None
|
||
|
||
|
||
DOMAINS = ["industrial_automation", "automotive", "medical", "environmental"]
|
||
DLAB = {"industrial_automation": "Industrial", "automotive": "Automotive", "medical": "Medical", "environmental": "Environmental"}
|
||
|
||
w("# Capability Convergence Explanation — WARUM konvergieren diese Capabilities?")
|
||
w("")
|
||
w('_Nicht „welche MCAPs?", sondern „warum verlangen völlig verschiedene Welten immer wieder DIESELBEN?". Drei abgeleitete Sichten über vorhandene Daten (kein ML, keine neue Architektur). Der eigentliche Burggraben ist nicht „MCAP-X existiert", sondern „warum MCAP-X existieren MUSS"._')
|
||
w("")
|
||
|
||
# ── 1. Why converge? ──────────────────────────────────────────────────────
|
||
cross = sorted((c for c, e in idx.items() if len(e["domains"]) >= 2), key=lambda c: (-len(idx[c]["domains"]), c))
|
||
w("## 1. Warum konvergieren sie? — Domänen-Matrix + Grund")
|
||
w("| Capability | %s | Grund (Familie) |" % " | ".join(DLAB[d] for d in DOMAINS))
|
||
w("|---|%s|---|" % ("---|" * len(DOMAINS)))
|
||
for c in cross[:10]:
|
||
cells = ["✓" if d in idx[c]["domains"] else "–" for d in DOMAINS]
|
||
f = family_of(c)
|
||
w("| `%s` | %s | %s |" % (c, " | ".join(cells), (f["reason"] if f else "—")))
|
||
w("")
|
||
w("→ Das ist keine Statistik mehr, sondern **Erkenntnis**: dieselbe Fähigkeit kehrt wieder, weil ein universelles Prinzip dahinter steht (Softwareprodukt · Lieferkette · Produktbetrieb · universeller Prozess).")
|
||
w("")
|
||
|
||
# ── 2. Capability Families ─────────────────────────────────────────────────
|
||
fam_members = {f["id"]: [] for f in FAM}
|
||
unassigned = []
|
||
for c in idx:
|
||
f = family_of(c)
|
||
(fam_members[f["id"]] if f else unassigned).append(c)
|
||
w("## 2. Capability Families — %d MCAPs reduzieren sich auf %d Familien" % (len(idx), len([f for f in FAM if fam_members[f['id']]])))
|
||
w("| Familie | Art | MCAPs | Domänen | Warum universell / spezifisch |")
|
||
w("|---|---|---:|---:|---|")
|
||
for f in FAM:
|
||
ms = fam_members[f["id"]]
|
||
if not ms:
|
||
continue
|
||
doms = set()
|
||
for c in ms:
|
||
doms |= idx[c]["domains"]
|
||
w("| **%s** | %s | %d | %d | %s |" % (f["label"], f["kind"], len(ms), len(doms), f["reason"]))
|
||
if unassigned:
|
||
w("| _(nicht zugeordnet)_ | — | %d | — | Review: Familie fehlt oder Name untypisch |" % len(unassigned))
|
||
w("")
|
||
w("→ Die Familien **erklären** die Konvergenz: unterschiedliche Regelwerke brauchen dieselben MCAPs, weil sie dieselbe FAMILIE adressieren. Vermutete Langzeit-Reduktion: ~%d Familien statt %d Einzel-MCAPs." % (len([f for f in FAM if fam_members[f['id']]]), len(idx)))
|
||
w("")
|
||
|
||
# ── 3. Core vs Domain (COMPUTED, not stored) ──────────────────────────────
|
||
core = sorted(c for c, e in idx.items() if len(e["domains"]) >= 2 and len(e["types"]) >= 2)
|
||
domain_caps = sorted(c for c, e in idx.items() if len(e["domains"]) == 1)
|
||
bridging = [c for c in idx if c not in core and c not in domain_caps]
|
||
w("## 3. Core vs Domain — eine BERECHNETE Eigenschaft (keine neue Klasse, keine Architektur)")
|
||
w("- **Core (%d):** kehren über ≥2 unabhängige Domänen UND ≥2 Quelltypen wieder — z. B. %s." % (
|
||
len(core), ", ".join("`%s`" % c for c in core[:6]) + " …"))
|
||
w("- **Domain (%d):** überwiegend EINE Fachdomäne — z. B. %s." % (
|
||
len(domain_caps), ", ".join("`%s`" % c for c in domain_caps[:6]) + " …"))
|
||
w("- **Bridging (%d):** dazwischen (mehrere Domänen, aber 1 Quelltyp)." % len(bridging))
|
||
w("")
|
||
w('> **Befund:** Medical machte es offensichtlich — die neuen medizinischen Capabilities sind fast alle **Domain**, während Update/SBOM/Access/Logging **Core** sind. Die Zweiteilung ist eine **abgeleitete Eigenschaft** aus (Domänen × Quelltypen), kein neues Modell. Der eigentliche Burggraben ist die Erklärung, WARUM die Core-Familien existieren: sie sind die wenigen universellen Prinzipien (Risiko · Update · Identität · Inventar · Nachweis · Lieferant · Lebenszyklus), auf die sehr unterschiedliche Anforderungswelten immer wieder zurückfallen. Reine Aggregation, 0 Runtime, 0 neue Architektur.')
|
||
w("")
|
||
|
||
print("\n".join(OUT))
|