b6cfc0a503
The bottleneck is not content, it is knowledge PRODUCTION. Instead of writing 200 playbooks by hand, generate drafts deterministically from data the software already owns, then have an expert review them. Mirrors the legal pipeline (Gesetz -> Parser -> Obligation -> Review) for BreakPilot's own knowledge: new Capability -> Registry -> Transition Pattern -> Playbook Draft Generator -> Expert Review -> versioned Playbook. - compliance/knowledge_production/: generate_playbook_draft(capability, requirement, control_links) + drafts_from_pattern(pattern) -> one PlaybookDraft per delta capability. Owned fields (why / closes_regulations / expected_evidence / typical_controls) are assembled with per-field provenance; the practitioner know-how (tools / process_steps / how_others) is left as an explicit TODO. - DraftStatus lifecycle (Freigabestatus): draft_generated -> in_review -> reviewed -> validated -> proven. Deterministic, NO LLM in the core (any model enrichment stays offline/advisory/propose-only). - ADR-005: extends "the engine does not change, the corpus grows" with "and the corpus is not written by hand — it is deterministically prepared, then curated". - reference suite: "Knowledge Production" section turns the convergence pattern into 12 auto-assembled drafts (why/closes/evidence filled, tools/steps TODO) -> review 12 drafts, don't write 12 playbooks. 10 tests (50 with playbook/optimization/transition/company), mypy --strict clean, check-loc 0. Product code with no app caller + ADR/reference = non-runtime -> no deploy (ADR-001). Freeze-safe. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
90 lines
3.7 KiB
Python
90 lines
3.7 KiB
Python
"""Tests for Knowledge Production — the Playbook Draft Generator.
|
|
|
|
Acceptance: deterministically assemble a playbook DRAFT for a capability from a transition-pattern
|
|
delta requirement (why / closes / evidence with provenance), leaving practitioner know-how as an
|
|
explicit TODO; turn a whole pattern into one draft per delta capability. No LLM, fully deterministic.
|
|
The expert reviews drafts instead of writing from a blank page.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from compliance.knowledge_production import (
|
|
DraftStatus, PlaybookDraft, drafts_from_pattern, generate_playbook_draft,
|
|
)
|
|
|
|
REQ = {
|
|
"capability": "sbom_creation",
|
|
"why_asked": "CRA requires an SBOM; MaschinenVO does not.",
|
|
"covers_targets": ["CRA"],
|
|
"expected_evidence": ["sbom"],
|
|
}
|
|
CONV_REQ = {
|
|
"capability": "product_cyber_risk_assessment",
|
|
"why_asked": "Both require assessing cyber threats.",
|
|
"covers_targets": ["CRA", "MaschinenVO"],
|
|
"expected_evidence": ["product_risk_assessment"],
|
|
}
|
|
|
|
|
|
def test_assembles_owned_fields_with_provenance():
|
|
d = generate_playbook_draft("sbom_creation", REQ, control_links=["component_inventory"])
|
|
assert d.status == DraftStatus.DRAFT_GENERATED
|
|
assert d.why.startswith("CRA requires an SBOM")
|
|
assert d.closes_regulations == ["CRA"] and d.expected_evidence == ["sbom"]
|
|
assert d.typical_controls == ["component_inventory"]
|
|
assert d.provenance["why"] == "transition_pattern:why_asked"
|
|
assert d.provenance["closes_regulations"] == "leverage:covers_targets"
|
|
assert d.provenance["typical_controls"] == "execution:control_links"
|
|
|
|
|
|
def test_soft_fields_are_todo():
|
|
d = generate_playbook_draft("sbom_creation", REQ)
|
|
assert d.todo == ["tools", "process_steps", "how_others_do_it"] # practitioner know-how owed
|
|
|
|
|
|
def test_missing_owned_fields_go_to_todo():
|
|
d = generate_playbook_draft("x", {})
|
|
assert "why" in d.todo and "expected_evidence" in d.todo
|
|
assert d.closes_regulations == [] and d.typical_controls == []
|
|
assert d.status == DraftStatus.DRAFT_GENERATED
|
|
|
|
|
|
def test_missing_because_fallback_for_why():
|
|
d = generate_playbook_draft("x", {"missing_because": "no analogue in ISO 27001"})
|
|
assert d.why == "no analogue in ISO 27001" and "why" not in d.todo
|
|
|
|
|
|
def test_closes_deduped_sorted_and_title_humanised():
|
|
d = generate_playbook_draft("secure_signed_update_distribution", {"covers_targets": ["MaschinenVO", "CRA", "CRA"]})
|
|
assert d.closes_regulations == ["CRA", "MaschinenVO"]
|
|
assert d.title == "secure signed update distribution"
|
|
|
|
|
|
def test_controls_default_empty_no_execution_data():
|
|
d = generate_playbook_draft("x", REQ)
|
|
assert d.typical_controls == [] # nothing injected -> empty
|
|
|
|
|
|
def test_drafts_from_pattern_one_per_delta_in_order():
|
|
pattern = {"delta_requirements": [REQ, CONV_REQ]}
|
|
drafts = drafts_from_pattern(pattern)
|
|
assert [d.capability_id for d in drafts] == ["sbom_creation", "product_cyber_risk_assessment"]
|
|
assert drafts[1].closes_regulations == ["CRA", "MaschinenVO"] # leverage 2 carried through
|
|
|
|
|
|
def test_drafts_from_pattern_injects_controls_and_skips_unnamed():
|
|
pattern = {"delta_requirements": [REQ, {"why_asked": "no capability key"}]}
|
|
drafts = drafts_from_pattern(pattern, control_links_by_cap={"sbom_creation": ["c1"]})
|
|
assert len(drafts) == 1 and drafts[0].typical_controls == ["c1"] # entry without capability skipped
|
|
|
|
|
|
def test_deterministic():
|
|
pattern = {"delta_requirements": [REQ, CONV_REQ]}
|
|
a = [(d.capability_id, d.why, tuple(d.todo)) for d in drafts_from_pattern(pattern)]
|
|
b = [(d.capability_id, d.why, tuple(d.todo)) for d in drafts_from_pattern(pattern)]
|
|
assert a == b
|
|
|
|
|
|
def test_returns_playbookdraft_type():
|
|
assert isinstance(generate_playbook_draft("x", REQ), PlaybookDraft)
|