"""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)