Files
breakpilot-compliance/backend-compliance/reference_scenarios/capability_convergence_explanation.py
T
Benjamin Admin a98076196b feat: Capability Convergence Explanation — why the registry converges + Core/Domain (Phase Ω)
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.
2026-06-28 12:26:22 +02:00

129 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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))