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:
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user