feat(playbook): Implementation Playbooks — the Berater renderer ("wie komme ich dort hin?")

Roadmap item 4. After WHAT applies / WHAT is missing / WHICH first, the GF asks HOW. The
Implementation Playbook renders, for one capability, the full journey — why / which regulations
it closes / tools / process / evidence / controls — and chains the Optimization Roadmap into
per-measure playbooks. Another renderer over the same Capability spine (ADR-003/004), not a new
engine: ~95% of the data already exists, it just needs a different rendering.

- compliance/playbook/: build_playbook() + playbooks_for_plan() (chains optimization -> playbook,
  acyclic; reuses leverage for "closes which regulations"). Capabilities without curated content
  render as honest status:missing stubs — the content-owed signal.
- knowledge/implementation_playbooks/: curated knowledge layer (Reasoning Knowledge Acquisition),
  two deep expert drafts (SBOM, CVD/PSIRT, status draft, expert-draft-not-normative) + README.
  The bottleneck is now CONTENT, not software; Playbook (own knowledge) != regulatory domain.
- ADR-004: Implementation Playbooks = renderer + knowledge layer; content is the bottleneck.
- reference suite: "Implementation Playbook" section renders the SBOM journey + Roadmap->Playbook
  table (high-leverage caps flagged "fehlt (Inhalt)" — content backlog, highest leverage first).
- refactor: extracted markdown helpers to reference_scenarios/_helpers.py to keep generate.py
  under the 500-LOC budget.

9 playbook tests (40 with optimization+transition+company), mypy --strict clean, check-loc 0.
Product code with no app caller + knowledge/ADR/reference = non-runtime -> no deploy (ADR-001).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-27 10:38:13 +02:00
parent 50d88d611d
commit 78f0ffa9de
11 changed files with 610 additions and 52 deletions
@@ -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",
]
@@ -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
]
@@ -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
@@ -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`.
@@ -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).
@@ -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).
@@ -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"
@@ -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("")
@@ -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).
+88
View File
@@ -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)
@@ -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)).