# ruff: noqa # mypy: ignore-errors """Customer Mission #4 — a SECOND, different contract target (no tender-special-logic). Mission #3 showed one contract (a public tender) runs through the same engine as a regulation and a certification. One contract is not enough: with a single example we might accidentally bake tender-shaped assumptions into the later Scope→Journey selector. So this mission runs TWO deliberately different contract sub-types against the same company through the same engine: - a PUBLIC TENDER (öffentliche Ausschreibung — procurement: pentest report, references, SLA) - a PRIVATE OEM SPEC (Lastenheft — automotive customer: CSMS, functional safety, SUMS, ASPICE) If both reduce to a plain `Required` set and produce DIFFERENT deltas with the IDENTICAL engine and NO per-contract code, then „Contract" is not a special case — it is just another requirement source, and the selector can treat any contract uniformly. Synthetic company + synthetic contracts (NO real names). Runs the REAL engine. Non-runtime -> no deploy. Run: cd backend-compliance && PYTHONPATH=. python3 reference_scenarios/mission_second_contract.py """ from __future__ import annotations 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, ) OUT = [] def w(s=""): OUT.append(s) # ── Two contracts, each just a set of required capabilities (a contract has no parser — injected) ── TENDER = [ # public procurement: security baseline + procurement-specific evidence "information_security_management", "access_control_and_authentication", "incident_management", "technical_vulnerability_management", "coordinated_vulnerability_disclosure", "sbom_creation", "supplier_security", "penetration_test_evidence", "reference_project_evidence", "security_sla_and_support_commitment", ] OEM_SPEC = [ # private automotive customer Lastenheft: security + automotive-engineering-specific "information_security_management", "access_control_and_authentication", "incident_management", "supplier_security", "prototype_protection", "secure_signed_update_distribution", "dedicated_security_contact", "cybersecurity_management_system", "software_update_management_system", "functional_safety_evidence", "aspice_process_capability", ] CONTRACTS = [ ("Öffentliche Ausschreibung", "public tender", TENDER), ("OEM-Lastenheft", "private OEM spec", OEM_SPEC), ] # ── ONE company profile (same multi-certified archetype as Missions #2/#3) ────────────────── CERT_OBS = { "ISO27001": ["information_security_management", "incident_management", "access_control_and_authentication", "technical_vulnerability_management", "security_logging_and_monitoring", "secure_development_lifecycle", "cybersecurity_management_system"], "TISAX": ["information_security_management", "access_control_and_authentication", "incident_management", "supplier_security", "prototype_protection", "cybersecurity_management_system"], "PSIRT": ["coordinated_vulnerability_disclosure", "dedicated_security_contact", "secure_signed_update_distribution"], "ISO9001": ["ce_conformity_assessment_and_technical_documentation"], "ISO14001": ["environmental_management_documentation"], "CE": ["ce_conformity_assessment_and_technical_documentation"], } cmap = {k: CapabilityMappingEntry(capability_ids=v, confidence=Confidence.MEDIUM) for k, v in CERT_OBS.items()} profile = build_company_profile( CompanyContext(company_id="mc4", certifications=[Certification(certification_id=k) for k in CERT_OBS]), cmap) def _delta(req_caps): reqs = [TargetRequirement(capability_id=c) for c in req_caps] a = assess_transition(TransitionContext(company_id="mc4", target=TransitionGoal(target_id="c")), reqs, profile) return sorted({c.capability_id for c in a.coverage if c.status == CoverageStatus.MISSING}) def _relevance(cert_caps, req_caps): n = len(set(cert_caps) & set(req_caps)) return n, ("hoch" if n >= 3 else "mittel" if n >= 1 else "keine") w('# Customer Mission #4 — zwei verschiedene Verträge, eine Engine (kein Contract-Spezialfall)') w("") w('_Mission #3 zeigte EINEN Vertrag (öffentliche Ausschreibung) durch dieselbe Engine wie Gesetz/Zertifizierung. Ein Beispiel reicht nicht — sonst backen wir Tender-Annahmen in den späteren Selektor. Hier laufen ZWEI bewusst unterschiedliche Vertragsarten gegen dasselbe Unternehmen. Synthetischer Kunde + synthetische Verträge, keine echten Namen._') w("") w("## Der Kunde (synthetisch) — EIN Profil") w("> **ISO 9001 · ISO 27001 · ISO 14001 · TISAX · CE-Prozess · PSIRT** · vernetzte Maschinen · Export EU") w("") # ── 1. Zwei Verträge durch DIESELBE Engine ──────────────────────────────── deltas = {name: _delta(caps) for name, _, caps in CONTRACTS} w("## 1. Zwei Vertragsarten — dieselbe Engine `Profil − Required = Delta`") w("") w("| Vertrag | Art | geforderte Fähigkeiten | Delta (fehlt) |") w("|---|---|---|---|") for name, kind, caps in CONTRACTS: w("| **%s** | %s | %d | **%d** |" % (name, kind, len(caps), len(deltas[name]))) w("") w("→ Beide Verträge sind nur ein `Required`-Satz; **es gibt keinen Contract-spezifischen Codepfad** — `assess_transition` behandelt sie identisch zu Gesetz und Zertifizierung.") w("") # ── 2. Die Deltas sind UNTERSCHIEDLICH (also echte verschiedene Verträge) ── only_t = sorted(set(deltas["Öffentliche Ausschreibung"]) - set(deltas["OEM-Lastenheft"])) only_o = sorted(set(deltas["OEM-Lastenheft"]) - set(deltas["Öffentliche Ausschreibung"])) shared = sorted(set(deltas["Öffentliche Ausschreibung"]) & set(deltas["OEM-Lastenheft"])) w("## 2. Verschiedene Verträge → verschiedene Deltas (Beweis: keine Tender-Speziallogik nötig)") w("- **Nur Ausschreibung:** %s" % (", ".join("`%s`" % c for c in only_t) or "—")) w("- **Nur OEM-Lastenheft:** %s" % (", ".join("`%s`" % c for c in only_o) or "—")) w("- **Beiden gemeinsam:** %s" % (", ".join("`%s`" % c for c in shared) or "—")) w("") w("→ Die zwei Verträge fordern **strukturell anderes** (Beschaffungsnachweise vs. Automotive-Engineering: CSMS/funktionale Sicherheit/SUMS/ASPICE). Trotzdem **ein** Mechanismus. Genau das brauchten wir vor dem Selektor: zwei diverse Contract-Ziele, kein Sonderpfad.") w("") # ── 3. Evidence-Relevance(Vertrag) — dieselbe Evidence, anderer Wert je Vertrag ── w("## 3. Evidence-Relevanz(Vertrag)") w("| Zertifizierung (Evidence) | → Ausschreibung | → OEM-Lastenheft |") w("|---|---|---|") for cert, caps in CERT_OBS.items(): nt, lt = _relevance(caps, TENDER) no, lo = _relevance(caps, OEM_SPEC) w("| **%s** | %s (%d) | %s (%d) |" % (cert, lt, nt, lo, no)) w("") w("→ **TISAX** zählt gegen den **Automotive-OEM** mehr als gegen die generische Ausschreibung (Prototype Protection, CSMS); **ISO 14001** ist gegen beide **keine**. Bestätigt: **Relevanz ist eine Funktion des Ziels** — auch zwischen zwei Verträgen.") w("") # ── Befund ──────────────────────────────────────────────────────────────── w("## Befund") w("") w('> **Zwei strukturell verschiedene Verträge, ein Mechanismus, keine Zeile Contract-Spezialcode.** Damit ist „Contract" als Anforderungsquelle abgesichert (≥2 diverse Fälle): der spätere Scope→Journey-Selektor kann **jeden** Vertrag als reinen `Required`-Satz behandeln, ohne Tender-Speziallogik. Nächste sinnvolle Diversität vor dem Selektor: ein Vertrag, der bewusst auf NICHT-Security-Fähigkeiten zielt (z. B. Umwelt-/Materialnachweise), um auch die zielrelative Evidence-Relevanz über Domänen hinweg zu prüfen.') w("") print("\n".join(OUT))