Files
breakpilot-compliance/backend-compliance/reference_scenarios/mission_second_contract.py
T
Benjamin Admin b71771e52e feat: Customer Mission #4 — a second, different contract target (no tender-special-logic)
One contract example (Mission #3's public tender) is not enough to safely generalise: it risks
baking tender-shaped assumptions into the later Scope→Journey selector. This mission runs TWO
deliberately different contract sub-types against the same company through the IDENTICAL engine:

  - public tender   (procurement: pentest report, references, support SLA, SBOM)  -> delta 4
  - private OEM spec (Lastenheft: CSMS, functional safety, SUMS, ASPICE)            -> delta 3

The two deltas are completely DISJOINT (no shared missing capability), proving the contracts are
genuinely different — yet there is no per-contract code: assess_transition treats each as a plain
Required set, exactly like a regulation or a certification. Evidence-Relevance is target-relative
even between two contracts (TISAX worth more to the automotive OEM than to the generic tender).

Conclusion: "Contract" as a requirement source is now covered by >=2 diverse cases, so the later
selector can treat any contract uniformly. Synthetic company + synthetic contracts (NO real names).
Non-runtime -> no deploy. 5 tests pass.
2026-06-28 09:42:31 +02:00

135 lines
8.0 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 #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))