Files
breakpilot-compliance/backend-compliance/reference_scenarios/mission_multicert_cra.py
T
Benjamin Admin 3856bb3a4f feat: Customer Mission #2 — the company arrives with a PROFILE, not a journey
Second mission, deliberately different from #1: a highly-certified company (ISO 9001 +
ISO 27001 + ISO 14001 + TISAX + CE + PSIRT) asking „what do WE still need for the CRA?".
Stresses Mission #1's one open seam (Scope → Journey) and proves the reframe with the
real engines:

- The start is a Company Capability Profile (certs aggregated), NOT a single cert→target
  journey. Certifications are OBSERVATIONS feeding the profile.
- Evidence is target-relative: ISO 14001 is in the profile but irrelevant to the CRA;
  PSIRT covers two CRA-delta capabilities. More evidence = smaller delta (12 → 9).
- The „journey" is the computed delta (Profile, Target) — not a thing a selector picks.
  This SHRINKS Mission #1's jump: the seam is profile-intake + target-pick, not a
  journey-matcher engine. There is no „ISO 27001 → CRA"; only „Profile → CRA".

Records the 5 per-mission selection-rationale questions (which journey/why/decisive
info/model-extended?/new-parameter?). Selector input = (Company Profile, Target), which
collapses the 2^N cert-combination explosion.

Non-runtime (reference_scenarios + tests only) -> no deploy. 6 tests pass; check-loc 0.
2026-06-28 09:00:51 +02:00

158 lines
10 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
"""Customer Mission #2 — the company arrives with a PROFILE, not a journey.
Mission #1 (Maschinenbauer) ended on one open seam: „Scope → Journey" — a consultant had to hand-pick
„ISO 27001 → CRA". This mission deliberately STRESSES that seam with a multi-certified company (the
ETO-archetype: many certs at once) and asks the reframe question: is the start a *certification* you map
to a journey, or a *Company Capability Profile* you compute a delta from?
Finding (proven below with the real engines): there is NO per-certificate journey. The start is the whole
profile; certifications are OBSERVATIONS that feed it; more evidence = smaller delta (12 → 9). The
„journey" is not selected — it is the delta `(Company Profile, Target)`. That shrinks Mission #1's jump:
the seam is not a journey-matcher, it is profile-intake + target-pick.
Synthetic multi-certified company (NO real names). Runs the REAL engines end-to-end.
Run: cd backend-compliance && PYTHONPATH=. python3 reference_scenarios/mission_multicert_cra.py
Not product code; not imported by the app. Non-runtime -> no deploy.
"""
from __future__ import annotations
import os
import yaml
from compliance.company import (
CompanyContext, Certification, CapabilityMappingEntry, build_company_profile,
)
from compliance.reasoning.enums import Confidence
from compliance.transition_reasoning import (
TransitionContext, TransitionGoal, TargetRequirement, assess_transition, CoverageStatus,
)
from compliance.optimization import roadmap_from_delta, select_within_budget
OUT = []
RAT = [] # (question, answer) — the per-mission selection rationale the user asked to record
def w(s=""):
OUT.append(s)
def rationale(question, answer):
RAT.append((question, answer))
_HERE = os.path.dirname(__file__)
_K = os.path.join(_HERE, "..", "knowledge")
CP = yaml.safe_load(open(os.path.join(_K, "transition_patterns",
"transition_pattern_iso27001_to_cra_maschinenvo_v1.yaml"), encoding="utf-8"))
w('# Customer Mission #2 — „Wir haben SCHON viel. Was fehlt UNS noch für die CRA?"')
w("")
w('_Zweite Mission, bewusst ANDERS als #1: nicht „ein Zertifikat → ein Ziel", sondern ein hoch-zertifiziertes Unternehmen, das mit einem ganzen Profil ankommt. Test der einzigen offenen Naht aus Mission #1 (Scope → Journey). Synthetischer Kunde, keine echten Namen._')
w("")
w("## Der Kunde (synthetisch, hoch-zertifiziert)")
w("> **ISO 9001** · **ISO 27001** · **ISO 14001** · **TISAX** · **CE-Prozess** · **PSIRT** · vernetzte Maschinen · Export EU")
w('> **Eine Frage:** „Wir sind schon in vielem zertifiziert — was genau fehlt UNS noch, um CRA-konform zu sein?"')
w("")
# ── 0. Company Capability Profile — DER Startzustand (nicht ein Zertifikat) ──
# Each certification is an OBSERVATION yielding probable capabilities. The profile is their AGGREGATE.
# Evidence is TARGET-RELATIVE: ISO 14001 is in the profile but irrelevant to the CRA (honest).
infosec = [a["capability"] for a in CP["likely_covered"]]
CERT_OBS = {
"ISO27001": (infosec, Confidence.MEDIUM, "Informationssicherheit (ISMS)"),
"TISAX": (infosec, Confidence.MEDIUM, "Automotive-ISMS — verstärkt dieselben Infosec-Fähigkeiten"),
"PSIRT": (["coordinated_vulnerability_disclosure", "exploited_vuln_and_incident_reporting"],
Confidence.HIGH, "Product-Security-Incident-Response — deckt ZWEI CRA-Delta-Fähigkeiten"),
"ISO9001": (["ce_conformity_assessment_and_technical_documentation"],
Confidence.MEDIUM, "QM-Dokumentendisziplin → CE-/Technische-Doku-Fähigkeit"),
"ISO14001": (["environmental_management_documentation"],
Confidence.MEDIUM, "Umweltmanagement — im Profil, aber für die CRA NICHT relevant"),
}
cmap = {k: CapabilityMappingEntry(capability_ids=v[0], confidence=v[1]) for k, v in CERT_OBS.items()}
ctx = CompanyContext(company_id="mc", certifications=[Certification(certification_id=k) for k in CERT_OBS])
profile = build_company_profile(ctx, cmap)
w("## 0. Company Capability Profile — der eigentliche Startzustand")
w("> Das Unternehmen bringt **kein Zertifikat als Startpunkt**, sondern ein **Profil**. Jedes Zertifikat ist eine *Beobachtung*, die wahrscheinliche Fähigkeiten beisteuert; der Startzustand ist ihre **Aggregation**.")
w("")
w("| Zertifikat (Beobachtung) | steuert Fähigkeiten bei | Vertrauen |")
w("|---|---|---|")
for k, (caps, conf, note) in CERT_OBS.items():
w("| **%s** — %s | %s | %s |" % (k, note, ", ".join("`%s`" % c for c in caps), conf.value))
w("")
w("→ **Evidence ist zielrelativ:** ISO 14001 liegt im Profil, hilft der CRA aber **nicht**; PSIRT (oft übersehen) deckt **zwei** CRA-kritische Delta-Fähigkeiten. Genau deshalb darf man **nicht ein Zertifikat** zur Journey machen — das ganze Profil zählt.")
w("")
# ── 1. Ziel (Intent) — was wollen SIE erreichen? ──────────────────────────
# The selection input is NOT a certificate. It is (Company Profile, Target). Intent = „CRA-konform werden".
TARGET = "CRA"
w("## 1. Ziel _(Intent)_ — was wollen Sie erreichen?")
w('- Intent: **„CRA-konform werden"** → Ziel-Profil = **CRA** (die von der CRA geforderten Fähigkeiten).')
w("- Auswahl-Eingabe ist damit **(Company Profile, Ziel)** — **kein** Zertifikat, das auf eine Journey gemappt wird.")
w("")
# ── 2. Capability Delta — Profil → Ziel (das ist „die Journey") ────────────
reqs = [TargetRequirement(capability_id=a["capability"]) for a in CP["likely_covered"]]
reqs += [TargetRequirement(capability_id=d["capability"], question_intent=d.get("needed_information", "verify_existence"))
for d in CP["delta_requirements"]]
assess = assess_transition(TransitionContext(company_id="mc", target=TransitionGoal(target_id=TARGET)), reqs, profile)
missing = sorted({c.capability_id for c in assess.coverage if c.status == CoverageStatus.MISSING})
# counterfactual: a single-certificate company (ISO 27001 only) — to show evidence shrinks the delta.
single_prof = build_company_profile(
CompanyContext(company_id="s", certifications=[Certification(certification_id="ISO27001")]),
{"ISO27001": CapabilityMappingEntry(capability_ids=infosec, confidence=Confidence.MEDIUM)})
single_missing = sorted({c.capability_id for c in
assess_transition(TransitionContext(company_id="s", target=TransitionGoal(target_id=TARGET)), reqs, single_prof).coverage
if c.status == CoverageStatus.MISSING})
closed_by_evidence = sorted(set(single_missing) - set(missing))
w('## 2. Capability Delta — Profil → CRA _(das IST die „Journey")_')
w("> %s" % assess.summary.headline)
w("- **Delta dieses Profils:** %d fehlende Fähigkeiten." % len(missing))
w("- **Gegenprobe (nur ISO 27001):** %d fehlende Fähigkeiten." % len(single_missing))
w("- **Mehr Evidence → kleineres Delta:** die zusätzliche Zertifizierung schließt **%d** Fähigkeiten *vorab*: %s." % (
len(closed_by_evidence), ", ".join("`%s`" % c for c in closed_by_evidence)))
w("")
w("→ Es wurde **keine Journey ausgewählt**. Das Delta `(Profil, Ziel)` IST die Journey — berechnet, nicht gewählt. Die Journey ist nur die *Erklärung* dieses Deltas.")
w("")
# ── 3. Roadmap — was zuerst? (gleicher Engine wie #1) ─────────────────────
delta_t = {d["capability"]: d.get("covers_targets", []) for d in CP["delta_requirements"]}
opt = roadmap_from_delta(assess, delta_t)
bud = select_within_budget({m.capability_id: m.covers for m in opt.ranked_measures}, 5)
w("## 3. Roadmap — was zuerst? _(gleicher Hebel-Engine wie Mission #1)_")
w("> %s" % opt.headline)
w("- Top-5 nach Hebel schließen **%d von %d** offenen Anforderungen (%d%%)." % (
bud.requirements_closed, bud.total_requirements, int(round(bud.coverage_ratio * 100))))
w("")
# ── Selektions-Rationale (die vom User geforderten 5 Fragen) ───────────────
rationale("Welche Journey wurde gewählt?",
"**Keine per-Zertifikat-Journey.** Die Journey ist `Company Capability Profile → CRA`. Gewählt wurde nur das **Ziel** (CRA); der Startzustand wurde aus ALLEN Zertifikaten aggregiert.")
rationale("Warum?",
'Bei 6 Zertifikaten würde „ISO 27001 → CRA" die Evidence aus PSIRT/ISO 9001 wegwerfen (= 3 Fähigkeiten, die sonst fälschlich als Delta erschienen). Nur das **ganze Profil** ergibt das korrekte Delta.')
rationale("Welche Informationen waren für die Auswahl entscheidend?",
"(a) das **Ziel/Intent** (CRA) und (b) die **vollständige Zertifikatsliste** als Profil — **nicht** ein einzelnes Zertifikat. Welche Evidence hilft, ist **zielrelativ** (ISO 14001 irrelevant, PSIRT hoch-relevant).")
rationale("Musste das Journey-Modell erweitert werden?",
"**Konzeptionell ja, strukturell nein.** `from → to` bleibt; aber `from` ist ein **Company Capability Profile** (Multi-Cert-Aggregat), kein Zertifikat. Die Engines (`build_company_profile` + `assess_transition`) tun das BEREITS — es war ein Benenn-/Framing-Fehler, kein fehlender Code.")
rationale("Musste ein neuer Selektionsparameter eingeführt werden?",
"**Ja — und er VEREINFACHT.** Eingabe ist `(Company Profile, Ziel)`, nicht `(Zertifikat, Ziel)`. Die Zertifikate kollabieren ins Profil → **keine 2^N Cert-Kombinationen**, nur Profil→Ziel. Der Selektor wird damit kleiner, nicht größer.")
w("## Selektions-Rationale — die 5 Fragen (pro Mission zu dokumentieren)")
w("")
for q, a in RAT:
w("**%s** " % q)
w(a)
w("")
# ── Was diese Mission über Mission #1 verändert ───────────────────────────
w("## Was Mission #2 an Mission #1 verändert")
w("")
w('- Mission #1 nannte **Scope → Journey** einen Sprung („kein Selektor `certs × targets → journeys`"). Mission #2 zeigt: **diese Naht schrumpft.** Es gibt keinen Journey-Matcher zu bauen — die Journey ist das **berechnete Delta** `(Profil, Ziel)`. Was real fehlt, ist nur **Profil-Intake + Ziel-Wahl**, nicht eine Journey-Auswahl-Engine.')
w('- Bestätigt den Reframe: **Es gibt keine „ISO 27001 → CRA"-Transition — nur „Company Capability Profile → CRA".** Zertifikate sind **Beobachtungen/Evidence**, kein Journey-Startpunkt.')
w("- **Beobachtung für den (noch nicht gebauten) Selektor:** Eingabe = `(Company Profile, Ziel)`. Diversität über weitere Missionen muss zeigen, ob auch **Produktprofil** und **Intent-Klasse** als Parameter nötig werden — erst dann kanonisieren ([[rule-of-three-canonicalization]]).")
w("")
print("\n".join(OUT))