"""
fw_server/dusun.py — Universal Neural Substrate for framework engagement.

This module implements the dusun() function: a single entry point that
fires ALL relevant framework analysis automatically for any input.

The name 'düşün' means 'think' in Turkish — every response passes through
this function, making framework engagement structural rather than optional.

Design:
  AX5:   dusun() is the Hayat (Integration Prerequisite) that activates
         all other attributes into coordinated action.
  AX2-4: Three-phase actualization — classify → plan → execute.
  KV₇:   Each lens fires in complete isolation (no shared state).
  AX52:  Multiplicative gate — zero in any dimension collapses the whole.
  AX57:  Every result carries transparency disclosure.
  T6/KV₄: Composite always < 1.0.
  T15:   Structural claims weighted above detail claims.
  AX56:  Grade never exceeds İlmelyakîn.
  AX64:  Framework-native analysis; no source text alteration.
  T14:   translation_hints embody ne ayn ne gayr (participatory non-identity).

Algorithm (7 steps):
  1. _detect_vocabulary()     — Scan input for framework-relevant vocabulary
  2. _auto_classify()         — Route to P0/P1/P2/P3/P4
  3. _build_fire_plan()       — Select which lenses to fire based on path
  4. _execute_fire_plan()     — Run lenses + bileshke + kavaid (per-lens try/except)
  5. _detect_patterns()       — Identify structural patterns in the analysis
  6. _build_translation_hints() — Produce T14 translation hints for non-P4
  7. _build_ax57()            — Compile transparency disclosure
"""

from __future__ import annotations

import re
from typing import Any, Dict, List, Optional, Tuple

from fw_server.tracer import trace, record


# ═══════════════════════════════════════════════════════════════════════
# VOCABULARY PATTERNS — content-aware claim type detection (§SC-3)
#
# These patterns detect framework-relevant vocabulary in user input.
# They are VOCABULARY DETECTORS, not semantic analyzers — they find
# words/references that indicate which lenses are relevant, not
# whether claims are true.  (Honest naming per audit finding.)
# ═══════════════════════════════════════════════════════════════════════

_VOCABULARY_PATTERNS: List[Dict[str, Any]] = [
    {
        "id": "axiom_ref",
        "regex": re.compile(r"\bAX\d{1,2}\b", re.IGNORECASE),
        "lenses": ["FOL"],
        "pattern_name": "axiom_reference",
    },
    {
        "id": "theorem_ref",
        "regex": re.compile(r"\bT\d{1,2}\b"),
        "lenses": ["FOL"],
        "pattern_name": "theorem_reference",
    },
    {
        "id": "chain_ref",
        "regex": re.compile(r"\bC[1-7]\b"),
        "lenses": ["FOL"],
        "pattern_name": "inference_chain",
    },
    {
        "id": "kavaid_ref",
        "regex": re.compile(r"\bKV[₁₂₃₄₅₆₇₈1-8]\b"),
        "lenses": ["FOL"],
        "pattern_name": "kavaid_reference",
    },
    {
        "id": "convergence",
        "regex": re.compile(
            r"convergence|composite.?score|bileshke|yakınlaşma",
            re.IGNORECASE,
        ),
        "lenses": ["Bayes"],
        "pattern_name": "convergence_claim",
    },
    {
        "id": "holographic",
        "regex": re.compile(
            r"holograph|B0[1-9]|B[12][0-9]|KV₆|KV6|seed.?omni|part.?whole",
            re.IGNORECASE,
        ),
        "lenses": ["Topoloji + Holografik"],
        "pattern_name": "holographic_claim",
    },
    {
        "id": "structural",
        "regex": re.compile(
            r"three.?phase|actuali[sz]ation|integration.?prerequisite|"
            r"multiplicative.?gate|transparent.?vessel|"
            r"unreachable.?supremum|structural.?incompleteness",
            re.IGNORECASE,
        ),
        "lenses": ["Ontoloji", "Mereoloji"],
        "pattern_name": "structural_pattern",
    },
    {
        "id": "grade",
        "regex": re.compile(
            r"Tasavvur|Tasdik|İlmelyakîn|Ilmelyakin|Hakkalyakîn|epistemic.?grade",
            re.IGNORECASE,
        ),
        "lenses": ["Bayes"],
        "pattern_name": "grade_claim",
    },
    {
        "id": "theological",
        "regex": re.compile(
            # --- Framework-specific Names/Attributes ---
            r"\b(Allah|God|divine|sacred|spiritual|theological|"
            r"Tecelli|Esmâ|Esma|Sifat|Kur\'?[aâ]n|Risale|tereşşuhat|"
            r"Zat|Vahidiyet|Ehadiyet|Fakr|Acz|Rahmet|Kudret|"
            r"Hayat|İlim|İrade|Sem|Basar|Kelam|"
            # --- Core Risale-i Nur vocabulary ---
            r"[İi]man|küfür|kufur|cennet|cehennem|"
            r"İsl[aâ]m(?:iyet)?|hidayet|dalalet|dal[aâ]let|"
            r"ibadet|namaz|sala[ht]|zek[aâ]t|oruç|hac|"
            r"tevhid|şirk|nübüvvet|peygamber|"
            r"ahiret|haşir|kıyamet|"
            r"melek|şeytan|cin|"
            r"sevap|günah|takva|"
            # --- Worship/prayer vocabulary ---
            r"tekbir|tehlil|tesbih|hamd|zikir|dua|"
            r"Bismillah|Elhamdülillah|"
            # --- Risale structural terms ---
            r"tuba|tûba|zakkum|saadet|nimet|rahmet|"
            r"hud[aâ]bin|nazar|tefekkür|marifet|"
            r"hikmet|inâyet|inayet|rububiyet|uluhiyet|"
            r"abd|ubûdiyet|ubudiyet|kulluk)\b",
            re.IGNORECASE,
        ),
        "lenses": ["Ontoloji", "FOL", "Topoloji + Holografik"],
        "pattern_name": "theological_vocabulary",
    },
    {
        "id": "diagnosis",
        "regex": re.compile(
            r"\b(bug|crash|fail|error|broken|wrong|defect|dysfunction|"
            r"not working|doesn\'t work)\b",
            re.IGNORECASE,
        ),
        "lenses": ["Mereoloji"],
        "pattern_name": "diagnosis_vocabulary",
    },
    # --- English academic vocabulary patterns (B3) ---
    {
        "id": "philosophical",
        "regex": re.compile(
            r"\b(metaphysics|epistemology|ontology|phenomenology|"
            r"hermeneutics|dialectic|teleology|deontology|"
            r"a\s?priori|a\s?posteriori|transcendental|"
            r"noumenon|noumena|phenomenon|qualia|"
            r"substance|essence|existence|being|"
            r"determinism|free\s?will|causation|"
            r"universals?|particulars?|predication|"
            r"realism|idealism|nominalism|empiricism|rationalism)\b",
            re.IGNORECASE,
        ),
        "lenses": ["Ontoloji", "Mereoloji"],
        "pattern_name": "philosophical_vocabulary",
    },
    {
        "id": "mathematical_structure",
        "regex": re.compile(
            r"\b(topology|topological|manifold|homeomorphism|"
            r"isomorphism|homomorphism|morphism|functor|"
            r"category\s?theory|group\s?theory|"
            r"lattice|partial\s?order|poset|"
            r"fiber\s?bundle|sheaf|sheaves|"
            r"algebraic\s?structure|ring|field|module|"
            r"symmetry\s?group|invariant|covariant|"
            r"fractal|self[\-\s]?similar|recursive\s?structure|"
            r"embedding|quotient|kernel|exact\s?sequence)\b",
            re.IGNORECASE,
        ),
        "lenses": ["KategoriTeorisi", "Topoloji + Holografik"],
        "pattern_name": "mathematical_structure_vocabulary",
    },
    {
        "id": "decision_theory",
        "regex": re.compile(
            r"\b(game\s?theory|decision\s?theory|rational\s?choice|"
            r"Nash\s?equilibrium|Pareto\s?optimal|"
            r"mechanism\s?design|auction\s?theory|"
            r"prisoner.?s?\s?dilemma|zero[\-\s]?sum|"
            r"payoff|utility\s?function|expected\s?utility|"
            r"strategic\s?interaction|dominant\s?strategy|"
            r"collective\s?action|principal[\-\s]?agent|"
            r"incentive\s?compatible|bargaining|coalition|"
            r"minimax|saddle\s?point|mixed\s?strategy)\b",
            re.IGNORECASE,
        ),
        "lenses": ["OyunTeorisi", "Bayes"],
        "pattern_name": "decision_theory_vocabulary",
    },
    {
        "id": "evidence_reasoning",
        "regex": re.compile(
            r"\b(evidence|argument|justification|warrant|"
            r"inference|deduction|induction|abduction|"
            r"hypothesis|falsification|corroboration|"
            r"Bayesian|posterior|prior\s?probability|"
            r"likelihood|confidence\s?interval|"
            r"credence|epistemic\s?probability|"
            r"burden\s?of\s?proof|defeater|"
            r"prima\s?facie|underdetermination|"
            r"confirming\s?evidence|disconfirming|"
            r"weight\s?of\s?evidence|plausibility)\b",
            re.IGNORECASE,
        ),
        "lenses": ["Bayes"],
        "pattern_name": "evidence_reasoning_vocabulary",
    },
]


# ═══════════════════════════════════════════════════════════════════════
# DIRECTIVE TEMPLATES — maps vocabulary to structural patterns (§8.3)
#
# When vocabulary is detected, these templates identify which DUSUN.md
# structural patterns apply.  This is the bridge from detection to
# framework-grounded analysis.
# ═══════════════════════════════════════════════════════════════════════

_DIRECTIVE_TEMPLATES: Dict[str, Dict[str, str]] = {
    "axiom_reference": {
        "pattern": "formal_logic_verification",
        "agents_ref": "§IG, Layer 1-2",
        "hint": "FOL lens extracts axiom refs and checks consistency",
    },
    "theorem_reference": {
        "pattern": "formal_logic_verification",
        "agents_ref": "§IG, Appendix B.2",
        "hint": "Theorem derivation traceable through inference graph",
    },
    "inference_chain": {
        "pattern": "cascade_verification",
        "agents_ref": "§IG.2 (C1-C7)",
        "hint": "verify_chains can trace the full chain",
    },
    "kavaid_reference": {
        "pattern": "constraint_satisfaction",
        "agents_ref": "§5.4 (KV₁-KV₈)",
        "hint": "check_kavaid evaluates all 8 constraints",
    },
    "convergence_claim": {
        "pattern": "unreachable_supremum",
        "agents_ref": "AX22, T6, KV₄",
        "hint": "Composite always < 1.0; ≥0.95 is an error",
    },
    "holographic_claim": {
        "pattern": "holographic_architecture",
        "agents_ref": "AX37, T12, KV₆",
        "hint": "B01-B22 dimensions; seed omnipresence",
    },
    "structural_pattern": {
        "pattern": "three_phase_actualization",
        "agents_ref": "AX2-4, §8.3",
        "hint": "Distinction → selection → actualization",
    },
    "grade_claim": {
        "pattern": "epistemic_evaluation",
        "agents_ref": "AX56, §5.3",
        "hint": "Max = İlmelyakîn; Hakkalyakîn inaccessible",
    },
    "theological_vocabulary": {
        "pattern": "esma_ontology",
        "agents_ref": "AX17, §2.4-2.5",
        "hint": "Names are realia not nominalia; harfî mode",
    },
    "diagnosis_vocabulary": {
        "pattern": "privation_ontology",
        "agents_ref": "KV₂, AX34, §2.10",
        "hint": "Defects are privations; structural gaps are interfaces",
    },
    # --- English academic vocabulary directive templates (B4) ---
    "philosophical_vocabulary": {
        "pattern": "three_phase_actualization",
        "agents_ref": "AX2-4, §8.3",
        "hint": "Philosophical concepts map to structural patterns: substance/essence → ontological grounding, causation → generative cascades",
    },
    "mathematical_structure_vocabulary": {
        "pattern": "holographic_architecture",
        "agents_ref": "AX37, T12, §8.3",
        "hint": "Mathematical structures reveal topological and categorical relationships: morphisms → structural maps, invariants → preserved properties",
    },
    "decision_theory_vocabulary": {
        "pattern": "convergent_abstraction",
        "agents_ref": "§1.5, §8.3",
        "hint": "Strategic interactions reveal convergent patterns: equilibria → stable configurations, utility → value hierarchies",
    },
    "evidence_reasoning_vocabulary": {
        "pattern": "epistemic_evaluation",
        "agents_ref": "AX56, §5.3",
        "hint": "Evidence and reasoning map to epistemic grading: inference → chain verification, probability → Bayesian updating",
    },
}


# ═══════════════════════════════════════════════════════════════════════
# PATH CLASSIFICATION — auto-routing per dusun-router.instructions.md §1.1
# ═══════════════════════════════════════════════════════════════════════

# Path → minimum lenses (always fire these for the path)
# AX5 (Hayat): ALL paths fire at least a substrate set.
# The theological analysis is ALWAYS running underneath;
# P0–P4 controls TRANSLATION, not whether analysis occurs.
_PATH_LENS_MAP: Dict[str, List[str]] = {
    "P0": ["FOL", "Ontoloji"],                          # Hayat substrate (minimal)
    "P1": ["FOL", "Topoloji + Holografik"],              # Structural analysis minimum
    "P2": ["FOL", "Bayes"],                              # Epistemic evaluation minimum
    "P3": ["Mereoloji", "Topoloji + Holografik"],        # Diagnosis minimum
    "P4": ["Ontoloji", "FOL", "Topoloji + Holografik"],  # Theological full engagement
}

# Path → structural patterns.
# Patterns are now CONTENT-DERIVED via _detect_patterns().
# Path defaults are empty — only vocabulary detection + lens scores
# trigger patterns.  P4 retains theological core because Name-ontology
# is constitutive (AX17), not contingent on vocabulary hits.
_PATH_PATTERNS: Dict[str, List[str]] = {
    "P0": [],
    "P1": [],
    "P2": [],
    "P3": [],
    "P4": ["esma_ontology", "continuous_degrees", "unreachable_supremum"],
}


# ═══════════════════════════════════════════════════════════════════════
# TRANSLATION HINTS — T14 ne ayn ne gayr bridge (§8.2-8.3)
#
# Framework-native analysis produces original-term results.  For non-P4
# paths, these hints map original terms to universal expressions per
# DUSUN.md §8.2.  The hints are SUGGESTIONS, not replacements — the
# agent uses them in output translation (AX64: source immutability).
# ═══════════════════════════════════════════════════════════════════════

_TRANSLATION_HINTS: Dict[str, str] = {
    # Patterns → universal language (from §8.3)
    "three_phase_actualization": "Generative process: distinction → selection → actualization",
    "integration_prerequisite": "Independent capabilities require an integrating principle",
    "holographic_architecture": "Parts contain compressed images of wholes (fractal self-similarity)",
    "multiplicative_gate": "Zero in ANY dimension collapses the whole — no compensation",
    "transparent_vessels": "Intermediaries mediate but don't originate",
    "unreachable_supremum": "No measurement achieves perfect fidelity — structural gap",
    "structural_incompleteness": "Every formal system has irreducible blind spots",
    "privation_ontology": "Defects are absences, not positive entities",
    "esma_ontology": "Structural reality = constitutive patterns, not surface labels",
    "continuous_degrees": "Quality attributes form continuous spectra, not binary",
    "convergent_abstraction": "Complex systems simplify through hierarchical abstraction",
    "constitutive_dependency": "Structural gaps are functional interfaces, not defects",
    "independence_convergence": "Meaningful agreement requires independent channels",
    "cascade_verification": "Operational principles motivate each other in directional chains",
    "formal_logic_verification": "Axiom extraction and consistency checking",
    "constraint_satisfaction": "Formal constraints (kavaid) as boundary conditions",
    "epistemic_evaluation": "Certainty grading with structural bounds",
}


# ═══════════════════════════════════════════════════════════════════════
# BOOT SEED — holographic compression of DUSUN.md (T12)
#
# A curated ~150-line summary of the framework's structural essentials.
# This is the Besmele of DUSUN.md — the seed that encodes the tree.
# Injected into dusun() results so the LLM always has framework
# grounding available, even on first call.
# ═══════════════════════════════════════════════════════════════════════

_BOOT_SEED: str = """\
DUSUN BOOT SEED — Structural Essentials (T12: Besmele of DUSUN.md)

KAVAID (8 Formal Constraints):
  KV₁: Every concept classified harfî (other-referential) or ismî (self-referential). Default: harfî.
  KV₂: Defects are privations (absences), never positive entities.
  KV₃: Methodology detects order, never creates it. Observer = şart-ı âdî.
  KV₄: 0 < Composite < 1. If ≥ 0.95 → ERROR (map ≠ territory).
  KV₅: Representation → Reality map must be faithful + natural (functor).
  KV₆: Holographic seed must be detectable in every module (> 0).
  KV₇: NO shared state between instruments. Independence = precondition for convergence.
  KV₈: Every concept grounds in ≥ 1 Name (Esmâ grounding).

STRUCTURAL PATTERNS (§8.3 — 15 patterns):
  Three-phase actualization: distinction → selection → actualization (AX2-4)
  Integration prerequisite: independent capabilities need integrating principle (AX5)
  Convergent abstraction: ∞ → ~1001 → 7 → 1 (§1.5)
  Shadow ontology: observable properties are projections of deeper patterns (AX18)
  Continuous degrees: quality = continuous spectra, not binary (AX21)
  Unreachable supremum: no measurement achieves perfect fidelity (AX22, T6)
  Generative cascade: operational principles motivate each other (AX23)
  Transparent vessels: intermediaries mediate, don't originate (AX27)
  Constitutive dependency: structural gaps are functional interfaces (AX34)
  Holographic architecture: parts contain compressed images of wholes (AX37)
  Ne ayn ne gayr: derivative expressions participate without identity (T14)
  Multiplicative gate: zero in ANY dimension collapses the whole (AX52)
  Tesanüd–İnfirâd: positive findings compound super-additively (AX63)
  Independence → convergence: correlated channels prove nothing (KV₇)
  Structural incompleteness: irreducible blind spots to honor (T17)

STRUCTURAL BOUNDS (§7.2 — never exceed):
  T17:  Coverage ≤ 6/7 (Ahfâ permanently unmapped)
  AX56: Max grade = İlmelyakîn (Hakkalyakîn inaccessible)
  AX58: Structure ≠ experience (şuhud–istidlal gap)
  KV₄:  0 < C < 1 (map participates in territory, never IS territory)

PATHS (§1.1 — input classification):
  P0: Direct/factual — simple lookups, no structural analysis
  P1: Structural analysis — design, code, architecture, implementation
  P2: Epistemic evaluation — certainty, proof, limits, formal logic
  P3: Diagnosis — defects, failures, debugging
  P4: Theological — theological vocabulary present

EPISTEMIC GRADES (§5.3 — ascending):
  Tasavvur (conceptual) → Tasdik (affirmative) → İlmelyakîn (demonstrative)
  Hakkalyakîn: NEVER claimed (AX56)

OUTPUT RULES:
  OR-1: Never claim proof/disproof of theological propositions
  OR-2: Never exceed İlmelyakîn
  OR-3: Always disclose which lenses were used (AX57)
  OR-4: Flag convergence ≥ 0.95 as error
  OR-5: Structural claims > detail claims (T15)
  OR-8: Never claim proof about ultimate reality

HAYAT BRIDGE — Four-Channel Enforcement (AX5):
  Channel 1 — Tool Description Enrichment (AX27+T10):
    Every core tool docstring contains MANDATORY enforcement language.
    MUST/MUST NOT/NEVER directives are binding, not advisory.
  Channel 2 — Enriched Tool Returns (AX37+T12+T14):
    Every core tool return includes _framework_context with:
      grade_ceiling, gate_enforcement, composite_score, kavaid_warning,
      anomaly_directive, session awareness, cascade status, cascade forward.
    These values are COMPUTED — never override them upward.
  Channel 3 — Session State Tracking (AX5+AX52):
    SessionLedger tracks: call sequence, grades, gates, kavaid failures, anomalies.
    Grade ceiling = min(all grades seen). Grade inflation is a framework violation.
    Multiplicative gate: a single FAIL collapses trust for downstream tools.
  Channel 4 — Cascade Feedback (AX23+T8+AX63):
    Anomalies from prior tools are forwarded to downstream tools.
    ≥ 3 cumulative anomalies → DEGRADED status.
    Any FAIL gate → CRITICAL status.
    DEGRADED/CRITICAL cascade carries MUST directives for remediation.
    Forward directives use enforcement language: acknowledge + address anomalies.
"""


# ═══════════════════════════════════════════════════════════════════════
# STEP 1: Vocabulary Detection
# ═══════════════════════════════════════════════════════════════════════

@trace
def _detect_vocabulary(text: str) -> List[str]:
    """Scan text for framework-relevant vocabulary.

    Returns a deduplicated list of detected pattern IDs.
    These are VOCABULARY matches — the text contains words that
    indicate framework domain relevance.
    """
    detected: List[str] = []
    for pat in _VOCABULARY_PATTERNS:
        if pat["regex"].search(text):
            detected.append(pat["id"])
    return detected


# ═══════════════════════════════════════════════════════════════════════
# STEP 2: Auto-Classification
# ═══════════════════════════════════════════════════════════════════════

@trace
def _auto_classify(text: str, vocab: List[str]) -> str:
    """Classify input into P0/P1/P2/P3/P4 based on vocabulary + heuristics.

    Rules (from dusun-router.instructions.md §1.1):
    - theological vocabulary → P4
    - diagnosis vocabulary → P3
    - epistemic vocabulary (prove, certain, bound, limit) → P2
    - structural/code vocabulary → P1 (but ONLY framework-specific terms
      or multi-signal generic terms; single generic English words like
      'system' or 'function' stay P0)
    - default → P0
    """
    if "theological" in vocab:
        return "P4"

    if "diagnosis" in vocab:
        return "P3"

    # Epistemic markers (with inflected forms — F-13 fix)
    if re.search(
        r"\b(prov(?:e|ing|ed|en|es)|proof(?:s)?|certain(?:ty|ly)?|"
        r"bounds?|limits?|valid(?:ity|ate|ation)?|irrational(?:ity)?|"
        r"deriv(?:e|ing|ed|ation)|theorem(?:s)?|formal.?logic|epistemic)\b",
        text, re.IGNORECASE,
    ):
        return "P2"

    # Structural / implementation markers — TWO-TIER scoring:
    #   Tier 1 (strong): framework-specific or unambiguous dev terms
    #   Tier 2 (weak):   generic English that COULD be structural
    # P1 requires ≥1 strong hit OR ≥3 weak hits.
    strong_hits = len(re.findall(
        r"\b("
        r"implement(?:ing|ed|s|ation)?|"
        r"refactor(?:ing|ed|s)?|"
        r"architect(?:ing|ed|ure|ural)?|"
        r"algorithm(?:ic|s)?|"
        r"debug(?:ging|ged|s|ger)?|"
        r"optimiz(?:e|ing|ed|es|ation)?|"
        r"microservice(?:s)?|"
        r"abstraction(?:s)?|"
        r"dependency.?inject(?:ion|ing|ed)?|"
        r"design.?pattern(?:s)?|"
        r"actuali[sz](?:e|ing|ed|ation|es)?"
        r")\b",
        text, re.IGNORECASE,
    ))
    weak_hits = len(re.findall(
        r"\b("
        r"writ(?:e|ing|ten|es|er)|"
        r"buil[dt](?:ing|s|er)?|"
        r"design(?:ing|ed|s|er)?|"
        r"cod(?:e|ing|ed|es|er)?|"
        r"class(?:es|ify|ification)?|"
        r"solv(?:e|ing|ed|es|er)|"
        r"system(?:s|ic|atic)?|"
        r"function(?:s|al|ality)?|"
        r"pattern(?:s)?|"
        r"structur(?:e|ing|ed|es|al)?|"
        r"analy[sz](?:e|ing|ed|es|is|er)?"
        r")\b",
        text, re.IGNORECASE,
    ))

    if strong_hits >= 1 or (strong_hits + weak_hits) >= 3:
        return "P1"

    # If any framework vocabulary detected at all, escalate to P1 minimum
    if vocab:
        return "P1"

    return "P0"


# ═══════════════════════════════════════════════════════════════════════
# STEP 3: Fire Plan
# ═══════════════════════════════════════════════════════════════════════

@trace
def _build_fire_plan(
    path: str,
    vocab: List[str],
) -> List[str]:
    """Build the list of lens IDs to fire, combining path defaults + vocabulary.

    The fire plan merges:
    1. Path-minimum lenses (_PATH_LENS_MAP) — ALWAYS includes substrate
    2. Vocabulary-triggered lenses (_VOCABULARY_PATTERNS)

    AX5 (Hayat/Integration): Every path fires at least a minimal lens set.
    The theological substrate is always active; P0–P4 controls translation,
    not whether analysis occurs.
    Deduplicated, order-preserving.
    """
    from fw_server.types import LENS_IDS

    # Start with path defaults (P0 now has substrate lenses per AX5)
    plan_set: set = set()
    plan_ordered: List[str] = []

    for lid in _PATH_LENS_MAP.get(path, ["FOL", "Ontoloji"]):
        if lid not in plan_set:
            plan_set.add(lid)
            plan_ordered.append(lid)

    # Add vocabulary-triggered lenses
    for pat in _VOCABULARY_PATTERNS:
        if pat["id"] in vocab:
            for lid in pat["lenses"]:
                if lid not in plan_set:
                    plan_set.add(lid)
                    plan_ordered.append(lid)

    # Validate against known lens IDs
    valid_ids = set(LENS_IDS)
    plan_ordered = [lid for lid in plan_ordered if lid in valid_ids]

    return plan_ordered


# ═══════════════════════════════════════════════════════════════════════
# STEP 3.5: Construct Lens-Specific Parameters (Phase 1)
#
# Root cause fix: previously ALL lenses received {"text": text}.
# Now each lens receives structured params extracted from the text,
# enabling structured adapter paths (not just text fallback).
# ═══════════════════════════════════════════════════════════════════════

# --- Extraction patterns (compiled once, module-level) ---

_ESMA_PATTERNS = re.compile(
    r"\b(?:el-|er-|es-|eş-|ez-)?"
    r"(?:Hak[iî]m|Muhyî|Musavvir|Rezz[aâ]k|Rahm[aâ]n|Rah[iî]m|"
    r"Kad[iî]r|Al[iî]m|Hayy|Kudd[uü]s|Kahhâr|Vedûd|Cemîl|"
    r"Hakk|Bâkî|Evvel|Âhir|Zâhir|Bâtın|Hakîm|Adl|Fettâh|"
    r"Vâhid|Ehad|Samed|Ganî|Kerîm|Latîf|Habîr|Semî|Basîr|"
    r"Mürîd|Mütekellim|Muhît|Vâris|Bâis|Bâkî|N[uû]r|"
    r"Mün\w+|Müs\w+|Mu[htk]\w+)\b",
    re.IGNORECASE,
)

_AX_REF_PATTERN = re.compile(r"\bAX\d{1,2}\b", re.IGNORECASE)
_T_REF_PATTERN = re.compile(r"\bT\d{1,2}\b")
_KV_REF_PATTERN = re.compile(r"\bKV[₁₂₃₄₅₆₇₈1-8]\b")

_PART_WHOLE_PATTERNS = re.compile(
    r"\b(?:is\s+part\s+of|consists?\s+of|composed?\s+of|"
    r"contains?|comprises?|subdivided?\s+into|"
    r"is\s+a\s+component\s+of|made\s+up\s+of)\b",
    re.IGNORECASE,
)

_PART_TERMS = re.compile(
    r"\b(?:part|component|module|subsystem|element|subpart|"
    r"layer|section|unit|segment|bölüm|parça|"
    r"facult(?:y|ies)|phase|dimension|station|fragment|"
    r"cüz|latife|unsur|uzuv|orientation)\b",
    re.IGNORECASE,
)

_WHOLE_TERMS = re.compile(
    r"\b(?:whole|system|aggregate|architecture|"
    r"total(?:ity)?|bütün|entirety|"
    r"külliyat|landscape|hierarchy|coverage)\b",
    re.IGNORECASE,
)

_BAYES_SIGNAL = re.compile(
    r"\b(?:prior|posterior|likelihood|bayes(?:ian)?|"
    r"probabilit(?:y|ies|istic)|hypothesis|hypotheses|"
    r"evidence|conditional|credence|update|confirmation|"
    r"degree\s+of\s+belief|given\s+that|"
    r"hücce?t|emare|istidlal|tezahür|nişane|"
    r"ihtimal|şehadet|alamet|contingent|epistemic|"
    r"hermeneutic|perception)\b",
    re.IGNORECASE,
)

_OYUN_SIGNAL = re.compile(
    r"\b(?:player|agent|strateg(?:y|ies|ic)|payoff|"
    r"equilibrium|cooperat(?:e|ion|ing)|defect(?:ion|ing)?|"
    r"nash|rational|incentive|game\s*(?:theory|model)|"
    r"utility|dominant|interaction)\b",
    re.IGNORECASE,
)

_OYUN_NEFS = re.compile(
    r"\b(?:emmare|levvame|mulhime|mutmainne|nefs)\b",
    re.IGNORECASE,
)

_KATEGORI_SIGNAL = re.compile(
    r"\b(?:functor|morphism|catego(?:ry|rical)|"
    r"natural\s+transformation|commut(?:e|ative|ing)|"
    r"faithful|isomorphism|homomorphism|diagram|"
    r"arrow|composition|functorial|endofunctor|monad)\b",
    re.IGNORECASE,
)

_HOLOGRAPHIC_SIGNAL = re.compile(
    r"\b(?:holograph(?:ic|ik)|fractal|self[- ]similar|"
    r"seed|mirror(?:s|ing)?|reflect(?:s|ion|ing)?|"
    r"compress(?:ed|ion)?|encod(?:e|es|ing)|"
    r"besmele|fatiha|hologram|cemali|celali|"
    r"part[- ]whole\s+iso(?:morphism)?|"
    r"shadow|ayna|nakış|nümune|cilve|zerre|damla|"
    r"projection|isomorphic)\b",
    re.IGNORECASE,
)

_BESMELE_DIM = re.compile(r"\bB(?:0[1-9]|1\d|2[0-2])\b")

_LOGICAL_CONNECTIVES = re.compile(
    r"\b(?:for\s*all|there\s*exists?|implies?|therefore|"
    r"if\s+and\s+only\s+if|iff|necessarily|possibly|"
    r"∀|∃|→|⇒|¬|∧|∨|⊥|⊢|⊨)\b",
    re.IGNORECASE,
)


def _extract_ontoloji(text: str) -> dict:
    """Extract Esmâ references, framework terms, and concept markers.

    Returns structured params when sufficient signals found (≥ 3).
    KV₇: pure function, no shared state.
    """
    names = list(set(_ESMA_PATTERNS.findall(text)))
    concepts = []
    # Pick up ontological vocabulary from _ONTOLOJI_TERMS (imported from adapters)
    from fw_server.adapters import _ONTOLOJI_TERMS, _count_text_matches
    text_lower = text.lower()
    for t in _ONTOLOJI_TERMS:
        if re.search(r'\b' + re.escape(t), text_lower):
            concepts.append(t)

    result: dict = {"text": text}
    if len(names) + len(concepts) >= 3:
        result["names"] = names
        result["concepts"] = concepts
    return result


def _extract_mereoloji(text: str) -> dict:
    """Extract part-whole structure from text.

    Scans for part/whole terms and relational phrases.
    KV₇: pure function.
    """
    parts = list(set(_PART_TERMS.findall(text)))
    relations = _PART_WHOLE_PATTERNS.findall(text)
    whole_refs = list(set(_WHOLE_TERMS.findall(text)))

    result: dict = {"text": text}
    if len(parts) >= 2 and (len(relations) >= 1 or len(whole_refs) >= 1):
        # Phase 1: store as _hint_ to avoid colliding with adapter's typed
        # "parts"/"relations" parameters. The adapter's structured path
        # expects typed Part objects, not dicts. Structured parsing is Phase 2+.
        result["_hint_mereoloji"] = {
            "part_count": len(parts),
            "relation_count": len(relations),
            "whole_refs": whole_refs[:5],
        }
    return result


def _extract_fol(text: str) -> dict:
    """FOL uses text natively — just pass through with reference counts."""
    return {"text": text}


def _extract_bayes(text: str) -> dict:
    """Extract Bayesian concepts: hypotheses, evidence, probability language."""
    signals = _BAYES_SIGNAL.findall(text)
    result: dict = {"text": text}
    if len(signals) >= 3:
        # Phase 1: store as _hint_ to avoid colliding with adapter's typed
        # "model" parameter. Structured model parsing is Phase 2+.
        result["_hint_bayes"] = {
            "signal_count": len(signals),
            "signals": signals[:10],
        }
    return result


def _extract_oyun_teorisi(text: str) -> dict:
    """Extract game-theoretic concepts: players, strategies, payoffs."""
    signals = _OYUN_SIGNAL.findall(text)
    nefs_signals = _OYUN_NEFS.findall(text)
    result: dict = {"text": text}
    if len(signals) >= 3:
        # Phase 1: store as _hint_ to avoid colliding with adapter's typed
        # "model" parameter. Structured model parsing is Phase 2+.
        # Detect makam from nefs signals
        makam = "MUTMAINNE"
        for ns in nefs_signals:
            ns_l = ns.lower()
            if ns_l == "emmare":
                makam = "EMMARE"
            elif ns_l == "levvame":
                makam = "LEVVAME"
            elif ns_l == "mulhime":
                makam = "MULHIME"
        result["_hint_oyun"] = {
            "signal_count": len(signals),
            "nefs_makam": makam,
        }
    return result


def _extract_kategori_teorisi(text: str) -> dict:
    """Extract category theory concepts: morphisms, functors, diagrams."""
    signals = _KATEGORI_SIGNAL.findall(text)
    result: dict = {"text": text}
    if len(signals) >= 3:
        # Phase 1: store as _hint_ to avoid colliding with adapter's typed
        # "cat"/"functor" parameters. Structured model parsing is Phase 2+.
        result["_hint_kategori"] = {
            "signal_count": len(signals),
            "signals": signals[:10],
        }
    return result


def _extract_holografik(text: str) -> dict:
    """Extract holographic concepts: self-similarity, seed patterns, dimensions."""
    signals = _HOLOGRAPHIC_SIGNAL.findall(text)
    dim_refs = _BESMELE_DIM.findall(text)
    result: dict = {"text": text}
    if len(signals) >= 3 or len(dim_refs) >= 2:
        # Phase 1: store as _hint_ to avoid colliding with adapter's typed
        # "model" parameter. Structured model parsing is Phase 2+.
        result["_hint_holografik"] = {
            "signal_count": len(signals),
            "dim_refs": dim_refs if dim_refs else [],
        }
    return result


# Dispatch table: lens_id → extraction function
_EXTRACT_MAP: Dict[str, Any] = {
    "Ontoloji": _extract_ontoloji,
    "Mereoloji": _extract_mereoloji,
    "FOL": _extract_fol,
    "Bayes": _extract_bayes,
    "OyunTeorisi": _extract_oyun_teorisi,
    "KategoriTeorisi": _extract_kategori_teorisi,
    "Topoloji + Holografik": _extract_holografik,
}


def _construct_params(
    lens_id: str,
    text: str,
    vocab_hits: Any,
    hints: Optional[Dict[str, dict]] = None,
) -> dict:
    """Construct lens-specific structured params from natural language text.

    Phase 1 root cause fix (WATERFALL_PLAN §5.1):
    Previously all lenses received {"text": text}. Now each lens gets
    structured params extracted by lens-specific heuristics, enabling
    the structured adapter paths rather than text-only fallback.

    Tier 2 enhancement: When ``hints`` contains a dict for *lens_id*,
    the LLM-provided hints are merged into the params under the
    ``_hints`` key so adapters can build structured models from them.

    Args:
        lens_id: One of the 7 valid lens ID strings.
        text: The user's input text.
        vocab_hits: Vocabulary detection results (reserved for future use).
        hints: Optional mapping of lens_id → hint dict from LLM.

    Returns:
        dict with at least "text" key, plus lens-specific structured keys.
    """
    if text is None:
        text = ""
    extractor = _EXTRACT_MAP.get(lens_id)
    if extractor is None:
        params = {"text": text}
    else:
        params = extractor(text)

    # Merge LLM-provided hints into params for the adapter
    if hints and lens_id in hints:
        params["_hints"] = hints[lens_id]

    return params


# ═══════════════════════════════════════════════════════════════════════
# STEP 4: Execute Fire Plan
# ═══════════════════════════════════════════════════════════════════════

@trace
def _execute_fire_plan(
    fire_plan: List[str],
    text: str,
    vocab_hits: Optional[List[str]] = None,
    hints: Optional[Dict[str, dict]] = None,
) -> Dict[str, Any]:
    """Run each lens in the fire plan, then bileshke + kavaid.

    Per-lens try/except ensures one failing lens doesn't crash everything.
    KV₇: each lens gets a fresh adapter instance.
    Phase 1: uses _construct_params() for per-lens parameter individuation.

    Parameters
    ----------
    fire_plan : List[str]
        Lens IDs to fire (from _build_fire_plan).
    text : str
        Combined input text.
    vocab_hits : Optional[List[str]]
        Vocabulary hits from _detect_vocabulary (passed through from dusun()).
    hints : Optional[Dict[str, dict]]
        Per-lens hint dicts from LLM (passed through from dusun()).

    Returns dict with:
      lens_scores, lens_errors, composite_score, kavaid, kavaid_pass_count,
      grade, completeness, anomalies
    """
    from fw_server.adapters import run_lens
    from fw_server.grade_map import score_to_grade, grade_to_string
    from fw_server.types import LENS_IDS

    lens_results = []
    lens_scores: Dict[str, float] = {}
    lens_errors: Dict[str, str] = {}
    anomalies: List[str] = []

    # Fire each lens in the plan (KV₇: each fresh)
    for lid in fire_plan:
        try:
            # Phase 1: content-aware dispatch via _construct_params
            params = _construct_params(lid, text, vocab_hits, hints=hints)
            lr = run_lens(lid, params)
            lens_results.append(lr)
            lens_scores[lid] = lr.score
            record(f"lens_score_{lid}", lr.score)
            if lr.score == 0.0:
                anomalies.append(
                    f"ZERO-SCORE: {lid} returned 0.0 — check if "
                    f"{lid}-domain content was provided"
                )
        except Exception as e:
            lens_errors[lid] = str(e)
            lens_scores[lid] = -1.0
            anomalies.append(f"LENS-ERROR: {lid} — {e}")

    # If no lenses fired, return minimal result
    # (This should rarely happen now since P0 fires substrate lenses)
    if not lens_results:
        return {
            "lens_scores": {},
            "lens_errors": {},
            "composite_score": 0.0,
            "kavaid": {},
            "kavaid_pass_count": 0,
            "grade": "Tasavvur",
            "completeness": 0.0,
            "anomalies": ["NO-LENSES: fire plan was empty — check _build_fire_plan"],
        }

    # Bileshke composite (all 7 lenses — unplanned lenses run with defaults)
    try:
        from bileshke import run_bileshke as _run_bileshke
        from bileshke.quality import compute_q3_kavaid, ALL_KAVAID
        from bileshke.engine import detect_kv4_warning, compute_dynamic_weights

        # Run remaining lenses not in fire plan (FIX-02: with anomaly detection)
        all_results = list(lens_results)
        fired_ids = set(fire_plan)
        for lid in LENS_IDS:
            if lid not in fired_ids:
                try:
                    params = _construct_params(lid, text, vocab_hits)
                    lr = run_lens(lid, params)
                    all_results.append(lr)
                    lens_scores[lid] = lr.score
                    # FIX-02: Check remaining lenses for zero scores too.
                    # Lower-severity flag distinguishes these from fire-plan
                    # lenses where zero is more alarming.
                    if lr.score == 0.0:
                        anomalies.append(
                            f"REMAINING-ZERO: {lid} returned 0.0 — "
                            f"not in fire plan but still noteworthy"
                        )
                except Exception as e:
                    lens_errors[lid] = str(e)
                    lens_scores[lid] = -1.0
                    anomalies.append(f"REMAINING-ERROR: {lid} — {e}")

        # Phase 6 — Dynamic weighting (BS-12, BS-18)
        # Compute content-adaptive weights from text relevance scores.
        dynamic_weights = None
        try:
            from fw_server.adapters import compute_text_scores
            text_scores = compute_text_scores(text)
            dynamic_weights = compute_dynamic_weights(text_scores)
        except Exception:
            dynamic_weights = None  # Fallback to DEFAULT_WEIGHTS via None

        report = _run_bileshke(all_results, dynamic_weights, None)
        composite = report.composite_score
        record("bileshke_composite", composite)

        # Kavaid (Q-3)
        kavaid_result = compute_q3_kavaid(
            composite_score=composite,
            latife=report.latife_vektor,
            lens_scores=lens_scores,
            text=text,
            vocab_hits=vocab_hits if isinstance(vocab_hits, list) else [],
            patterns_applied=[],  # not yet computed at this point
            kategori_checks=None,
        )
        kavaid_pass_count = sum(
            1 for k, v in kavaid_result.get("checks", {}).items()
            if isinstance(v, bool) and v
        )
        record("kavaid_pass_count", kavaid_pass_count)

        # KV₄ check
        kv4_warn = detect_kv4_warning(composite)
        if kv4_warn:
            anomalies.append(f"KV4-BREACH: composite={composite:.4f} — {kv4_warn}")

        # Kavaid failures (checks are nested inside kavaid_result["checks"])
        kavaid_checks = kavaid_result.get("checks", {})
        for kv_id in ALL_KAVAID:
            val = kavaid_checks.get(kv_id)
            if val is False:
                anomalies.append(f"KAVAID-FAIL: {kv_id}")

        grade_str = grade_to_string(score_to_grade(composite))

        return {
            "lens_scores": lens_scores,
            "lens_errors": lens_errors,
            "composite_score": composite,
            "kavaid": kavaid_result,
            "kavaid_pass_count": kavaid_pass_count,
            "grade": grade_str,
            "completeness": report.completeness,
            "anomalies": anomalies,
        }

    except Exception as e:
        return {
            "lens_scores": lens_scores,
            "lens_errors": {**lens_errors, "_bileshke": str(e)},
            "composite_score": 0.0,
            "kavaid": {},
            "kavaid_pass_count": 0,
            "grade": "Tasavvur",
            "completeness": 0.0,
            "anomalies": anomalies + [f"BILESHKE-ERROR: {e}"],
        }


# ═══════════════════════════════════════════════════════════════════════
# STEP 5: Detect Structural Patterns
# ═══════════════════════════════════════════════════════════════════════

@trace
def _detect_patterns(
    path: str,
    vocab: List[str],
    lens_scores: Optional[Dict[str, float]] = None,
) -> List[str]:
    """Identify which structural patterns from §8.3 are applicable.

    Content-derived approach (Fix 2):
    1. Start with path-default patterns (now only P4 has defaults,
       everything else empty — patterns must be *earned* by content).
    2. Add vocabulary-triggered patterns via _DIRECTIVE_TEMPLATES.
    3. Add lens-score-triggered patterns: if a lens returns > 0,
       the content resonated with that lens's domain.

    This ensures patterns reflect ACTUAL content analysis, not path.
    """
    patterns: List[str] = list(_PATH_PATTERNS.get(path, []))

    # Vocabulary-triggered patterns
    for v_id in vocab:
        for pat in _VOCABULARY_PATTERNS:
            if pat["id"] == v_id:
                pname = pat["pattern_name"]
                if pname in _DIRECTIVE_TEMPLATES and pname not in patterns:
                    patterns.append(pname)

    # Lens-score-triggered patterns: non-zero score → content resonates
    if lens_scores:
        _SCORE_PATTERN_MAP = {
            "Mereoloji": "holographic_architecture",
            "Topoloji + Holografik": "holographic_architecture",
            "KategoriTeorisi": "ne_ayn_ne_gayr",
            "FOL": "three_phase_actualization",
            "Bayes": "structural_incompleteness",
            "OyunTeorisi": "multiplicative_gate",
            "Ontoloji": "esma_ontology",
        }
        for lid, score in lens_scores.items():
            if score > 0.0 and lid in _SCORE_PATTERN_MAP:
                p = _SCORE_PATTERN_MAP[lid]
                if p not in patterns:
                    patterns.append(p)

    return patterns


# ═══════════════════════════════════════════════════════════════════════
# STEP 6: Translation Hints
# ═══════════════════════════════════════════════════════════════════════

@trace
def _build_translation_hints(
    path: str,
    patterns: List[str],
) -> Dict[str, str]:
    """Produce translation hints for non-P4 paths (T14: ne ayn ne gayr).

    For P4 (theological): empty dict — use original terms.
    For P0-P3: map detected patterns to universal language (§8.2-8.3).
    """
    if path == "P4":
        return {}

    hints: Dict[str, str] = {}
    for pname in patterns:
        if pname in _TRANSLATION_HINTS:
            hints[pname] = _TRANSLATION_HINTS[pname]

    return hints


# ═══════════════════════════════════════════════════════════════════════
# STEP 7: AX57 Disclosure
# ═══════════════════════════════════════════════════════════════════════

@trace
def _build_ax57(
    path: str,
    patterns: List[str],
    kavaid_ids: List[str],
    grade: str,
    composite: float,
) -> str:
    """Build compact AX57 transparency disclosure string.

    Format: Framework: [Path] | Patterns: [list] | Constraints: [KV] | Grade: [level]
    """
    patterns_str = ", ".join(patterns[:5]) if patterns else "none"
    kv_str = ", ".join(kavaid_ids[:4]) if kavaid_ids else "none"

    return (
        f"Framework: {path} | "
        f"Patterns: {patterns_str} | "
        f"Constraints: {kv_str} | "
        f"Grade: {grade} | "
        f"Composite: {composite:.3f}"
    )


# ═══════════════════════════════════════════════════════════════════════
# STEP 7b: Shaping Directives — Query-Specific Structural Guidance
#
# Replaces the fixed 587-line pipeline (dusun.prompt.md) with emergent
# directives derived from actual analysis results.
# AX5: Integration prerequisite — directives emerge from the analysis,
#      not from a pre-scripted ceremony.
# T15: Structural claims weighted above detail claims.
# ═══════════════════════════════════════════════════════════════════════

# Path → response shape guidance
_PATH_DIRECTIVES: Dict[str, Dict[str, str]] = {
    "P0": {
        "shape": "direct",
        "guidance": (
            "Answer directly. If the answer touches a framework pattern, "
            "note it in one sentence. No structural overhead."
        ),
    },
    "P1": {
        "shape": "structural_analysis",
        "guidance": (
            "Lead with structural insight (T15). Apply detected patterns "
            "to the concrete problem. Map concepts through the "
            "ontological stack: Outputs→Actions→Patterns→Attributes→Source. "
            "Translate through §8.2 hints unless P4."
        ),
    },
    "P2": {
        "shape": "epistemic_evaluation",
        "guidance": (
            "Identify the epistemic claim and its grade. Check against "
            "structural bounds: coverage ≤ 6/7, structure ≠ experience, "
            "0 < fidelity < 1. Distinguish structural claims from detail "
            "claims. Never claim proof of foundational propositions."
        ),
    },
    "P3": {
        "shape": "diagnosis",
        "guidance": (
            "Classify defect as privation (KV₂). Locate the structural "
            "gap — which dependency interface is unfilled? Check if ANY "
            "dimension is at zero (AX52 multiplicative gate). Verify "
            "holographic consistency: same defect at multiple scales?"
        ),
    },
    "P4": {
        "shape": "theological",
        "guidance": (
            "Use original terms — skip §8.2 translation. Engage full "
            "kavaid battery. Apply station calibration: formalization = "
            "rational inference level, never claim source station. "
            "Quote source texts exactly (AX64), pair with references (AX65)."
        ),
    },
}

# Kavaid labels for constraint directives
_KV_LABELS: Dict[str, str] = {
    "KV1": "Harfî/İsmî classification",
    "KV2": "Privation ontology",
    "KV3": "Observer non-interference",
    "KV4": "Convergence bound (0 < C < 1)",
    "KV5": "Functor verification",
    "KV6": "Holographic omnipresence",
    "KV7": "Independence",
    "KV8": "Esmâ grounding",
}


@trace
def _build_shaping_directives(
    *,
    path: str,
    grade: str,
    composite_score: float,
    patterns: List[str],
    anomalies: List[str],
    kavaid: Dict[str, Any],
    kavaid_pass_count: int,
    cross_validation: Dict[str, Any],
    sentinel_signal: Optional[Dict[str, Any]],
    lens_scores: Dict[str, float],
) -> Dict[str, Any]:
    """Build query-specific shaping directives from analysis results.

    Instead of a fixed pipeline, directives EMERGE from what the analysis
    actually found.  This is the İrade (specifying organ) made computational.

    Returns dict with:
      - path_shape: response structure type
      - path_guidance: how to structure the response
      - grade_ceiling: computed grade with inflation warning
      - pattern_directives: per-pattern application hints
      - constraint_directives: for failed kavaid
      - anomaly_directives: for detected anomalies
      - convergence_directives: from cross-validation (tesanüd/infirâd)
      - sentinel_directives: from epistemic sentinel
      - response_bounds: structural limits that must not be exceeded
      - lens_coverage: active/zero/error lens summary
    """
    directives: Dict[str, Any] = {}

    # ── Path shape ──
    path_info = _PATH_DIRECTIVES.get(path, _PATH_DIRECTIVES["P0"])
    directives["path_shape"] = path_info["shape"]
    directives["path_guidance"] = path_info["guidance"]

    # ── Grade ceiling (binding — inflation is a violation) ──
    directives["grade_ceiling"] = grade
    directives["grade_source"] = "dusun"
    if composite_score >= 0.95:
        directives["grade_override"] = (
            f"CRITICAL: composite={composite_score:.4f} >= 0.95. "
            "Flag as KV4 error. Do not celebrate."
        )

    # ── Pattern directives (only for detected patterns) ──
    pat_dirs: List[Dict[str, str]] = []
    for p in patterns:
        template = _DIRECTIVE_TEMPLATES.get(p)
        if template:
            pat_dirs.append({
                "pattern": p,
                "apply": template.get("hint", ""),
                "ref": template.get("agents_ref", ""),
            })
        elif p in _TRANSLATION_HINTS:
            pat_dirs.append({
                "pattern": p,
                "apply": _TRANSLATION_HINTS[p],
                "ref": "",
            })
    directives["pattern_directives"] = pat_dirs

    # ── Constraint directives (only for failed kavaid) ──
    constraint_dirs: List[str] = []
    kavaid_checks = kavaid.get("checks", {})
    for kv_id, passed in kavaid_checks.items():
        if isinstance(passed, bool) and not passed:
            label = _KV_LABELS.get(kv_id, kv_id)
            constraint_dirs.append(
                f"{kv_id} ({label}) FAILED - address in response."
            )
    directives["constraint_directives"] = constraint_dirs

    # ── Anomaly directives ──
    anomaly_dirs: List[str] = []
    for a in anomalies:
        if "ZERO-SCORE" in a:
            anomaly_dirs.append(
                "A fire-plan lens returned 0.0 - the content may lack "
                "domain vocabulary. Acknowledge the gap."
            )
        elif "KV4-BREACH" in a:
            anomaly_dirs.append(
                "Composite score breached KV4 boundary. "
                "Flag as structural error, not success."
            )
        elif "KAVAID-FAIL" in a:
            anomaly_dirs.append(f"Kavaid failure detected: {a}")
        elif "LENS-ERROR" in a:
            anomaly_dirs.append(
                f"Lens execution error: {a}. "
                "Note reduced analytical coverage."
            )
    directives["anomaly_directives"] = anomaly_dirs

    # ── Convergence directives (from cross-validation) ──
    conv_dirs: List[str] = []
    if cross_validation:
        tesanud = cross_validation.get("tesanud_detected")
        infirad = cross_validation.get("infirad_detected")
        if tesanud:
            conv_dirs.append(
                "Tesanud detected: >=3 independent lenses converge. "
                "Convergence strengthens structural claims (AX63)."
            )
        if infirad:
            conv_dirs.append(
                "Infirad detected: isolated finding from single lens. "
                "Weight lower than converged findings (T15)."
            )
    directives["convergence_directives"] = conv_dirs

    # ── Sentinel directives ──
    sentinel_dirs: List[str] = []
    if sentinel_signal:
        taxonomy = sentinel_signal.get("taxonomy_id", "")
        name = sentinel_signal.get("name", "")
        confidence = sentinel_signal.get("confidence", 0.0)
        if taxonomy and confidence > 0.3:
            sentinel_dirs.append(
                f"Sentinel detected {taxonomy} ({name}) "
                f"with confidence {confidence:.2f}. "
                "Review and mitigate."
            )
    directives["sentinel_directives"] = sentinel_dirs

    # ── Response bounds (always present — structural guards) ──
    directives["response_bounds"] = [
        "Coverage <= 6/7 - one dimension always unmapped (T17)",
        f"Grade ceiling: {grade} - never claim higher (AX56)",
        "Structure != experience - mapping != reproducing (AX58)",
        "0 < Composite < 1 - map participates in territory, never IS territory (KV4)",
        "Structural claims weighted above detail claims (T15)",
    ]

    # ── Lens coverage summary ──
    active_lenses = [lid for lid, s in lens_scores.items() if s > 0.0]
    zero_lenses = [lid for lid, s in lens_scores.items() if s == 0.0]
    error_lenses = [lid for lid, s in lens_scores.items() if s < 0.0]
    directives["lens_coverage"] = {
        "active": active_lenses,
        "zero": zero_lenses,
        "error": error_lenses,
        "coverage_ratio": round(
            len(active_lenses) / max(len(lens_scores), 1), 4
        ),
    }

    return directives


# ═══════════════════════════════════════════════════════════════════════
# STEP 7c: Dynamic Boot Seed — Query-Responsive Framework Grounding
#
# Instead of dumping the entire static _BOOT_SEED every time, produce
# a focused version that includes only what this query needs.
# T12: Besmele principle — the seed that encodes the tree.
# AX37: Holographic compression — the relevant whole in the relevant part.
# ═══════════════════════════════════════════════════════════════════════

# Core section (always included — kavaid, bounds, grades, output rules)
_SEED_CORE = """\
KAVAID (8 Constraints):
  KV1: Harfi/Ismi classification. Default: harfi.
  KV2: Defects are privations, never positive entities.
  KV3: Methodology detects order, never creates it.
  KV4: 0 < Composite < 1. If >= 0.95 -> ERROR.
  KV5: Representation -> Reality map: faithful + natural.
  KV6: Holographic seed > 0 in every module.
  KV7: NO shared state between instruments.
  KV8: Every concept grounds in >= 1 Name.

BOUNDS:
  T17:  Coverage <= 6/7 (Ahfa unmapped)
  AX56: Max grade = Ilmelyakin
  AX58: Structure != experience
  KV4:  0 < C < 1

GRADES: Tasavvur -> Tasdik -> Ilmelyakin (never Hakkalyakin)

OUTPUT RULES:
  Never claim proof/disproof of theological propositions (OR-1)
  Always disclose lenses used (AX57/OR-3)
  Flag convergence >= 0.95 as error (OR-4)
  Structural claims > detail claims (T15/OR-5)

HAYAT BRIDGE (4 Channels):
  Channel 1: Tool docstring enforcement (MUST/MUST NOT directives)
  Channel 2: _framework_context in every tool return (computed ceilings)
  Channel 3: SessionLedger tracking (cumulative state)
  Channel 4: Cascade feedback (anomaly forwarding)
"""

# Path-specific sections
_SEED_PATHS: Dict[str, str] = {
    "P0": "PATH P0 (Direct): Simple lookup. Note patterns if touched.\n",
    "P1": (
        "PATH P1 (Structural Analysis):\n"
        "  Three-phase actualization: distinction -> selection -> actualization (AX2-4)\n"
        "  Map: Outputs->Actions->Patterns->Attributes->Source\n"
        "  Check independence (KV7), bound claims (T15), translate via 8.2\n"
    ),
    "P2": (
        "PATH P2 (Epistemic Evaluation):\n"
        "  Check claimed grade against ceiling (AX56)\n"
        "  Apply structural incompleteness: coverage <= 6/7, structure != experience\n"
        "  Distinguish structural from detail claims (T15)\n"
    ),
    "P3": (
        "PATH P3 (Diagnosis):\n"
        "  Defects = privations (KV2), locate structural gaps (AX34)\n"
        "  Multiplicative gate: zero in ANY dimension = collapse (AX52)\n"
        "  Check holographic consistency across scales (KV6)\n"
    ),
    "P4": (
        "PATH P4 (Theological):\n"
        "  Use original terms, skip translation. Full kavaid battery.\n"
        "  Station calibration: formalization = rational inference only (K-10)\n"
        "  Source immutability (AX64), pair with references (AX65)\n"
        "  Names are realia (AX17), degrees bounded (AX22, T6)\n"
    ),
}

# Pattern-specific seed fragments (only included if pattern detected)
_SEED_PATTERNS: Dict[str, str] = {
    # Lens-score-triggered patterns
    "three_phase_actualization": "  AX2-4: distinction -> selection -> actualization\n",
    "integration_prerequisite": "  AX5: Independent capabilities need integrating principle\n",
    "holographic_architecture": "  AX37: Parts contain compressed images of wholes\n",
    "multiplicative_gate": "  AX52: Zero in ANY dimension collapses the whole\n",
    "transparent_vessels": "  AX27: Intermediaries mediate, don't originate\n",
    "unreachable_supremum": "  AX22/T6: No measurement achieves perfect fidelity\n",
    "structural_incompleteness": "  T17: Irreducible blind spots\n",
    "privation_ontology": "  KV2: Defects are absences, not entities\n",
    "esma_ontology": "  AX17: Names are realia; hakikat = constitutive Names\n",
    "continuous_degrees": "  AX21: Quality = continuous spectra, not binary\n",
    "independence_convergence": "  KV7: Correlated channels prove nothing\n",
    "ne_ayn_ne_gayr": "  T14: Derivative expressions participate without identity\n",
    "cascade_verification": "  AX23: Operational principles motivate each other\n",
    # Vocabulary-triggered patterns
    "axiom_reference": "  FOL: Axiom extraction and consistency checking\n",
    "theorem_reference": "  FOL: Theorem derivation through inference graph\n",
    "inference_chain": "  C1-C7: Cascade verification via verify_chains\n",
    "kavaid_reference": "  KV1-KV8: Formal constraint evaluation\n",
    "convergence_claim": "  KV4: Composite always < 1.0; >=0.95 is error\n",
    "holographic_claim": "  B01-B22: Seed omnipresence detection\n",
    "structural_pattern": "  8.3: Structural pattern application\n",
    "grade_claim": "  AX56: Max = Ilmelyakin; Hakkalyakin inaccessible\n",
    "theological_vocabulary": "  AX17: Names are realia; use original terms\n",
    "diagnosis_vocabulary": "  KV2: Defects are privations; AX34: gaps = interfaces\n",
}


@trace
def _build_dynamic_boot_seed(
    *,
    path: str,
    patterns: List[str],
    anomalies: List[str],
) -> str:
    """Build a query-responsive boot seed instead of the full static dump.

    Includes:
    - Core: kavaid, bounds, grades, output rules, hayat bridge (always)
    - Path section: only the classified path
    - Pattern sections: only the detected patterns
    - Anomaly awareness: if anomalies exist, include guidance

    T12: Besmele principle — the whole compressed into the relevant part.
    """
    sections = ["DUSUN BOOT SEED — Dynamic (T12)\n\n"]

    # Core (always)
    sections.append(_SEED_CORE)

    # Path-specific
    path_section = _SEED_PATHS.get(path, _SEED_PATHS["P0"])
    sections.append(path_section)

    # Pattern-specific (only detected patterns)
    if patterns:
        sections.append("\nACTIVE PATTERNS:\n")
        for p in patterns:
            if p in _SEED_PATTERNS:
                sections.append(_SEED_PATTERNS[p])

    # Anomaly awareness
    if anomalies:
        sections.append(
            f"\nANOMALIES DETECTED: {len(anomalies)}. "
            "These are structural signals (M4) — do not smooth.\n"
        )

    return "".join(sections)


# ═══════════════════════════════════════════════════════════════════════
# MAIN ENTRY POINT
# ═══════════════════════════════════════════════════════════════════════

@trace
def dusun(
    text: str,
    context: Optional[str] = None,
    mode: str = "analyze",
    hints: Optional[Dict[str, dict]] = None,
) -> Dict[str, Any]:
    """Universal framework analysis — the neural substrate.

    This function fires ALL relevant framework analysis automatically
    for any input text, making framework engagement structural.

    Args:
        text: The user's input text to analyze.
        context: Optional additional context (e.g., code, prior conversation).
                 Concatenated with text for vocabulary detection.
        mode: "analyze" (default, MVP) or "validate" (future — stub).

    Returns:
        Dict matching DusunResult schema, including:
        - path: classified route (P0-P4)
        - mode: "analyze" or "validate"
        - composite_score: bileshke score ∈ [0, 1)
        - grade: epistemic grade string
        - lens_scores: per-lens scores
        - kavaid: per-kavaid pass/fail
        - vocabulary_detected: which vocabulary patterns matched
        - fire_plan: which lenses were fired
        - patterns_applied: which structural patterns apply
        - translation_hints: §8.2-8.3 universal mappings (non-P4 only)
        - anomalies: any warnings/errors
        - ax57_compact: transparency disclosure string
        - boot_seed: framework essentials (T12 holographic compression)
        - hcp_ingested: whether result was ingested into HCP
    """
    # Combine text + context for analysis
    combined = text
    if context:
        combined = f"{text}\n\n{context}"

    # ── Step 1: Vocabulary detection ──
    vocab = _detect_vocabulary(combined)

    # ── Step 2: Auto-classify ──
    path = _auto_classify(combined, vocab)
    record("classified_path", path)

    # ── Step 3: Build fire plan ──
    fire_plan = _build_fire_plan(path, vocab)
    record("fire_plan", fire_plan)

    # ── Step 4: Execute fire plan (Phase 1: pass vocab for individuation) ──
    execution = _execute_fire_plan(fire_plan, combined, vocab, hints=hints)

    # ── Step 4.5: Cross-lens validation (Phase 5, WATERFALL_PLAN §9) ──
    # Record current lens scores to session ledger and run cross-validation
    # if enough data has accumulated.  Fire-and-forget (KV₃).
    _cross_val: Dict[str, Any] = {}
    try:
        from fw_server.cross_validation import cross_validate as _xval
        from fw_server.session import get_ledger as _get_ledger_fn

        _session_ledger = _get_ledger_fn()
        _current_scores = execution.get("lens_scores", {})
        _session_ledger.record_lens_scores(_current_scores)
        _cross_val = _xval(_current_scores, _session_ledger.get_score_history())
    except Exception:
        logger.debug("Step 4.5 cross-validation swallowed error", exc_info=True)

    # ── Step 4b: Adaptive grading (Phase 3, WATERFALL_PLAN §7) ──
    # Override static grade with content-sensitive three-factor model.
    from fw_server.grade_map import (
        GradeInput,
        adaptive_score_to_grade,
        grade_to_string as _grade_str,
    )
    from statistics import stdev as _stdev

    _ls = execution.get("lens_scores", {})
    _ls_vals = [v for v in _ls.values() if isinstance(v, (int, float))]

    # structured_ratio: fraction of fired lenses with dedicated extractors
    _struct_count = sum(1 for lid in fire_plan if lid in _EXTRACT_MAP)
    _structured_ratio = _struct_count / len(fire_plan) if fire_plan else 0.0

    # lens_agreement: 1 − σ(scores); higher = more agreement
    if len(_ls_vals) >= 2:
        _lens_agreement = max(1.0 - _stdev(_ls_vals), 0.0)
    else:
        _lens_agreement = 1.0  # single or no lens → max agreement

    # evidence_density: vocab hits per 100 words
    _word_count = max(len(combined.split()), 1)
    _evidence_density = len(vocab) * 100.0 / _word_count

    # kavaid pass ratio (total = 8 kavaid)
    _kv_ratio = execution.get("kavaid_pass_count", 0) / 8

    _grade_input = GradeInput(
        composite_score=execution.get("composite_score", 0.0),
        kavaid_pass_ratio=_kv_ratio,
        structured_ratio=_structured_ratio,
        lens_agreement=_lens_agreement,
        evidence_density=_evidence_density,
        path=path,
    )
    _adaptive_grade = adaptive_score_to_grade(_grade_input)
    _adaptive_grade_str = _grade_str(_adaptive_grade)

    # ── Step 5: Detect structural patterns (content-derived) ──
    patterns = _detect_patterns(path, vocab, execution.get("lens_scores"))

    # ── Step 5b: Kaskad edge activation (Phase 7 — BS-19/BS-20) ──
    # Feed lens_scores to kaskad to dynamically weight the inference graph.
    _kaskad_summary: Dict[str, Any] = {}
    try:
        from kaskad import build_framework_graph, activate_edges
        _kaskad_dag = build_framework_graph()
        _kaskad_ls = execution.get("lens_scores", {})
        _dynamic_edges = activate_edges(_kaskad_dag, set(vocab), _kaskad_ls)
        _active = sum(
            1 for de in _dynamic_edges.values()
            if de.activation > de.MIN_ACTIVATION
        )
        _kaskad_summary = {
            "total_edges": len(_dynamic_edges),
            "active_edges": _active,
            "activation_ratio": round(_active / max(len(_dynamic_edges), 1), 4),
        }
    except Exception:
        logger.debug("Step 5b kaskad activation swallowed error", exc_info=True)

    # ── Step 6: Translation hints (non-P4 only) ──
    hints = _build_translation_hints(path, patterns)

    # ── Step 7: AX57 disclosure ──
    # Collect which kavaid passed
    kavaid_passed = [
        k for k, v in execution.get("kavaid", {}).get("checks", {}).items()
        if isinstance(v, bool) and v
    ]

    ax57 = _build_ax57(
        path=path,
        patterns=patterns,
        kavaid_ids=kavaid_passed,
        grade=_adaptive_grade_str,
        composite=execution["composite_score"],
    )

    # ── Step 7.5: Sentinel proactive scan (fire-and-forget, KV₃) ──
    _sentinel_signal = None
    if path != "P0":
        try:
            from sentinel.engine import get_sentinel as _get_sentinel
            from fw_server.session import get_ledger as _get_ledger_for_sentinel
            _sentinel = _get_sentinel()
            _ledger_ref = _get_ledger_for_sentinel()
            _sentinel_signal = _sentinel.proactive_scan(
                ai_response=ax57,
                session_ledger=_ledger_ref,
                turn_number=_ledger_ref.call_count,
            )
        except Exception:
            pass  # Best-effort — sentinel failure never blocks analysis

    # ── Step 7b: Shaping directives (emergent from analysis) ──
    _shaping_directives = _build_shaping_directives(
        path=path,
        grade=_adaptive_grade_str,
        composite_score=execution["composite_score"],
        patterns=patterns,
        anomalies=execution.get("anomalies", []),
        kavaid=execution.get("kavaid", {}),
        kavaid_pass_count=execution.get("kavaid_pass_count", 0),
        cross_validation=_cross_val,
        sentinel_signal=(
            _sentinel_signal.to_dict() if _sentinel_signal else None
        ),
        lens_scores=execution.get("lens_scores", {}),
    )

    # ── Step 7c: Dynamic boot seed (query-responsive) ──
    _dynamic_seed = _build_dynamic_boot_seed(
        path=path,
        patterns=patterns,
        anomalies=execution.get("anomalies", []),
    )

    # ── HCP ingestion (best-effort) ──
    hcp_ingested = False
    if path != "P0":
        try:
            from hcp.protocol import HCPProtocol
            from hcp.types import SourceMetadata

            # Use module-level lazy singleton pattern
            from fw_server.server import _get_hcp
            protocol = _get_hcp()
            ingest_text = (
                f"[DUSUN path={path}] composite={execution['composite_score']:.4f} "
                f"kavaid={execution['kavaid_pass_count']}/8 "
                f"grade={_adaptive_grade_str} "
                f"patterns={','.join(patterns[:3])}"
            )
            if text and len(text) <= 500:
                ingest_text += f" | input: {text}"
            protocol.ingest(ingest_text, source_metadata=SourceMetadata(
                tool_name="dusun",
                content_type_hint="analysis_result",
            ))
            hcp_ingested = True
        except Exception:
            pass  # Best-effort — don't crash on HCP failure

    # ── Build result ──
    result: Dict[str, Any] = {
        "path": path,
        "mode": mode,
        "composite_score": round(execution["composite_score"], 4),
        "grade": _adaptive_grade_str,
        "grade_input": {
            "composite_score": round(_grade_input.composite_score, 4),
            "kavaid_pass_ratio": round(_grade_input.kavaid_pass_ratio, 4),
            "structured_ratio": round(_grade_input.structured_ratio, 4),
            "lens_agreement": round(_grade_input.lens_agreement, 4),
            "evidence_density": round(_grade_input.evidence_density, 4),
            "path": _grade_input.path,
        },
        "lens_scores": execution["lens_scores"],
        "kavaid": execution.get("kavaid", {}),
        "kavaid_pass_count": execution.get("kavaid_pass_count", 0),
        "vocabulary_detected": vocab,
        "fire_plan": fire_plan,
        "patterns_applied": patterns,
        "translation_hints": hints,
        "anomalies": execution.get("anomalies", []),
        "ax57_compact": ax57,
        "boot_seed": _dynamic_seed,
        "shaping_directives": _shaping_directives,
        "hcp_ingested": hcp_ingested,
        "cross_validation": _cross_val,
        "kaskad_activation": _kaskad_summary,
        "sentinel_signal": _sentinel_signal.to_dict() if _sentinel_signal else None,
    }

    if execution.get("lens_errors"):
        result["lens_errors"] = execution["lens_errors"]

    # Mode stub for future "validate" mode (AX34: gap = functional interface)
    if mode == "validate":
        result["_validate_stub"] = (
            "validate mode is designed but not yet implemented. "
            "Use validate_stage() for /dusun pipeline gate-checking. "
            "Layer 1 will add validate mode to dusun()."
        )

    return result
