diff --git a/backend-compliance/compliance/playbook/__init__.py b/backend-compliance/compliance/playbook/__init__.py new file mode 100644 index 00000000..e5ff3670 --- /dev/null +++ b/backend-compliance/compliance/playbook/__init__.py @@ -0,0 +1,20 @@ +"""Implementation Playbook — the Berater renderer ("wie komme ich dort hin?"). + +For one capability it assembles the full implementation journey (why / closes which regulations / +tools / process / evidence / controls) from curated playbook knowledge + regulatory leverage + +injected Execution links. `playbooks_for_plan` chains the Optimization Roadmap into per-measure +playbooks. Pure, deterministic, computed-not-stored. No new corpus, no new meta-model class +(freeze v1.0). Curated content = expert draft, never normative. +""" + +from __future__ import annotations + +from .engine import build_playbook, playbooks_for_plan +from .schemas import Playbook, PlaybookStep + +__all__ = [ + "build_playbook", + "playbooks_for_plan", + "Playbook", + "PlaybookStep", +] diff --git a/backend-compliance/compliance/playbook/engine.py b/backend-compliance/compliance/playbook/engine.py new file mode 100644 index 00000000..5083a46c --- /dev/null +++ b/backend-compliance/compliance/playbook/engine.py @@ -0,0 +1,96 @@ +"""Implementation Playbook — the Berater renderer ("wie komme ich dort hin?"). + +After the Capability Delta Engine says WHAT is missing and the Optimization renderer says WHICH +measure first, the Playbook renderer says HOW to implement it. For one capability it assembles the +full journey from three sources: + - curated playbook KNOWLEDGE (why / tools / process steps / evidence / how others do it) — the + Reasoning Knowledge Acquisition layer under `knowledge/implementation_playbooks/`, + - the regulatory LEVERAGE (which regulations a delivered capability closes) — reused from the + Optimization renderer, + - injected Procedure/Control/Evidence links (Execution-owned; empty until linked). + +Pure, deterministic, computed-not-stored. Chains optimization -> playbook (acyclic). No new corpus, +no new meta-model class (freeze v1.0). Python 3.9 compatible. + +The curated content is an EXPERT DRAFT, never a normative requirement. When no playbook knowledge +exists for a capability yet, the renderer emits a `status: missing` stub — the honest signal that +the bottleneck is CONTENT (Knowledge Acquisition), not software. +""" + +from __future__ import annotations + +from typing import Any, Dict, List, Optional + +from ..optimization import OptimizationPlan +from .schemas import Playbook, PlaybookStep + +_MISSING_WHY = "(Playbook-Inhalt fehlt — Knowledge Acquisition offen.)" +_DRAFT_DISCLAIMER = ( + "Kuratiertes Experten-Wissen (Erstentwurf), KEINE normative Anforderung. Tools/Schritte sind " + "Empfehlungen, kein Pflichtkatalog; Controls werden aus der Execution-Schicht injiziert." +) + + +def _steps(raw: Any) -> List[PlaybookStep]: + steps: List[PlaybookStep] = [] + for i, s in enumerate(raw or [], 1): + steps.append(PlaybookStep(order=i, title=str(s.get("title", "")), detail=str(s.get("detail", "")))) + return steps + + +def build_playbook( + capability_id: str, + knowledge: Optional[Dict[str, Any]] = None, + closes_regulations: Optional[List[str]] = None, + control_links: Optional[List[str]] = None, +) -> Playbook: + """Assemble the implementation journey for ONE capability. + + `knowledge`: the curated playbook dict (None/empty -> a `missing` stub). `closes_regulations`: + the regulations a delivered capability closes (leverage, from `covers_targets`). `control_links`: + Execution-owned control refs, injected (default empty — no Execution data in Reasoning code). + """ + closes = sorted(set(closes_regulations or [])) + if not knowledge: + return Playbook( + capability_id=capability_id, title=capability_id, why=_MISSING_WHY, + closes_regulations=closes, leverage=len(closes), controls=list(control_links or []), + status="missing", disclaimer=_DRAFT_DISCLAIMER, + ) + return Playbook( + capability_id=capability_id, + title=str(knowledge.get("title", capability_id)), + why=str(knowledge.get("why", "")), + closes_regulations=closes, + leverage=len(closes), + tools=list(knowledge.get("tools", [])), + process_steps=_steps(knowledge.get("process_steps")), + expected_evidence=list(knowledge.get("expected_evidence", [])), + controls=list(control_links or []), + how_others_do_it=str(knowledge.get("how_others_do_it", "")), + status=str(knowledge.get("status", "draft")), + disclaimer=str(knowledge.get("disclaimer", _DRAFT_DISCLAIMER)), + ) + + +def playbooks_for_plan( + plan: OptimizationPlan, + knowledge_by_cap: Dict[str, Dict[str, Any]], + top_k: Optional[int] = None, + control_links_by_cap: Optional[Dict[str, List[str]]] = None, +) -> List[Playbook]: + """Render playbooks for the highest-leverage measures of an OptimizationPlan (Roadmap -> How). + + Walks the ranked measures (top_k, or all) and builds each capability's playbook, using the + measure's own `covers` as the regulations it closes. Measures without curated knowledge become + `missing` stubs — surfacing exactly where playbook content is still owed. + """ + links = control_links_by_cap or {} + measures = plan.ranked_measures if top_k is None else plan.ranked_measures[: max(0, top_k)] + return [ + build_playbook( + m.capability_id, knowledge_by_cap.get(m.capability_id), + closes_regulations=m.covers, control_links=links.get(m.capability_id), + ) + for m in measures + ] diff --git a/backend-compliance/compliance/playbook/schemas.py b/backend-compliance/compliance/playbook/schemas.py new file mode 100644 index 00000000..1060a5e1 --- /dev/null +++ b/backend-compliance/compliance/playbook/schemas.py @@ -0,0 +1,45 @@ +"""Schemas for the Implementation Playbook renderer. + +A Playbook is a *derived view* (computed-not-stored): it assembles, for one capability, the full +"wie komme ich dort hin?" journey from (a) curated playbook KNOWLEDGE, (b) the regulatory leverage +(which regulations a delivered capability closes), and (c) injected Procedure/Control/Evidence links +(Execution-owned). Nothing here is persisted. No new meta-model class, no graph (freeze v1.0). +Python 3.9 compatible (no `|` unions). +""" + +from __future__ import annotations + +from typing import List + +from pydantic import BaseModel, Field + + +class PlaybookStep(BaseModel): + """One step in the recommended way to stand up a capability.""" + + order: int + title: str + detail: str = "" + + +class Playbook(BaseModel): + """The complete implementation journey for ONE capability — the Berater view. + + Answers, in order: Warum? -> Welche Regelwerke schliesst das? -> Welche Tools? -> Welche + Prozesse? -> Welche Nachweise? -> Welche Controls? The curated parts (why/tools/steps/evidence/ + how-others) are an EXPERT DRAFT, not a normative requirement; controls are injected from + Execution (may be empty until linked). + """ + + capability_id: str + title: str = "" + why: str = "" # why this is required (regulatory rationale) + closes_regulations: List[str] = Field(default_factory=list) # leverage: regulations a delivered cap closes + leverage: int = 0 # = len(closes_regulations) + tools: List[str] = Field(default_factory=list) # typical tooling (curated knowledge) + process_steps: List[PlaybookStep] = Field(default_factory=list) # how to stand it up + expected_evidence: List[str] = Field(default_factory=list) # artifacts that prove it + controls: List[str] = Field(default_factory=list) # control refs (injected from Execution; may be empty) + how_others_do_it: str = "" # "wie machen das andere?" (curated) + status: str = "draft" # draft -> reviewed -> validated -> proven + disclaimer: str = "" # expert draft, not a normative requirement diff --git a/backend-compliance/knowledge/implementation_playbooks/README.md b/backend-compliance/knowledge/implementation_playbooks/README.md new file mode 100644 index 00000000..336dd8a2 --- /dev/null +++ b/backend-compliance/knowledge/implementation_playbooks/README.md @@ -0,0 +1,51 @@ +# Implementation Playbooks — curated knowledge ("wie komme ich dort hin?") + +**Curated implementation KNOWLEDGE in machine-readable form — not an algorithm, not runtime code.** +After the Capability Delta Engine says WHAT is missing and the Optimization renderer says WHICH +measure first, a Playbook says HOW to implement one capability: why it is required, which tools and +process steps stand it up, which evidence proves it, which controls it maps to, and which regulatory +gaps it closes. The renderer is `compliance/playbook/`; this directory holds the content it renders. + +Nothing imports these at runtime — `compliance/playbook` reads them and assembles a `Playbook` +view. Adding or curating a playbook is therefore **non-runtime → no deploy** (ADR-001). + +## Playbook vs. regulatory domain (a deliberate distinction) + +- A **Playbook** is BreakPilot's OWN knowledge layer: `Capability → recommended approach → tools → + process → typical evidence → controls`. It does not introduce a new regulation. +- A **regulatory domain** (e.g. ISO 14001 → environmental law) is a new *regulatory* knowledge + domain (obligations, applicability), owned with Legal Knowledge / Execution. + +These scale independently. Once a domain lands, every new capability it needs can immediately be +embedded into the SAME playbook mechanism — which is why playbooks come first. + +## The bottleneck is CONTENT, not software + +The renderer is small and done. The value now grows with the **number and depth of playbooks**. +A capability with no playbook renders as a `status: missing` stub (the honest "content owed" signal). +This is Reasoning's Knowledge Acquisition responsibility (same as `../transition_patterns/`): +AI produces the first draft offline; BreakPilot reviews, versions and OWNS the library. + +## Maturity lifecycle + +`draft (L1) → reviewed (L2, internal) → validated (L3, domain expert) → proven (L4, field)`. +Curated content is an **EXPERT DRAFT, never a normative requirement**; tools/steps are recommended +practice, not a mandatory catalogue. Controls are **injected from the Execution layer** (may be +empty until linked) — no Execution data lives in the playbook content or in Reasoning product code. + +## Schema (per file) + +`id` · `capability_id` · `status` · `title` · `why` · `tools[]` · `process_steps[{title, detail}]` +· `expected_evidence[]` · `typical_controls[]` (indicative) · `how_others_do_it` · `disclaimer`. +`closes_regulations` / `leverage` are NOT stored here — the renderer supplies them from the +Optimization leverage (`covers_targets`), so one playbook serves every regulation it closes. + +## Catalogue + +| Playbook | Capability | status | +|---|---|---| +| `playbook_sbom_creation_v1.yaml` | sbom_creation | `draft` (L1) | +| `playbook_coordinated_vulnerability_disclosure_v1.yaml` | coordinated_vulnerability_disclosure (PSIRT) | `draft` (L1) | + +Next candidates (high-leverage CRA/MaschinenVO delta): `security_update_support_period` · +`secure_signed_update_distribution` · `product_cyber_risk_assessment`. diff --git a/backend-compliance/knowledge/implementation_playbooks/playbook_coordinated_vulnerability_disclosure_v1.yaml b/backend-compliance/knowledge/implementation_playbooks/playbook_coordinated_vulnerability_disclosure_v1.yaml new file mode 100644 index 00000000..21665302 --- /dev/null +++ b/backend-compliance/knowledge/implementation_playbooks/playbook_coordinated_vulnerability_disclosure_v1.yaml @@ -0,0 +1,58 @@ +# Implementation Playbook — curated KNOWLEDGE (the "wie komme ich dort hin?" layer), not runtime code. +# Capability: coordinated_vulnerability_disclosure (PSIRT). Expert FIRST DRAFT — a product-security +# practitioner would validate this; NOT a normative requirement. + +id: PB-coordinated_vulnerability_disclosure-v1 +capability_id: coordinated_vulnerability_disclosure +status: draft # draft -> reviewed -> validated -> proven +version: 1 + +title: "Coordinated Vulnerability Disclosure (CVD) / PSIRT aufbauen" + +why: > + Der CRA verlangt eine Richtlinie zur koordinierten Offenlegung von Schwachstellen und einen + Meldekanal (Annex I Teil II) sowie deren Bearbeitung. Forscher und Nutzer brauchen einen Weg, + Schwachstellen verantwortungsvoll zu melden; der Hersteller braucht einen Prozess, um zu + triagieren, zu beheben und abgestimmt zu veröffentlichen. Diese Funktion heißt PSIRT (Product + Security Incident Response Team). + +tools: + - "security.txt (RFC 9116) — maschinenlesbarer Sicherheitskontakt" + - "Veröffentlichte CVD-Policy (Webseite)" + - "Dediziertes PSIRT-Postfach + PGP-Schlüssel" + - "Triage-/Ticketsystem mit CVSS-Bewertung" + - "CSAF (Common Security Advisory Framework) für maschinenlesbare Advisories" + - "CVE Numbering Authority (CNA) — optional, für eigene CVE-Vergabe" + +process_steps: + - title: "CVD-Policy + Kontakt veröffentlichen" + detail: "Öffentliche Richtlinie (Scope, Safe-Harbor, Fristen) + security.txt mit Kontakt/PGP." + - title: "Meldekanal betreiben" + detail: "PSIRT-Postfach/Portal, verschlüsselte Übermittlung, Eingangsbestätigung mit SLA." + - title: "Triage + Schweregrad" + detail: "Eingang reproduzieren, CVSS bewerten, Betroffenheit über die SBOM bestimmen." + - title: "Behebung koordinieren" + detail: "Fix + Offenlegungsfrist mit dem Melder abstimmen (Coordinated Disclosure)." + - title: "CVE zuweisen" + detail: "Über die eigene oder eine partnernde CNA eine CVE-ID vergeben." + - title: "Advisory veröffentlichen + Nutzer benachrichtigen" + detail: "Maschinenlesbares CSAF-Advisory; betroffene Nutzer/Behörden informieren (verbindet mit Meldepflicht)." + +expected_evidence: + - "Veröffentlichte CVD-Policy + security.txt mit Kontakt" + - "PSIRT-Triage-Runbook (Rollen, Fristen, CVSS)" + - "Beispiel-Advisory (CSAF oder gleichwertig)" + +typical_controls: # INDIKATIV — echte Control-Zuordnung kommt aus der Execution-Schicht + - "vulnerability_disclosure" + - "incident_response" + +how_others_do_it: > + Verbreitete Praxis: security.txt + öffentliche CVD-Policy, ein PSIRT-Postfach mit PGP, Triage per + CVSS und Betroffenheitsanalyse über die SBOM, abgestimmte Veröffentlichung als CSAF-Advisory. + Viele Hersteller werden selbst CNA, um CVEs zeitnah vergeben zu können. + +disclaimer: > + Kuratiertes Experten-Wissen (Erstentwurf), KEINE normative Anforderung. Werkzeug- und + Schrittempfehlungen sind bewährte Praxis, kein Pflichtkatalog. Review durch eine:n PSIRT-/ + Product-Security-Expert:in ausstehend (status: draft). diff --git a/backend-compliance/knowledge/implementation_playbooks/playbook_sbom_creation_v1.yaml b/backend-compliance/knowledge/implementation_playbooks/playbook_sbom_creation_v1.yaml new file mode 100644 index 00000000..dc8145ec --- /dev/null +++ b/backend-compliance/knowledge/implementation_playbooks/playbook_sbom_creation_v1.yaml @@ -0,0 +1,58 @@ +# Implementation Playbook — curated KNOWLEDGE (the "wie komme ich dort hin?" layer), not runtime code. +# Capability: sbom_creation. Expert FIRST DRAFT — an SBOM practitioner would validate this; it is +# NOT a normative requirement. The renderer (compliance/playbook) assembles it into the journey. + +id: PB-sbom_creation-v1 +capability_id: sbom_creation +status: draft # draft -> reviewed -> validated -> proven +version: 1 + +title: "Software Bill of Materials (SBOM) aufbauen und betreiben" + +why: > + Der CRA verlangt von Herstellern, die Komponenten ihres Produkts zu identifizieren und zu + dokumentieren (Schwachstellenbehandlung, Annex I Teil II). Eine SBOM ist das maschinenlesbare + Inventar aller (auch transitiven) Software-Bestandteile mit Version und Lizenz. Ohne SBOM kann + niemand verlässlich sagen, welche Produkte von einer neuen Schwachstelle (CVE) betroffen sind — + SBOM ist damit die Voraussetzung für Schwachstellenüberwachung, Security-Updates und Meldepflichten. + +tools: + - "CycloneDX (Format, OWASP)" + - "SPDX (Format, Linux Foundation)" + - "Syft (Generator, Container/Filesystem)" + - "cdxgen (Generator, Multi-Ökosystem)" + - "Trivy (Generator + Scan)" + - "OWASP Dependency-Track (Verwaltung + kontinuierliche Überwachung)" + +process_steps: + - title: "Format festlegen" + detail: "CycloneDX oder SPDX wählen (maschinenlesbar). CycloneDX ist im Security-Kontext verbreitet." + - title: "Automatisch im Build erzeugen" + detail: "SBOM-Generierung (Syft/cdxgen/Trivy) als Schritt in die CI/CD-Pipeline einbauen — pro Build, nicht manuell." + - title: "Transitive Abhängigkeiten + Versionen + Lizenzen erfassen" + detail: "Nicht nur direkte Dependencies; gerade transitive Komponenten tragen die meisten CVEs." + - title: "Pro Release versionieren und ablegen" + detail: "Jede ausgelieferte Produktversion bekommt ihre eigene, unveränderliche SBOM (Nachvollziehbarkeit)." + - title: "An Schwachstellenüberwachung anbinden" + detail: "SBOM in Dependency-Track laden -> kontinuierlicher Abgleich gegen neue CVEs/Advisories." + - title: "Release-Gate setzen" + detail: "Auslieferung nur mit gültiger SBOM (Reifegrad-Schritt) — verbindet SBOM mit dem Update-Prozess." + +expected_evidence: + - "Maschinenlesbare SBOM (CycloneDX/SPDX) je ausgelieferter Produktversion" + - "CI-Job-Konfiguration, die die SBOM automatisch erzeugt" + - "Dependency-Track-Projekt (oder gleichwertig) mit laufender Überwachung" + +typical_controls: # INDIKATIV — echte Control-Zuordnung kommt aus der Execution-Schicht + - "component_inventory" + - "supply_chain_risk_management" + +how_others_do_it: > + Verbreitete Praxis: CycloneDX automatisch in der CI via Syft/cdxgen erzeugen und nach + OWASP Dependency-Track pushen, das kontinuierlich gegen neue CVEs prüft. Reifere Organisationen + gaten Releases auf das Vorhandensein einer SBOM und teilen sie auf Anfrage mit Kunden/Behörden. + +disclaimer: > + Kuratiertes Experten-Wissen (Erstentwurf), KEINE normative Anforderung. Werkzeug- und + Schrittempfehlungen sind Beispiele bewährter Praxis, kein Pflichtkatalog. Review durch eine:n + Product-Security-Expert:in ausstehend (status: draft). diff --git a/backend-compliance/reference_scenarios/_helpers.py b/backend-compliance/reference_scenarios/_helpers.py new file mode 100644 index 00000000..45ae94ab --- /dev/null +++ b/backend-compliance/reference_scenarios/_helpers.py @@ -0,0 +1,61 @@ +# ruff: noqa +# mypy: ignore-errors +"""Rendering helpers for the Reference Scenario Suite generator. + +Holds the shared mutable output buffers (OUT, ROLLUP) and the small markdown helpers so the +generator script (`generate.py`) stays under the LOC budget. Not product code; not imported by +the app — only by the generator (run via `PYTHONPATH=. python3 reference_scenarios/generate.py`). +""" +from __future__ import annotations + +from typing import List, Tuple + +Row = Tuple[str, str, str] +OUT: List[str] = [] +ROLLUP: List[str] = [] + + +def w(s: str = "") -> None: + OUT.append(s) + + +def coverage_table(rows: List[Row]) -> None: + w("**Architecture Coverage**") + w("") + w("| Layer | Status | Hinweis |") + w("|---|---|---|") + for layer, status, note in rows: + w("| %s | **%s** | %s |" % (layer, status, note)) + ROLLUP.append(status) + w("") + + +def reg_map_block(rmap) -> None: + w("**Expected Regulatory Map**") + w("") + w("> " + rmap.executive_summary) + w("") + for v in rmap.applicable_regulations: + obs = ", ".join(o.obligation_id for o in v.obligations) or v.obligations_note + w("- **%s** (%s) — Pflichten: %s" % (v.regulation_id, v.name, obs)) + for u in rmap.uncertain_regulations: + w("- _unsicher_ %s — fehlt: %s" % (u.regulation_id, ", ".join(u.missing_facts) or "-")) + for ov in rmap.overlaps: + w("- Overlap %s: %s" % (ov.overlap_group_id, ", ".join(ov.shared_obligations))) + for ev, ids in rmap.shared_evidence.items(): + w("- 1 Nachweis `%s` => %d Pflichten" % (ev, len(ids))) + w("") + + +def unsupported_block(rmap) -> None: + w("**Expected Unsupported Domains**") + w("") + if not rmap.unsupported_domains: + w("- keine — alle getriggerten Domaenen sind im Korpus") + for d in rmap.unsupported_domains: + w("- `%s` (Trigger: %s) -> %s" % (d.domain, d.trigger, d.note)) + w("") + + +def interp_status(verdict_value: str) -> str: + return "PARTIAL" if verdict_value in ("uncertain", "unsupported") else "PASS" diff --git a/backend-compliance/reference_scenarios/generate.py b/backend-compliance/reference_scenarios/generate.py index 3f480b34..d0976ebf 100644 --- a/backend-compliance/reference_scenarios/generate.py +++ b/backend-compliance/reference_scenarios/generate.py @@ -39,65 +39,19 @@ from compliance.transition_reasoning import ( regulatory_convergence, ) from compliance.optimization import roadmap_from_delta, select_within_budget +from compliance.playbook import playbooks_for_plan import os import yaml -Row = Tuple[str, str, str] -OUT: List[str] = [] -ROLLUP: List[str] = [] - - -def w(s: str = "") -> None: - OUT.append(s) - - -def coverage_table(rows: List[Row]) -> None: - w("**Architecture Coverage**") - w("") - w("| Layer | Status | Hinweis |") - w("|---|---|---|") - for layer, status, note in rows: - w("| %s | **%s** | %s |" % (layer, status, note)) - ROLLUP.append(status) - w("") - - -def reg_map_block(rmap) -> None: - w("**Expected Regulatory Map**") - w("") - w("> " + rmap.executive_summary) - w("") - for v in rmap.applicable_regulations: - obs = ", ".join(o.obligation_id for o in v.obligations) or v.obligations_note - w("- **%s** (%s) — Pflichten: %s" % (v.regulation_id, v.name, obs)) - for u in rmap.uncertain_regulations: - w("- _unsicher_ %s — fehlt: %s" % (u.regulation_id, ", ".join(u.missing_facts) or "-")) - for ov in rmap.overlaps: - w("- Overlap %s: %s" % (ov.overlap_group_id, ", ".join(ov.shared_obligations))) - for ev, ids in rmap.shared_evidence.items(): - w("- 1 Nachweis `%s` => %d Pflichten" % (ev, len(ids))) - w("") - - -def unsupported_block(rmap) -> None: - w("**Expected Unsupported Domains**") - w("") - if not rmap.unsupported_domains: - w("- keine — alle getriggerten Domaenen sind im Korpus") - for d in rmap.unsupported_domains: - w("- `%s` (Trigger: %s) -> %s" % (d.domain, d.trigger, d.note)) - w("") - +from _helpers import ( # noqa: E402 (script-dir module; keeps generate.py under the LOC budget) + OUT, ROLLUP, Row, w, coverage_table, reg_map_block, unsupported_block, interp_status, +) ISO_MAP = {"ISO27001": CapabilityMappingEntry( capability_ids=["cap_incident_response", "cap_supplier_management", "cap_asset_management"], confidence=Confidence.MEDIUM)} -def interp_status(verdict_value: str) -> str: - return "PARTIAL" if verdict_value in ("uncertain", "unsupported") else "PASS" - - w("# Reference Scenario Suite v1") w("") w("> **Kein Doku-Artefakt — die erste Ground Truth / Living Reference Suite.** Erzeugt aus den " @@ -442,6 +396,48 @@ coverage_table([ ("Budget-Priorisierung", "PASS", "Top-5 → %.0f%% der identifizierten Anforderungen" % (_bud.coverage_ratio * 100)), ]) +# ── Implementation Playbook — Berater-Renderer (wie komme ich dort hin?) ─── +w("## Implementation Playbook — wie komme ich dort hin? (Berater-Renderer)") +w("") +w('_Nach „was fehlt?" (Delta) und „womit anfangen?" (Hebel) die nächste Ebene: **wie umsetzen?** Pro Maßnahme eine komplette Reise aus kuratiertem Wissen + Hebel + (injizierten) Execution-Links. Inhalt ist der Engpass, nicht die Software._') +w("") +_pb_dir = os.path.join(os.path.dirname(__file__), "..", "knowledge", "implementation_playbooks") +_pb_kb = {} +for _pf in sorted(os.listdir(_pb_dir)): + if _pf.endswith(".yaml"): + with open(os.path.join(_pb_dir, _pf), encoding="utf-8") as _h: + _pd = yaml.safe_load(_h) + _pb_kb[_pd["capability_id"]] = _pd +_pbs = playbooks_for_plan(_opt, _pb_kb) # chain Roadmap -> Playbook over the SAME delta +_have = [p for p in _pbs if p.status != "missing"] +_miss = [p for p in _pbs if p.status == "missing"] +w("**Reise pro Maßnahme (aus der Roadmap):** %d von %d Maßnahmen haben ein Playbook; %d brauchen noch Inhalt (Knowledge Acquisition)." % (len(_have), len(_pbs), len(_miss))) +w("") +_show = next((p for p in _pbs if p.capability_id == "sbom_creation"), None) +if _show: + w("**Beispielreise — `%s`** _(%s, schließt %s)_" % (_show.capability_id, _show.status, "+".join(_show.closes_regulations) or "—")) + w("> **Warum?** %s" % _show.why.strip()) + w("- **Tools:** %s" % ", ".join(_show.tools)) + w("- **Prozess:** %s" % " → ".join(s.title for s in _show.process_steps)) + w("- **Nachweise:** %s" % ", ".join(_show.expected_evidence)) + w("- **Wie andere es tun:** %s" % _show.how_others_do_it.strip()) + w("") +w("**Roadmap → Implementation (Top-Maßnahmen nach Hebel):**") +w("") +w("| Maßnahme | Hebel | schließt | Playbook |") +w("|---|---|---|---|") +for _p in _pbs[:6]: + w("| `%s` | %d | %s | %s |" % (_p.capability_id, _p.leverage, "+".join(_p.closes_regulations) or "—", + ("✓ " + _p.status) if _p.status != "missing" else "**fehlt (Inhalt)**")) +w("") +w("_Derselbe Capability-Strang, neuer Renderer: aus Diagnose wird Beratung. Die `fehlt`-Einträge sind der ehrliche Content-Backlog (höchster Hebel zuerst befüllen)._") +w("") +coverage_table([ + ("Implementation Playbook Renderer", "PASS", "Reise pro Capability (why/tools/process/evidence/controls)"), + ("Roadmap → Playbook (Verkettung)", "PASS", "%d/%d Maßnahmen mit Playbook" % (len(_have), len(_pbs))), + ("Playbook-Inhalt (Knowledge)", "TODO" if _miss else "PASS", "%d Capabilities brauchen noch Inhalt" % len(_miss)), +]) + # ── Epics + roll-up ─────────────────────────────────────────────────────── w("## Gaps → Epics (Backlog — nur erfasst, NICHT implementiert)") w("") diff --git a/backend-compliance/reference_scenarios/reference_scenario_suite_v1.md b/backend-compliance/reference_scenarios/reference_scenario_suite_v1.md index 7dc9ae6d..43bd50e7 100644 --- a/backend-compliance/reference_scenarios/reference_scenario_suite_v1.md +++ b/backend-compliance/reference_scenarios/reference_scenario_suite_v1.md @@ -262,6 +262,40 @@ _Eine Wahrheit, zwei Renderer: dasselbe Capability Delta liefert dem Auditor **F | Roadmap/Management Renderer (Hebel) | **PASS** | 16 identifizierte Anforderungen aus 2 Regelwerken -> 12 Massnahmen (Ø Hebel 1.3). | | Budget-Priorisierung | **PASS** | Top-5 → 56% der identifizierten Anforderungen | +## Implementation Playbook — wie komme ich dort hin? (Berater-Renderer) + +_Nach „was fehlt?" (Delta) und „womit anfangen?" (Hebel) die nächste Ebene: **wie umsetzen?** Pro Maßnahme eine komplette Reise aus kuratiertem Wissen + Hebel + (injizierten) Execution-Links. Inhalt ist der Engpass, nicht die Software._ + +**Reise pro Maßnahme (aus der Roadmap):** 2 von 12 Maßnahmen haben ein Playbook; 10 brauchen noch Inhalt (Knowledge Acquisition). + +**Beispielreise — `sbom_creation`** _(draft, schließt CRA)_ +> **Warum?** Der CRA verlangt von Herstellern, die Komponenten ihres Produkts zu identifizieren und zu dokumentieren (Schwachstellenbehandlung, Annex I Teil II). Eine SBOM ist das maschinenlesbare Inventar aller (auch transitiven) Software-Bestandteile mit Version und Lizenz. Ohne SBOM kann niemand verlässlich sagen, welche Produkte von einer neuen Schwachstelle (CVE) betroffen sind — SBOM ist damit die Voraussetzung für Schwachstellenüberwachung, Security-Updates und Meldepflichten. +- **Tools:** CycloneDX (Format, OWASP), SPDX (Format, Linux Foundation), Syft (Generator, Container/Filesystem), cdxgen (Generator, Multi-Ökosystem), Trivy (Generator + Scan), OWASP Dependency-Track (Verwaltung + kontinuierliche Überwachung) +- **Prozess:** Format festlegen → Automatisch im Build erzeugen → Transitive Abhängigkeiten + Versionen + Lizenzen erfassen → Pro Release versionieren und ablegen → An Schwachstellenüberwachung anbinden → Release-Gate setzen +- **Nachweise:** Maschinenlesbare SBOM (CycloneDX/SPDX) je ausgelieferter Produktversion, CI-Job-Konfiguration, die die SBOM automatisch erzeugt, Dependency-Track-Projekt (oder gleichwertig) mit laufender Überwachung +- **Wie andere es tun:** Verbreitete Praxis: CycloneDX automatisch in der CI via Syft/cdxgen erzeugen und nach OWASP Dependency-Track pushen, das kontinuierlich gegen neue CVEs prüft. Reifere Organisationen gaten Releases auf das Vorhandensein einer SBOM und teilen sie auf Anfrage mit Kunden/Behörden. + +**Roadmap → Implementation (Top-Maßnahmen nach Hebel):** + +| Maßnahme | Hebel | schließt | Playbook | +|---|---|---|---| +| `ce_conformity_assessment_and_technical_documentation` | 2 | CRA+MaschinenVO | **fehlt (Inhalt)** | +| `product_cyber_risk_assessment` | 2 | CRA+MaschinenVO | **fehlt (Inhalt)** | +| `protection_against_corruption_of_safety_functions` | 2 | CRA+MaschinenVO | **fehlt (Inhalt)** | +| `secure_signed_update_distribution` | 2 | CRA+MaschinenVO | **fehlt (Inhalt)** | +| `coordinated_vulnerability_disclosure` | 1 | CRA | ✓ draft | +| `exploited_vuln_and_incident_reporting` | 1 | CRA | **fehlt (Inhalt)** | + +_Derselbe Capability-Strang, neuer Renderer: aus Diagnose wird Beratung. Die `fehlt`-Einträge sind der ehrliche Content-Backlog (höchster Hebel zuerst befüllen)._ + +**Architecture Coverage** + +| Layer | Status | Hinweis | +|---|---|---| +| Implementation Playbook Renderer | **PASS** | Reise pro Capability (why/tools/process/evidence/controls) | +| Roadmap → Playbook (Verkettung) | **PASS** | 2/12 Maßnahmen mit Playbook | +| Playbook-Inhalt (Knowledge) | **TODO** | 10 Capabilities brauchen noch Inhalt | + ## Gaps → Epics (Backlog — nur erfasst, NICHT implementiert) | Epic | Titel | schliesst Coverage-Luecke | @@ -273,6 +307,6 @@ _Eine Wahrheit, zwei Renderer: dasselbe Capability Delta liefert dem Auditor **F ## Suite-Status (Roll-up) -- Coverage-Zellen gesamt: **32** -- PASS: **24** · PARTIAL: 3 · UNSUPPORTED: 1 · TODO: 3 · N/A: 1 · NEEDS_FACTS: 0 +- Coverage-Zellen gesamt: **35** +- PASS: **26** · PARTIAL: 3 · UNSUPPORTED: 1 · TODO: 4 · N/A: 1 · NEEDS_FACTS: 0 - Fortschritt = PASS-Anteil steigt, wenn Epics RS-001…004 landen (objektiver Maßstab, kein LOC). diff --git a/backend-compliance/tests/test_playbook.py b/backend-compliance/tests/test_playbook.py new file mode 100644 index 00000000..158997f5 --- /dev/null +++ b/backend-compliance/tests/test_playbook.py @@ -0,0 +1,88 @@ +"""Tests for the Implementation Playbook renderer (the Berater view, "wie komme ich dort hin?"). + +Acceptance: for one capability, assemble the journey (why / closes-which-regulations / tools / +process / evidence / controls) from curated knowledge + leverage + injected Execution links; chain +the Optimization Roadmap into per-measure playbooks; surface capabilities without content as honest +`missing` stubs. Curated content is an expert draft, never normative. +""" + +from __future__ import annotations + +from compliance.optimization import regulatory_leverage +from compliance.playbook import Playbook, build_playbook, playbooks_for_plan + +SBOM = { + "title": "SBOM aufbauen", + "why": "CRA verlangt ein Komponenten-Inventar.", + "tools": ["CycloneDX", "Syft"], + "process_steps": [{"title": "Format wählen", "detail": "CycloneDX"}, {"title": "In CI erzeugen", "detail": ""}], + "expected_evidence": ["sbom_per_release"], + "how_others_do_it": "CI + Dependency-Track.", + "status": "draft", +} + + +def test_build_playbook_full_journey(): + pb = build_playbook("sbom_creation", SBOM, closes_regulations=["CRA", "MaschinenVO"], control_links=["component_inventory"]) + assert pb.title == "SBOM aufbauen" and pb.status == "draft" + assert pb.closes_regulations == ["CRA", "MaschinenVO"] and pb.leverage == 2 + assert pb.tools == ["CycloneDX", "Syft"] + assert [s.order for s in pb.process_steps] == [1, 2] + assert pb.process_steps[0].title == "Format wählen" + assert pb.expected_evidence == ["sbom_per_release"] + assert pb.controls == ["component_inventory"] # injected from Execution + assert pb.disclaimer # always carries the expert-draft caveat + + +def test_missing_knowledge_is_honest_stub(): + pb = build_playbook("product_cyber_risk_assessment", None, closes_regulations=["CRA", "MaschinenVO"]) + assert pb.status == "missing" # the content-owed signal + assert pb.leverage == 2 and pb.closes_regulations == ["CRA", "MaschinenVO"] # leverage still known + assert pb.tools == [] and pb.process_steps == [] + assert "Knowledge Acquisition" in pb.why + + +def test_closes_regulations_deduped_and_sorted(): + pb = build_playbook("x", SBOM, closes_regulations=["MaschinenVO", "CRA", "CRA"]) + assert pb.closes_regulations == ["CRA", "MaschinenVO"] and pb.leverage == 2 + + +def test_controls_default_empty_no_execution_data(): + pb = build_playbook("x", SBOM, closes_regulations=["CRA"]) + assert pb.controls == [] # no Execution data unless injected + + +def test_playbooks_for_plan_orders_by_leverage_and_stubs_missing(): + caps = {"sbom_creation": ["CRA"], "pcra": ["CRA", "MaschinenVO"], "guards": ["MaschinenVO"]} + plan = regulatory_leverage(caps) + pbs = playbooks_for_plan(plan, {"sbom_creation": SBOM}, top_k=3) + assert [p.capability_id for p in pbs][0] == "pcra" # highest leverage first + by = {p.capability_id: p for p in pbs} + assert by["pcra"].status == "missing" and by["pcra"].leverage == 2 + assert by["sbom_creation"].status == "draft" # has content + assert by["guards"].status == "missing" + + +def test_playbooks_for_plan_top_k_and_injected_controls(): + caps = {"a": ["CRA", "MaschinenVO"], "b": ["CRA"], "c": ["CRA"]} + plan = regulatory_leverage(caps) + pbs = playbooks_for_plan(plan, {}, top_k=1, control_links_by_cap={"a": ["ctrl_1"]}) + assert len(pbs) == 1 and pbs[0].capability_id == "a" + assert pbs[0].controls == ["ctrl_1"] + + +def test_playbooks_for_plan_empty(): + plan = regulatory_leverage({}) + assert playbooks_for_plan(plan, {}) == [] + + +def test_deterministic(): + caps = {"a": ["CRA", "MaschinenVO"], "b": ["CRA"]} + plan = regulatory_leverage(caps) + a = [p.capability_id for p in playbooks_for_plan(plan, {})] + b = [p.capability_id for p in playbooks_for_plan(plan, {})] + assert a == b + + +def test_returns_playbook_type(): + assert isinstance(build_playbook("x", None), Playbook) diff --git a/docs-src/architecture/adr/ADR-004-implementation-playbooks.md b/docs-src/architecture/adr/ADR-004-implementation-playbooks.md new file mode 100644 index 00000000..cb123f4c --- /dev/null +++ b/docs-src/architecture/adr/ADR-004-implementation-playbooks.md @@ -0,0 +1,51 @@ +# ADR-004: Implementation Playbooks — a renderer plus a knowledge layer + +- **Status:** Accepted +- **Datum:** 2026-06-27 +- **Typ:** Architektur-Entscheidung +- **Bezug:** [ADR-003](ADR-003-capability-delta-engine-with-renderers.md), [ADR-002](ADR-002-transition-is-data-not-architecture.md), Architektur-Freeze v1.0, [[transition-reasoning]] + +## Kontext + +BreakPilot beantwortet inzwischen drei Fragen: *Was gilt?* (Reasoning), *Was fehlt?* +(Capability Delta), *Womit anfangen?* (Optimization). Danach fragt der Geschäftsführer fast immer: +**„Wie komme ich dort hin?"** — nicht *was*, sondern *wie* (z. B. „Top-Maßnahme: PSIRT — und wie +baue ich einen PSIRT auf? Welche Tools? Wie machen das andere?"). + +Diese Umsetzungs-Ebene ist weder Reasoning noch Gap noch Roadmap. Das Risiko: sie als neue Engine +zu bauen — obwohl ~95 % der Daten bereits existieren (`Capability → Procedure → Control → Evidence`). +Sie muss nur **anders gerendert** werden. + +## Entscheidung + +1. **Ein Implementation Playbook ist ein RENDERER über einer Capability** (`compliance/playbook`), + kein neuer Datenpfad. Es setzt die Reise zusammen aus: kuratiertem Playbook-Wissen + der + regulatorischen **Leverage** (welche Regelwerke eine umgesetzte Capability schließt, aus der + Optimization) + **injizierten** Procedure/Control/Evidence-Links (Execution-owned). + +2. **Playbook ≠ regulatorische Domäne (bewusste Unterscheidung):** + - Ein **Playbook** ist BreakPilots EIGENE Wissensschicht (`Capability → empfohlene Vorgehensweise + → Tools → Prozess → typische Nachweise → Controls`). Es führt KEIN neues Regelwerk ein. + - Eine **regulatorische Domäne** (z. B. ISO 14001 → Umweltrecht) ist neues *regulatorisches* + Wissen (Obligations, Anwendbarkeit), Eigentum von Legal Knowledge / Execution. + Beide skalieren unabhängig — und jede neue Domäne dockt sofort an denselben Playbook-Mechanismus an. + +3. **Der Engpass ist INHALT, nicht Software.** Der Renderer ist klein und fertig; der Wert wächst mit + Zahl und Tiefe der Playbooks. Eine Capability ohne Playbook rendert als `status: missing`-Stub — + das ehrliche „Content owed"-Signal. Playbook-Inhalt ist Reasoning-**Knowledge-Acquisition** + (wie `knowledge/transition_patterns/`): KI liefert den Erstentwurf, BreakPilot reviewt/versioniert/besitzt. + +## Konsequenzen + +- **Aus Diagnose wird Beratung:** derselbe Capability-Strang, der Auditor-Fragen (Interview) und + GF-Maßnahmen (Roadmap) liefert, liefert nun auch die Umsetzungsreise (Playbook). Konsistent mit + ADR-003 (ein Modell, viele Renderer). +- **Reifegrad + Ehrlichkeit:** Playbook-Inhalt ist `draft → reviewed → validated → proven`, ein + **Experten-Entwurf, KEINE normative Anforderung**; Tools/Schritte sind Empfehlungen. Controls + werden aus Execution injiziert — keine Execution-Daten im Reasoning-Produktcode. +- **Freeze-konform:** kein neues Metamodell, kein neuer Graph, kein neuer Corpus — `Playbook` ist + eine abgeleitete Sicht (computed-not-stored). Abhängigkeit `playbook → optimization → + transition_reasoning` ist azyklisch; die Capability Delta Engine bleibt hermetisch. +- **Priorisierung:** Playbooks kommen VOR neuen Domänen (ISO 14001), damit jede künftige Capability + sofort eine Umsetzungsreise bekommt. Content-Backlog = höchster Hebel zuerst. +- Diese ADR ist non-runtime → kein Deploy (siehe [ADR-001](ADR-001-runtime-deploy-policy.md)).