"""
Empirical Hayat Bridge Prompt-Implementation Alignment Tests (v3.0).

Verifies that the instruction files (dusun.prompt.md,
dusun-field.instructions.md, dusun-check.prompt.md) reference
keys and structures that ACTUALLY EXIST in the server-side implementation.

These tests close the most critical gap: instruction files promise the LLM
specific keys in tool returns, sessions, and cascades — these tests prove
those promises are fulfilled.

Test categories:
  1. _framework_context key completeness per tool
  2. HAYAT BRIDGE PROTOCOL 9-step checklist alignment
  3. Instruction file content verification (shaping_directives refs exist)
  4. Data file sync verification (fw_server/data/ = .github/)
  5. Enforcement hierarchy in directives
  6. Session keys match protocol instructions
  7. Cascade + cascade_forward keys match protocol
  8. boot_seed presence in dusun returns
  9. Cross-file consistency (fw-tools schema = actual return)
"""

from __future__ import annotations

import json
import os
import pathlib
import re
from typing import Dict, List, Set

import pytest

from fw_server.session import get_ledger, reset_ledger
from fw_server.context import build_framework_context, _TOOL_DIRECTIVES


# ── Paths ────────────────────────────────────────────────────────────

_ROOT = pathlib.Path(__file__).resolve().parent.parent

_GITHUB_PROMPTS = _ROOT / ".github" / "prompts"
_GITHUB_INSTRUCTIONS = _ROOT / ".github" / "instructions"
_DATA_DIR = _ROOT / "fw_server" / "data"

_FW_PROMPT = _GITHUB_PROMPTS / "dusun.prompt.md"
_SELF_CHECK = _GITHUB_PROMPTS / "dusun-check.prompt.md"
_FIELD_INSTRUCTIONS = _GITHUB_INSTRUCTIONS / "dusun-field.instructions.md"


# ── Helpers ──────────────────────────────────────────────────────────

def _call_dusun(text: str, context: str = "") -> dict:
    import asyncio
    from fw_server.server import dusun_tool as _dt
    raw = asyncio.run(_dt(text=text, context=context or None, mode="analyze"))
    return json.loads(raw)


def _call_validate_stage(stage: str, summary: str, content: str = "") -> dict:
    from fw_server.server import validate_stage as _vs
    raw = _vs(stage=stage, summary=summary, content=content or None)
    return json.loads(raw)


def _call_check_kavaid(composite: float = 0.0, lens_scores: dict = None) -> dict:
    from fw_server.server import check_kavaid as _ck
    raw = _ck(composite_score=composite, lens_scores=lens_scores)
    return json.loads(raw)


def _call_bileshke() -> dict:
    from fw_server.server import run_bileshke_pipeline as _bl
    raw = _bl()
    return json.loads(raw)


def _read_file(path: pathlib.Path) -> str:
    """Read a text file and return its content."""
    return path.read_text(encoding="utf-8")


# ── Sample texts for real tool calls ─────────────────────────────────

_BOOT_TEXT = (
    "Analyze how a microservice architecture implements the three-phase "
    "actualization pattern (AX2-AX4): distinction of service boundaries "
    "(İlim), selection of communication protocols (İrade), and "
    "actualization through deployment (Kudret).  The integration "
    "prerequisite (AX5/Hayat) manifests as the service mesh."
)

_STAGE_TEXT = (
    "The system exhibits holographic architecture (AX37): each microservice "
    "reflects the overall system structure.  Convergence is bounded (KV₄) "
    "and the seven lenses produce independent readings (KV₇)."
)


# =====================================================================
# CLASS 1: _framework_context Key Completeness Per Tool
# =====================================================================

class TestFrameworkContextKeyCompleteness:
    """Verify that each core tool returns _framework_context with ALL keys
    that the instruction files tell the LLM to read."""

    # Keys mentioned in HAYAT BRIDGE PROTOCOL 9-step checklist
    # (dusun.prompt.md §HAYAT BRIDGE PROTOCOL)
    PROTOCOL_MANDATORY_KEYS = {
        "directives",          # Step 2 — list of MUST/MUST-NOT strings
    }

    # Keys that appear CONDITIONALLY (present when relevant data exists)
    PROTOCOL_CONDITIONAL_KEYS = {
        "grade_ceiling",       # Step 3 — max grade you may claim
        "gate_enforcement",    # Step 4 — FAIL/WARN enforcement
        "session",             # Step 5 — cumulative pipeline state
        "cascade",             # Step 6 — degradation_level
        "cascade_forward",     # Step 7 — forward_directives
        "kv4_violation",       # Step 8 — composite ≥ 0.95
        "kavaid_warning",      # Step 9 — <8/8 kavaid
    }

    @pytest.fixture(autouse=True)
    def _reset(self):
        reset_ledger()
        yield

    def test_dusun_returns_framework_context(self):
        """dusun() MUST return _framework_context with at least directives."""
        result = _call_dusun(_BOOT_TEXT)
        assert "_framework_context" in result, (
            "dusun() missing _framework_context — instruction files promise it"
        )
        ctx = result["_framework_context"]
        assert "directives" in ctx, (
            "_framework_context.directives missing — HAYAT BRIDGE step 2 unresolvable"
        )
        assert isinstance(ctx["directives"], list)
        assert len(ctx["directives"]) > 0

    def test_validate_stage_returns_framework_context(self):
        """validate_stage() MUST return _framework_context."""
        result = _call_validate_stage("1", "Testing output translation")
        assert "_framework_context" in result
        ctx = result["_framework_context"]
        assert "directives" in ctx
        assert isinstance(ctx["directives"], list)

    def test_check_kavaid_returns_framework_context(self):
        """check_kavaid() MUST return _framework_context."""
        result = _call_check_kavaid(composite=0.5)
        assert "_framework_context" in result
        ctx = result["_framework_context"]
        assert "directives" in ctx

    def test_bileshke_returns_framework_context(self):
        """run_bileshke_pipeline() MUST return _framework_context."""
        result = _call_bileshke()
        assert "_framework_context" in result
        ctx = result["_framework_context"]
        assert "directives" in ctx

    def test_dusun_has_grade_ceiling(self):
        """dusun() should populate grade_ceiling (instruction files reference it)."""
        result = _call_dusun(_BOOT_TEXT)
        ctx = result["_framework_context"]
        # dusun always produces a grade
        assert "grade_ceiling" in ctx, (
            "grade_ceiling missing from dusun _framework_context — "
            "dusun-router.instructions.md GRADE SOURCE RULES references it"
        )
        assert "grade_ceiling_source" in ctx

    def test_validate_stage_has_gate_enforcement(self):
        """validate_stage() should include gate_verdict and potentially gate_enforcement."""
        result = _call_validate_stage("5", "Quality framework analysis")
        ctx = result["_framework_context"]
        assert "gate_verdict" in ctx, (
            "gate_verdict missing — HAYAT BRIDGE step 4 unresolvable"
        )

    def test_dusun_context_has_session_after_multiple_calls(self):
        """After multiple calls, _framework_context.session should be present."""
        _call_dusun(_BOOT_TEXT)  # 1st call
        result = _call_dusun(_STAGE_TEXT)  # 2nd call
        ctx = result["_framework_context"]
        assert "session" in ctx, (
            "session key missing after multiple calls — HAYAT BRIDGE step 5 broken"
        )

    def test_cascade_appears_after_degradation(self):
        """After a WARN/FAIL gate, cascade should show in _framework_context."""
        # Force a call sequence — validate_stage records gate verdicts
        _call_dusun(_BOOT_TEXT)
        _call_validate_stage("1", "Testing cascade")
        _call_validate_stage("3", "Ontological framework")
        result = _call_validate_stage("5", "Quality framework")
        ctx = result["_framework_context"]
        # cascade should be present if session tracking is active
        assert "cascade" in ctx, (
            "cascade missing from _framework_context — HAYAT BRIDGE step 6 unresolvable"
        )


# =====================================================================
# CLASS 2: HAYAT BRIDGE PROTOCOL 9-Step Alignment
# =====================================================================

class TestHayatBridgeProtocolAlignment:
    """Verify that every key the HAYAT BRIDGE PROTOCOL checklist mentions
    is actually producible by build_framework_context()."""

    def test_all_9_checklist_keys_are_producible(self):
        """build_framework_context() can produce every key from the 9-step checklist."""
        # Construct a context with ALL conditional fields filled
        from fw_server.session import SessionLedger
        ledger = SessionLedger()
        ledger.record_call("dusun", grade="Tasdik", gate="WARN", anomalies=["test"])
        ledger.record_call("validate_stage", grade="Tasdik", gate="FAIL", anomalies=["a2"])

        ctx = build_framework_context(
            tool_name="dusun",
            grade="Tasdik",
            gate="FAIL",
            composite_score=0.96,
            kavaid_pass_count=6,
            anomalies=["test-anomaly"],
            session_context=ledger.get_cumulative_context(),
            cascade_status=ledger.get_cascade_status(),
            cascade_forward=ledger.get_cascade_forward(),
        )

        # Step 2: directives
        assert "directives" in ctx
        # Step 3: grade_ceiling
        assert "grade_ceiling" in ctx
        # Step 4: gate_enforcement
        assert "gate_enforcement" in ctx
        # Step 5: session
        assert "session" in ctx
        # Step 6: cascade
        assert "cascade" in ctx
        # Step 7: cascade_forward
        assert "cascade_forward" in ctx
        # Step 8: kv4_violation
        assert "kv4_violation" in ctx, (
            "kv4_violation not produced even with composite=0.96 — HAYAT BRIDGE step 8 broken"
        )
        # Step 9: kavaid_warning
        assert "kavaid_warning" in ctx, (
            "kavaid_warning not produced with 6/8 passed — HAYAT BRIDGE step 9 broken"
        )

    def test_session_sub_keys_match_protocol(self):
        """session dict must contain keys the protocol says to check:
        call count, grade ceiling, gate history, tool sequence."""
        from fw_server.session import SessionLedger
        ledger = SessionLedger()
        ledger.record_call("dusun", grade="Tasdik", gate="PASS")

        session = ledger.get_cumulative_context()
        # Protocol step 5 says: "call count, grade ceiling, gate history, anomalies"
        assert "session_call_count" in session
        assert "grade_ceiling" in session
        assert "gate_history" in session
        assert "tool_sequence" in session

    def test_cascade_sub_keys_match_protocol(self):
        """cascade dict must have degradation_level as protocol step 6 says."""
        from fw_server.session import SessionLedger
        ledger = SessionLedger()
        ledger.record_call("validate_stage", grade="Tasdik", gate="WARN", anomalies=["x"])

        cascade = ledger.get_cascade_status()
        assert "degradation_level" in cascade, (
            "cascade.degradation_level missing — HAYAT BRIDGE step 6 references it"
        )

    def test_cascade_forward_sub_keys_match_protocol(self):
        """cascade_forward must have forward_directives as protocol step 7 says."""
        from fw_server.session import SessionLedger
        ledger = SessionLedger()
        ledger.record_call("validate_stage", grade="Tasdik", gate="FAIL", anomalies=["failure1"])

        forward = ledger.get_cascade_forward()
        assert "forward_directives" in forward, (
            "cascade_forward.forward_directives missing — HAYAT BRIDGE step 7 references it"
        )

    def test_enforcement_hierarchy_uses_must_language(self):
        """All directives and enforcement strings use MUST/MUST NOT/NEVER language."""
        ctx = build_framework_context(
            tool_name="dusun",
            grade="Tasdik",
            gate="FAIL",
            composite_score=0.96,
            kavaid_pass_count=5,
            anomalies=["anomaly1"],
        )

        enforcement_pattern = re.compile(r"MUST|NEVER|BINDING", re.IGNORECASE)

        # Check directives
        for d in ctx.get("directives", []):
            assert enforcement_pattern.search(d), (
                f"Directive lacks MUST/NEVER/BINDING language: {d!r}"
            )

        # Check gate_enforcement
        ge = ctx.get("gate_enforcement", "")
        if ge:
            assert enforcement_pattern.search(ge), (
                f"gate_enforcement lacks MUST language: {ge!r}"
            )

        # Check kv4_violation
        kv4 = ctx.get("kv4_violation", "")
        if kv4:
            assert enforcement_pattern.search(kv4), (
                f"kv4_violation lacks MUST language: {kv4!r}"
            )


# =====================================================================
# CLASS 3: Instruction File Content Verification
# =====================================================================

class TestInstructionFileContent:
    """Verify that all 3 instruction files contain expected v3.0 references."""

    @pytest.fixture(autouse=True)
    def _load_files(self):
        self.fw_prompt = _read_file(_FW_PROMPT)
        self.field = _read_file(_FIELD_INSTRUCTIONS)
        self.check = _read_file(_SELF_CHECK)

    # ─── dusun.prompt.md ────────────────────────────────────────────────

    def test_fw_prompt_has_dusun_directive(self):
        """dusun.prompt.md must contain /dusun invocation directive."""
        assert "/dusun" in self.fw_prompt

    def test_fw_prompt_mentions_shaping_directives(self):
        """dusun.prompt.md must mention shaping_directives."""
        assert "shaping_directives" in self.fw_prompt

    def test_fw_prompt_mentions_grade_ceiling(self):
        """dusun.prompt.md must reference grade_ceiling."""
        assert "grade_ceiling" in self.fw_prompt

    def test_fw_prompt_mentions_composite_score(self):
        """dusun.prompt.md must reference composite_score."""
        assert "composite_score" in self.fw_prompt

    def test_fw_prompt_mentions_path_classification(self):
        """dusun.prompt.md must reference P0–P4 paths."""
        for p in ["P0", "P1", "P2", "P3", "P4"]:
            assert p in self.fw_prompt, f"Missing path {p} in dusun.prompt.md"

    def test_fw_prompt_always_active_constraints(self):
        """dusun.prompt.md must list always-active constraints."""
        assert "ceiling" in self.fw_prompt.lower()
        assert "anomal" in self.fw_prompt.lower()

    # ─── dusun-field.instructions.md ────────────────────────────────────

    def test_field_has_apply_to_all(self):
        """dusun-field.instructions.md must have applyTo: '**'."""
        assert "applyTo" in self.field
        assert "'**'" in self.field

    def test_field_mentions_shaping_directives(self):
        """dusun-field.instructions.md must mention shaping_directives."""
        assert "shaping_directives" in self.field

    def test_field_mentions_grade_ceiling(self):
        """dusun-field.instructions.md must reference grade_ceiling."""
        assert "grade_ceiling" in self.field

    def test_field_mentions_dusun_tool(self):
        """dusun-field.instructions.md must reference dusun() tool."""
        assert "dusun()" in self.field

    def test_field_anti_patterns(self):
        """dusun-field.instructions.md must have What NOT to Do section."""
        assert "NOT" in self.field

    # ─── dusun-check.prompt.md ──────────────────────────────────────────

    def test_check_has_self_check_directive(self):
        """/self-check directive must exist in dusun-check.prompt.md."""
        assert "/self-check" in self.check

    def test_check_mentions_shaping_directives(self):
        """dusun-check.prompt.md must reference shaping_directives."""
        assert "shaping_directives" in self.check

    def test_check_mentions_dusun_tool(self):
        """dusun-check.prompt.md must call dusun() as diagnostic probe."""
        assert "dusun(" in self.check


# =====================================================================
# CLASS 4: Data File Sync Verification
# =====================================================================

class TestDataFileSync:
    """Verify fw_server/data/ copies match canonical .github/ files."""

    _SYNC_PAIRS = [
        ("dusun.prompt.md", _FW_PROMPT),
        ("dusun-field.instructions.md", _FIELD_INSTRUCTIONS),
        ("dusun-check.prompt.md", _SELF_CHECK),
    ]

    @pytest.mark.parametrize("data_name,canonical_path", _SYNC_PAIRS)
    def test_data_file_matches_canonical(self, data_name, canonical_path):
        """fw_server/data/<file> must be byte-identical to canonical file."""
        data_path = _DATA_DIR / data_name
        if not data_path.exists():
            pytest.skip(f"{data_name} not in fw_server/data/")
        if not canonical_path.exists():
            pytest.skip(f"Canonical {canonical_path.name} not found")

        canonical = canonical_path.read_text(encoding="utf-8")
        data_copy = data_path.read_text(encoding="utf-8")
        assert data_copy == canonical, (
            f"fw_server/data/{data_name} is OUT OF SYNC with "
            f".github/ canonical. Update with: copy canonical → data."
        )


# =====================================================================
# CLASS 5: boot_seed Presence in dusun Returns
# =====================================================================

class TestBootSeedPresence:
    """Verify dusun() returns boot_seed as instruction files promise."""

    @pytest.fixture(autouse=True)
    def _reset(self):
        reset_ledger()
        yield

    def test_dusun_returns_boot_seed(self):
        """dusun() must return boot_seed — referenced in
        dusun-engagement.instructions.md T1 Boot Call."""
        result = _call_dusun(_BOOT_TEXT)
        assert "boot_seed" in result, (
            "dusun() missing boot_seed — "
            "dusun-engagement.instructions.md step 2 promises it"
        )

    def test_boot_seed_contains_hints_content(self):
        """boot_seed should contain hints as framework-engagement says:
        'If boot_seed is present, consult its hints for analysis direction.'
        boot_seed is a string — hints are embedded textual sections."""
        result = _call_dusun(_BOOT_TEXT)
        seed = result.get("boot_seed", "")
        assert isinstance(seed, str)
        assert len(seed) > 100, "boot_seed is too short to contain useful hints"
        # The string should contain structural hint content
        assert "HINT" in seed.upper() or "PATTERN" in seed.upper(), (
            "boot_seed lacks hint/pattern content — framework-engagement says "
            "'consult its hints for analysis direction'"
        )


# =====================================================================
# CLASS 6: _TOOL_DIRECTIVES Coverage
# =====================================================================

class TestToolDirectivesCoverage:
    """Verify that _TOOL_DIRECTIVES covers all 4 core tools."""

    CORE_TOOLS = {"dusun", "validate_stage", "run_bileshke_pipeline", "check_kavaid"}

    def test_all_core_tools_have_directives(self):
        """Every core tool name must have an entry in _TOOL_DIRECTIVES."""
        for tool in self.CORE_TOOLS:
            assert tool in _TOOL_DIRECTIVES, (
                f"{tool} missing from _TOOL_DIRECTIVES"
            )

    def test_directives_are_nonempty(self):
        """Each tool's directive list must be non-empty."""
        for tool in self.CORE_TOOLS:
            directives = _TOOL_DIRECTIVES.get(tool, [])
            assert len(directives) > 0, (
                f"{tool} has empty directives"
            )

    def test_all_directives_use_enforcement_language(self):
        """Every directive string must contain MUST, MUST NOT, or NEVER."""
        pattern = re.compile(r"MUST|NEVER", re.IGNORECASE)
        for tool, directives in _TOOL_DIRECTIVES.items():
            for d in directives:
                assert pattern.search(d), (
                    f"{tool} directive lacks enforcement language: {d!r}"
                )


# =====================================================================
# CLASS 7: Cross-File Consistency — fw-tools Schema vs Actual Returns
# =====================================================================

class TestCrossFileConsistency:
    """Verify that dusun-tools.prompt.md's documented return schema for
    validate_stage matches what the actual tool returns."""

    @pytest.fixture(autouse=True)
    def _reset(self):
        reset_ledger()
        yield

    # Keys documented in dusun-tools.prompt.md validate_stage section
    FW_TOOLS_DOCUMENTED_KEYS = {
        "gate", "composite_score", "kavaid", "all_lens_scores",
        "relevant_lens_scores", "anomalies", "remediation",
        "ax57_compact", "_framework_context",
    }

    def test_validate_stage_return_has_all_documented_keys(self):
        """Every key documented in dusun-tools.prompt.md for validate_stage
        must be present in the actual return."""
        result = _call_validate_stage("1", "Output translation")
        for key in self.FW_TOOLS_DOCUMENTED_KEYS:
            assert key in result, (
                f"validate_stage missing '{key}' — "
                f"dusun-tools.prompt.md documents it in return schema"
            )

    def test_framework_context_sub_keys_match_fw_tools_docs(self):
        """dusun-tools.prompt.md says _framework_context Contains:
        directives, grade_ceiling, gate_enforcement, session, cascade,
        cascade_forward."""
        # Call with enough history to populate all fields
        _call_dusun(_BOOT_TEXT)
        result = _call_validate_stage("5", "Quality framework check")
        ctx = result["_framework_context"]

        # These are listed in the dusun-tools.prompt.md schema note
        assert "directives" in ctx
        assert "gate_verdict" in ctx  # gate_enforcement only if WARN/FAIL


# =====================================================================
# CLASS 8: Docstring Channel 1 Verification
# =====================================================================

class TestDocstringChannel1:
    """Verify that tool docstrings contain enforcement language (Channel 1)."""

    def _get_docstring(self, func_name: str) -> str:
        """Import a server function and return its docstring."""
        import fw_server.server as srv
        func = getattr(srv, func_name)
        return func.__doc__ or ""

    @pytest.mark.parametrize("func_name", [
        "dusun_tool", "validate_stage",
        "run_bileshke_pipeline", "check_kavaid",
    ])
    def test_docstring_contains_must(self, func_name):
        """Core tool docstrings MUST contain enforcement language."""
        doc = self._get_docstring(func_name)
        assert "MUST" in doc, (
            f"{func_name} docstring missing MUST — Channel 1 broken"
        )

    @pytest.mark.parametrize("func_name", [
        "dusun_tool", "validate_stage",
        "run_bileshke_pipeline", "check_kavaid",
    ])
    def test_docstring_mentions_framework_context(self, func_name):
        """Core tool docstrings MUST mention _framework_context."""
        doc = self._get_docstring(func_name)
        assert "_framework_context" in doc, (
            f"{func_name} docstring missing _framework_context — Channel 1 broken"
        )


# =====================================================================
# CLASS 9: End-to-End Prompt→Implementation Round-Trip
# =====================================================================

class TestEndToEndRoundTrip:
    """The ultimate alignment test: call every core tool and verify that
    EVERY key the HAYAT BRIDGE PROTOCOL tells the LLM to read is
    present in the combined returns from a realistic session."""

    @pytest.fixture(autouse=True)
    def _reset(self):
        reset_ledger()
        yield

    def test_full_session_produces_all_protocol_keys(self):
        """A realistic 4-tool session must produce every key referenced
        in the HAYAT BRIDGE PROTOCOL 9-step checklist at least once."""

        seen_keys: Set[str] = set()

        # 1. dusun — boot call
        r1 = _call_dusun(_BOOT_TEXT)
        ctx1 = r1.get("_framework_context", {})
        seen_keys.update(ctx1.keys())

        # 2. validate_stage
        r2 = _call_validate_stage("1", "Output translation check")
        ctx2 = r2.get("_framework_context", {})
        seen_keys.update(ctx2.keys())

        # 3. validate_stage (more history)
        r3 = _call_validate_stage("5", "Quality framework analysis")
        ctx3 = r3.get("_framework_context", {})
        seen_keys.update(ctx3.keys())

        # 4. check_kavaid
        r4 = _call_check_kavaid(composite=0.5)
        ctx4 = r4.get("_framework_context", {})
        seen_keys.update(ctx4.keys())

        # 5. bileshke
        r5 = _call_bileshke()
        ctx5 = r5.get("_framework_context", {})
        seen_keys.update(ctx5.keys())

        # Every protocol-critical key must appear in at least one return
        protocol_keys = {
            "directives",       # Step 2
            "grade_ceiling",    # Step 3
            "session",          # Step 5
            "cascade",          # Step 6
        }
        missing = protocol_keys - seen_keys
        assert not missing, (
            f"HAYAT BRIDGE PROTOCOL keys never produced in full session: {missing}"
        )

    def test_session_grade_ceiling_flows_from_dusun_to_subsequent_tools(self):
        """Grade ceiling set by dusun must propagate via session to later tools."""
        r1 = _call_dusun(_BOOT_TEXT)
        grade_from_dusun = r1.get("_framework_context", {}).get("grade_ceiling")
        assert grade_from_dusun is not None

        r2 = _call_validate_stage("3", "Ontological framework")
        session2 = r2.get("_framework_context", {}).get("session", {})
        session_ceiling = session2.get("grade_ceiling")
        assert session_ceiling is not None, (
            "session.grade_ceiling absent after dusun set a grade — "
            "grade ceiling not flowing through session"
        )

    def test_cascade_forward_directives_accumulate(self):
        """After anomalies, cascade_forward_directives should be present."""
        _call_dusun(_BOOT_TEXT)
        _call_validate_stage("1", "Stage with anomalies")
        _call_validate_stage("3", "Stage with more anomalies")
        _call_validate_stage("5", "Quality check")

        result = _call_validate_stage("9a", "Actualize")
        ctx = result.get("_framework_context", {})
        # cascade_forward should exist with forward_directives
        cf = ctx.get("cascade_forward")
        assert cf is not None, "cascade_forward absent after multi-stage pipeline"
        assert "forward_directives" in cf, "forward_directives key missing from cascade_forward"

    def test_boot_seed_and_framework_context_coexist_in_dusun(self):
        """dusun() must return BOTH boot_seed AND _framework_context."""
        result = _call_dusun(_BOOT_TEXT)
        assert "boot_seed" in result, "boot_seed missing"
        assert "_framework_context" in result, "_framework_context missing"
        # boot_seed is a string, _framework_context is a dict
        assert isinstance(result["boot_seed"], str)
        assert isinstance(result["_framework_context"], dict)

    def test_all_four_tools_share_same_session_counter(self):
        """All 4 core tools should increment the same session ledger."""
        _call_dusun(_BOOT_TEXT)                       # call 1
        _call_validate_stage("1", "test")             # call 2
        _call_check_kavaid(composite=0.3)             # call 3
        result = _call_bileshke()                     # call 4
        ctx = result.get("_framework_context", {})
        session = ctx.get("session", {})
        count = session.get("session_call_count", 0)
        assert count >= 4, (
            f"session_call_count={count} after 4 tool calls — "
            "not all tools are recording to session ledger"
        )
