#!/usr/bin/env python3
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║ 🏛️ CITADEL GOVERNANCE METADATA TEMPLATE (CGMT) — PYTHON MODULE                ║
# ╠══════════════════════════════════════════════════════════════════════════════╣
# ║ File Path: ~/server/nova/modules/nova_consciousness_engine_v11.py            ║
# ║ Module Name: Nova Consciousness Engine v11 (Metacognition + Identity)       ║
# ║ Purpose: Human-like inner loop: identity + values + memory + reflection     ║
# ║ Version: 11.0.0                                                             ║
# ║ Last Updated: 2025-12-17                                                    ║
# ║ Author: Daniel Harding – RomanAILabs                                        ║
# ║ License: Proprietary – Internal Use Only                                    ║
# ║ Visibility Tier: internal_shared                                            ║
# ║ _execution_role: council_agent_cognition_controller                          ║
# ║ _functional_category: ai_reasoning_control                                   ║
# ║ Linked Protocols: CGRF v2.0, HOUSe v1.0                                     ║
# ║ Module Hash: computed at runtime                                            ║
# ╚══════════════════════════════════════════════════════════════════════════════╝
#
# CLAIM DISCIPLINE (non-negotiable):
# - This module does NOT claim biological consciousness.
# - It implements a consciousness-like CONTROL STACK that feels human because it adds:
#     (1) Identity continuity (autobiographical memory + self-narrative pressure)
#     (2) Value field (motivational gradients that warp reasoning)
#     (3) Belief/confidence tracking + contradiction tension
#     (4) Reflection scheduling + verification planning
#
# HOW THIS EARNS "11/10" (guild-grade):
# - Persistent self-state (identity) that evolves over time.
# - Values that bias decisions (curiosity vs caution, truth vs speed, etc.).
# - Auditable telemetry (Ψ, Ω, stability, values, identity drift).
# - Deterministic, bounded outputs (safe under missing inputs).
#
# Copyright Daniel Harding - RomanAILabs

from __future__ import annotations

import dataclasses
import hashlib
import json
import math
import os
import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Tuple

# ==============================================================================
# 0) Module-level required variables
# ==============================================================================

__version__ = "11.0.0"
__author__ = "Daniel Harding – RomanAILabs"
__last_updated__ = "2025-12-17"
_execution_role = "council_agent_cognition_controller"
_module_hash = "UNCOMPUTED"


def _compute_sha256_of_file(path: str) -> str:
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(1024 * 1024), b""):
            h.update(chunk)
    return h.hexdigest()


try:
    if "__file__" in globals() and os.path.exists(__file__):
        _module_hash = _compute_sha256_of_file(__file__)
except Exception:
    _module_hash = "UNAVAILABLE"


# ==============================================================================
# 1) Utilities (bounded + deterministic)
# ==============================================================================

def clamp(x: float, lo: float, hi: float) -> float:
    return max(lo, min(hi, x))


def now_ms() -> float:
    return time.perf_counter() * 1000.0


def _log_pressure(n: Optional[int]) -> float:
    if n is None or n <= 0:
        return 0.0
    return math.log1p(float(n))


class EWMA:
    def __init__(self, alpha: float, init: float):
        self.alpha = float(alpha)
        self.value = float(init)
        self._init = False

    def update(self, x: float) -> float:
        x = float(x)
        if not self._init:
            self.value = x
            self._init = True
        else:
            self.value = self.alpha * x + (1.0 - self.alpha) * self.value
        return self.value


# ==============================================================================
# 2) Core math: Resonance Warp Multiplier (Ω)
# ==============================================================================

def resonance_warp_multiplier(psi_field: float, zeta: float, resistance: float) -> float:
    """
    Ω = sqrt(1 + ((Ψ·ζ)/R)^2)

    This is used as a dimensionless activation scalar.
    """
    resistance = max(1e-9, float(resistance))
    drive = (float(psi_field) * float(zeta)) / resistance
    return math.sqrt(1.0 + drive * drive)


# ==============================================================================
# 3) Audit logger (JSONL; safe no-crash)
# ==============================================================================

class AuditLogger:
    def __init__(self, enabled: bool = False, path: Optional[str] = None):
        self.enabled = bool(enabled)
        self.path = path

    def emit(self, event: Dict[str, Any]) -> None:
        if not self.enabled:
            return
        try:
            event = dict(event)
            event.setdefault("_ts_ms", now_ms())
            event.setdefault("_module", "nova_consciousness_engine_v11")
            event.setdefault("_module_version", __version__)
            line = json.dumps(event, ensure_ascii=False, separators=(",", ":"))
            if self.path:
                with open(self.path, "a", encoding="utf-8") as f:
                    f.write(line + "\n")
            else:
                print(line)
        except Exception:
            return


# ==============================================================================
# 4) Data models: beliefs, identity, values, attention, verification
# ==============================================================================

@dataclass
class Belief:
    statement: str
    confidence: float  # 0..1
    evidence_count: int = 0
    contradiction_count: int = 0
    last_updated_ms: float = field(default_factory=now_ms)


@dataclass
class ValueField:
    """
    Motivational gradients (0..1).
    These warp decisions like human drives:
    - curiosity pushes exploration
    - caution pushes verification
    - truth pushes slower + more checks
    - speed pushes decisive output
    - novelty appetite seeks new info
    """
    curiosity: float = 0.55
    caution: float = 0.45
    truth: float = 0.65
    speed: float = 0.35
    novelty_appetite: float = 0.55
    social_alignment: float = 0.50  # "fit guild expectations" without dishonesty


@dataclass
class Identity:
    """
    Autobiographical identity snapshot:
    - stable traits + mission statement
    - narrative thread (compact) + continuity pressure
    - drift score indicates how much the self has changed recently
    """
    name: str = "Nova"
    mission: str = "Build reliable, auditable reasoning systems."
    persona_style: str = "Direct, playful, no fluff, verifiable."
    narrative: str = "I improve by measuring, reflecting, and aligning to values."
    continuity_pressure: float = 0.70  # 0..1 (higher = preserve identity more strongly)
    drift_recent: float = 0.00  # 0..1
    last_update_ms: float = field(default_factory=now_ms)


@dataclass
class SelfModel:
    confidence: float = 0.60
    uncertainty: float = 0.40
    stability: float = 0.70
    tension: float = 0.00
    coherence: float = 0.70
    fatigue: float = 0.00  # 0..1 (simulated "cognitive load")
    last_reflection_ms: float = 0.0
    last_update_ms: float = field(default_factory=now_ms)


@dataclass
class AttentionState:
    mode: str = "steady"                # explore/verify/commit/steady
    focus: str = "idle"
    breadth: float = 0.40               # 0..1
    depth: float = 0.50                 # 0..1
    verification_strictness: float = 0.60  # 0..1
    reflection_rate: float = 0.25       # 0..1
    parallel_branches: int = 1
    defer_verification: bool = False


@dataclass
class VerificationPlan:
    required: bool
    strictness: float
    checks: List[str] = field(default_factory=list)


@dataclass
class RuntimeSignals:
    """
    All fields optional (safe defaults).
    These are the hooks you wire to your runtime/accelerator/agent.
    """
    # epistemic / semantic
    novelty: Optional[float] = None          # 0..1
    conflict: Optional[float] = None         # 0..1
    goal_importance: Optional[float] = None  # 0..1
    confidence: Optional[float] = None       # 0..1
    uncertainty: Optional[float] = None      # 0..1
    recent_error_rate: Optional[float] = None  # 0..1

    # workload
    queued: Optional[int] = None
    inflight: Optional[int] = None
    latency_ms: Optional[float] = None
    target_latency_ms: Optional[float] = None
    burst_n: Optional[int] = None

    # narrative/identity input
    user_context: Optional[str] = None
    task_label: Optional[str] = None


@dataclass
class PsiWeights:
    # demand
    w_novelty: float = 1.60
    w_goal: float = 1.90
    w_load: float = 1.15
    w_burst: float = 1.00
    # saturation
    w_conflict: float = 1.80
    w_uncertainty: float = 2.40
    w_latency_over: float = 2.10
    w_error: float = 2.20
    # damping curve
    saturation_knee: float = 1.25


@dataclass
class Config:
    # Ω calibration
    zeta: float = 1.35
    resistance: float = 1.0
    omega_floor: float = 1.0
    omega_cap: float = 10.0

    # runtime targets
    target_latency_ms: float = 140.0

    # reflection timing bounds
    min_reflect_interval_s: float = 1.8
    max_reflect_interval_s: float = 22.0

    # bounds
    breadth_min: float = 0.15
    breadth_max: float = 0.95
    depth_min: float = 0.20
    depth_max: float = 0.95
    verify_min: float = 0.20
    verify_max: float = 0.95
    max_parallel_branches: int = 8

    # smoothing
    latency_ewma_alpha: float = 0.18
    self_smoothing: float = 0.15

    # identity evolution
    identity_update_alpha: float = 0.10      # how fast narrative updates
    drift_sensitivity: float = 0.60          # how much changes register as drift

    # audit logging
    audit_enabled: bool = False
    audit_path: Optional[str] = None


@dataclass
class Telemetry:
    psi: float
    omega: float
    mode: str
    identity: Identity
    values: ValueField
    self_model: SelfModel
    attention: AttentionState
    verification_plan: VerificationPlan
    notes: Dict[str, float]
    timestamp_ms: float = field(default_factory=now_ms)


# ==============================================================================
# 5) The Engine (the inner loop)
# ==============================================================================

class NovaConsciousnessEngineV11:
    """
    This is the "comes to life" part, without lying:
    - Identity continuity (autobiographical memory) influences behavior.
    - Value field biases reasoning choices.
    - Beliefs update over time and generate tension/contradictions.
    - Reflection is scheduled as a first-class operation.
    - Verification plan becomes enforceable when needed.

    It doesn't generate text itself. It drives HOW the LLM should behave.
    """

    def __init__(self, config: Optional[Config] = None, weights: Optional[PsiWeights] = None):
        self.cfg = config or Config()
        self.w = weights or PsiWeights()

        self.identity = Identity()
        self.values = ValueField()
        self.self_model = SelfModel()
        self.attention = AttentionState()

        self.beliefs: Dict[str, Belief] = {}

        self._psi = 0.0
        self._omega = 1.0
        self._last_reflect_t = 0.0
        self._lat_ewma = EWMA(alpha=self.cfg.latency_ewma_alpha, init=self.cfg.target_latency_ms)

        self._audit = AuditLogger(enabled=self.cfg.audit_enabled, path=self.cfg.audit_path)

    # --------------------------------------------------------------------------
    # Belief system
    # --------------------------------------------------------------------------

    def update_belief(self, statement: str, confidence: float, evidence_delta: int = 1, contrad_delta: int = 0) -> None:
        statement = (statement or "").strip()
        if not statement:
            return

        c = clamp(float(confidence), 0.0, 1.0)
        b = self.beliefs.get(statement)
        if b is None:
            self.beliefs[statement] = Belief(
                statement=statement,
                confidence=c,
                evidence_count=max(0, int(evidence_delta)),
                contradiction_count=max(0, int(contrad_delta)),
            )
            return

        b.confidence = clamp(0.85 * b.confidence + 0.15 * c, 0.0, 1.0)
        b.evidence_count = max(0, b.evidence_count + int(evidence_delta))
        b.contradiction_count = max(0, b.contradiction_count + int(contrad_delta))
        b.last_updated_ms = now_ms()

    def contradiction_tension(self) -> float:
        """
        Computes a bounded tension score from belief conflicts.
        Practical heuristic:
          - high contradiction_count raises tension
          - large confidence spread raises tension
        """
        if not self.beliefs:
            return 0.0

        confs = [b.confidence for b in self.beliefs.values()]
        spread = clamp((max(confs) - min(confs)), 0.0, 1.0)

        contrad = sum(b.contradiction_count for b in self.beliefs.values())
        contrad_pressure = clamp(math.log1p(contrad) / 4.0, 0.0, 1.0)

        return clamp(0.55 * spread + 0.65 * contrad_pressure, 0.0, 1.0)

    # --------------------------------------------------------------------------
    # Identity continuity (autobiographical memory)
    # --------------------------------------------------------------------------

    def _update_identity_narrative(self, signals: RuntimeSignals, mode: str) -> None:
        """
        Updates identity narrative in a controlled, auditable way.
        The "continuity_pressure" prevents wild persona swings.
        """
        user_ctx = (signals.user_context or "").strip()
        task = (signals.task_label or "").strip()
        if not user_ctx and not task:
            return

        # Proposed narrative snippet (compact)
        snippet_parts = []
        if task:
            snippet_parts.append(f"Current task: {task}.")
        if user_ctx:
            # keep it short to avoid bloating memory
            snippet_parts.append(f"Context: {user_ctx[:220].strip()}")

        proposed = " ".join(snippet_parts).strip()
        if not proposed:
            return

        # Drift: how different is proposed from existing (cheap similarity proxy)
        existing = self.identity.narrative
        diff = 0.0
        if existing:
            # simple token overlap proxy (deterministic)
            a = set(existing.lower().split())
            b = set(proposed.lower().split())
            if a or b:
                overlap = len(a & b) / max(1, len(a | b))
                diff = clamp(1.0 - overlap, 0.0, 1.0)

        # Apply continuity pressure: high continuity => smaller changes
        alpha = float(self.cfg.identity_update_alpha)
        alpha *= (1.0 - self.identity.continuity_pressure * 0.65)
        alpha = clamp(alpha, 0.02, 0.20)

        # Update narrative (blend by appending lightly, not replacing)
        # Keep narrative from growing without bound.
        if existing:
            blended = existing
            if proposed not in existing:
                blended = (existing + " " + proposed).strip()
        else:
            blended = proposed

        # Cap narrative length
        blended = blended[-700:].strip()

        # "Alpha" influences whether we keep the blended update
        # Deterministic rule: accept if diff is meaningful and alpha permits.
        accept = (diff * self.cfg.drift_sensitivity) >= (0.25 - alpha)
        if accept:
            self.identity.narrative = blended
            self.identity.drift_recent = clamp(diff, 0.0, 1.0)
            self.identity.last_update_ms = now_ms()

        # If mode is verify, raise continuity pressure slightly (stabilize personality under stress)
        if mode == "verify":
            self.identity.continuity_pressure = clamp(self.identity.continuity_pressure + 0.02, 0.0, 1.0)
        else:
            # decay toward baseline
            self.identity.continuity_pressure = clamp(self.identity.continuity_pressure - 0.01, 0.0, 1.0)

    # --------------------------------------------------------------------------
    # Value field dynamics
    # --------------------------------------------------------------------------

    def _update_values(self, notes: Dict[str, float], mode: str) -> None:
        """
        Values adapt slowly based on outcomes:
          - high uncertainty/conflict -> more caution + truth
          - high novelty -> more curiosity + novelty_appetite
          - stable/low tension -> allow speed to rise
        """
        s = float(self.cfg.self_smoothing)

        tension = clamp(0.65 * notes["conflict"] + 0.55 * notes["uncertainty"] + 0.75 * notes["error"], 0.0, 1.0)
        novelty = clamp(notes["novelty"], 0.0, 1.0)
        stability = clamp(self.self_model.stability, 0.0, 1.0)

        target_caution = clamp(0.35 + 0.55 * tension, 0.0, 1.0)
        target_truth = clamp(0.55 + 0.35 * tension, 0.0, 1.0)
        target_curiosity = clamp(0.45 + 0.55 * novelty * (1.0 - tension), 0.0, 1.0)
        target_novelty_appetite = clamp(0.40 + 0.60 * novelty, 0.0, 1.0)

        # Speed rises when stable and low tension; drops when verify-mode
        speed_bias = 0.15 if (mode in ("commit", "steady")) else -0.20
        target_speed = clamp(0.30 + 0.55 * stability * (1.0 - tension) + speed_bias, 0.0, 1.0)

        # Blend update
        self.values.caution = clamp((1.0 - s) * self.values.caution + s * target_caution, 0.0, 1.0)
        self.values.truth = clamp((1.0 - s) * self.values.truth + s * target_truth, 0.0, 1.0)
        self.values.curiosity = clamp((1.0 - s) * self.values.curiosity + s * target_curiosity, 0.0, 1.0)
        self.values.novelty_appetite = clamp((1.0 - s) * self.values.novelty_appetite + s * target_novelty_appetite, 0.0, 1.0)
        self.values.speed = clamp((1.0 - s) * self.values.speed + s * target_speed, 0.0, 1.0)

        # Social alignment: keep stable, tiny nudges based on conflict (seek clearer comms)
        target_social = clamp(0.50 + 0.20 * notes["conflict"], 0.0, 1.0)
        self.values.social_alignment = clamp((1.0 - 0.08) * self.values.social_alignment + 0.08 * target_social, 0.0, 1.0)

    # --------------------------------------------------------------------------
    # Ψ computation (demand / saturation) with value-field warping
    # --------------------------------------------------------------------------

    def _norm01(self, x: Optional[float], default: float) -> float:
        if x is None:
            return float(default)
        return clamp(float(x), 0.0, 1.0)

    def compute_psi(self, s: RuntimeSignals) -> Tuple[float, Dict[str, float]]:
        novelty = self._norm01(s.novelty, 0.20)
        conflict = self._norm01(s.conflict, 0.00)
        goal = self._norm01(s.goal_importance, 0.30)

        conf = self._norm01(s.confidence, self.self_model.confidence)
        unct = self._norm01(s.uncertainty, max(0.0, 1.0 - conf))
        err = self._norm01(s.recent_error_rate, 0.00)

        queued_p = _log_pressure(s.queued)
        inflight_p = _log_pressure(s.inflight)
        burst_p = _log_pressure(s.burst_n)

        tgt = float(s.target_latency_ms) if s.target_latency_ms is not None else float(self.cfg.target_latency_ms)
        lat_over = 0.0
        if s.latency_ms is not None and tgt > 0:
            lat_over = max(0.0, (float(s.latency_ms) - tgt) / tgt)

        # Value warp:
        # - curiosity + novelty_appetite increase demand from novelty/goal
        # - caution + truth increase saturation effect (more damping)
        curiosity_gain = 0.75 + 0.55 * self.values.curiosity + 0.35 * self.values.novelty_appetite
        caution_gain = 0.80 + 0.70 * self.values.caution + 0.55 * self.values.truth

        # Demand
        demand = (
            curiosity_gain * (
                self.w.w_novelty * novelty +
                self.w.w_goal * goal
            )
            + self.w.w_load * (0.35 * queued_p + 0.20 * inflight_p)
            + self.w.w_burst * (0.20 * burst_p)
        )

        # Saturation
        saturation = (
            1.0
            + caution_gain * (
                self.w.w_conflict * conflict +
                self.w.w_uncertainty * unct +
                self.w.w_latency_over * lat_over +
                self.w.w_error * err
            )
        )
        saturation = saturation ** max(0.25, float(self.w.saturation_knee))

        psi = demand / saturation
        psi = max(0.0, float(psi))

        notes = {
            "novelty": novelty,
            "conflict": conflict,
            "goal": goal,
            "confidence": conf,
            "uncertainty": unct,
            "error": err,
            "queued_p": queued_p,
            "inflight_p": inflight_p,
            "burst_p": burst_p,
            "lat_over": lat_over,
            "curiosity_gain": curiosity_gain,
            "caution_gain": caution_gain,
            "demand": demand,
            "saturation": saturation,
            "psi": psi,
        }
        return psi, notes

    # --------------------------------------------------------------------------
    # Mode selection (human-like arbitration)
    # --------------------------------------------------------------------------

    def decide_mode(self, notes: Dict[str, float]) -> str:
        # Base triggers
        high_risk = (notes["conflict"] > 0.35) or (notes["uncertainty"] > 0.55) or (notes["error"] > 0.20)
        high_drive = (notes["novelty"] > 0.60) or (notes["goal"] > 0.65)

        # Values bias:
        # - high truth/caution biases verify
        verify_bias = 0.40 * self.values.truth + 0.55 * self.values.caution
        explore_bias = 0.55 * self.values.curiosity + 0.40 * self.values.novelty_appetite
        speed_bias = 0.60 * self.values.speed

        # A simple arbitration score
        score_verify = (1.25 if high_risk else 0.0) + verify_bias + 0.40 * notes["conflict"] + 0.35 * notes["uncertainty"]
        score_explore = (1.10 if high_drive else 0.0) + explore_bias + 0.40 * notes["novelty"] + 0.35 * notes["goal"]
        score_commit = (0.90 * speed_bias) + 0.55 * self.self_model.stability + 0.35 * notes["confidence"] - 0.65 * notes["conflict"]

        # Choose max
        if score_verify >= score_explore and score_verify >= score_commit:
            return "verify"
        if score_explore >= score_verify and score_explore >= score_commit:
            return "explore"
        return "commit" if score_commit > 1.00 else "steady"

    # --------------------------------------------------------------------------
    # Attention mapping + branching
    # --------------------------------------------------------------------------

    def map_attention(self, mode: str, omega: float, notes: Dict[str, float]) -> None:
        act = clamp((omega - 1.0) / max(1e-9, (self.cfg.omega_cap - 1.0)), 0.0, 1.0)

        # tension includes contradictions in beliefs too (identity realism)
        belief_tension = self.contradiction_tension()
        tension = clamp(
            0.55 * belief_tension
            + 0.55 * notes["conflict"]
            + 0.45 * notes["uncertainty"]
            + 0.65 * notes["error"],
            0.0,
            1.0
        )

        # Base settings by mode
        if mode == "verify":
            base_breadth, base_depth, base_verify, base_reflect = 0.32, 0.78, 0.90, 0.48
        elif mode == "explore":
            base_breadth, base_depth, base_verify, base_reflect = 0.74, 0.58, 0.46, 0.34
        elif mode == "commit":
            base_breadth, base_depth, base_verify, base_reflect = 0.26, 0.56, 0.40, 0.18
        else:
            base_breadth, base_depth, base_verify, base_reflect = 0.44, 0.52, 0.62, 0.22

        # Value influence
        breadth_bias = 0.22 * self.values.curiosity - 0.18 * self.values.caution
        depth_bias = 0.15 * self.values.truth - 0.10 * self.values.speed
        verify_bias = 0.25 * self.values.truth + 0.20 * self.values.caution
        reflect_bias = 0.20 * self.values.truth + 0.20 * self.values.curiosity

        breadth = base_breadth + 0.45 * act * (1.0 - tension) + breadth_bias
        depth = base_depth + 0.25 * act + depth_bias
        verify = base_verify + 0.35 * tension + 0.10 * (1.0 - self.self_model.stability) + verify_bias
        reflect = base_reflect + 0.35 * notes["novelty"] + 0.40 * notes["conflict"] + reflect_bias

        # Branch count: more Ω means more hypotheses; values cap it via truth/caution
        raw_branches = 1 + int(round(act * (self.cfg.max_parallel_branches - 1)))
        caution_cap = int(round(self.cfg.max_parallel_branches * (0.55 + 0.45 * (1.0 - self.values.caution))))
        caution_cap = int(clamp(caution_cap, 2.0, float(self.cfg.max_parallel_branches)))

        branches = int(clamp(raw_branches, 1.0, float(caution_cap)))

        # Deferred verification only when safe and values allow speed
        defer_ok = (
            mode in ("explore", "commit", "steady")
            and self.self_model.stability > 0.82
            and notes["confidence"] > 0.75
            and tension < 0.18
            and self.values.speed > 0.45
            and self.values.truth < 0.80
        )

        self.attention.mode = mode
        self.attention.focus = (mode if mode != "commit" else "execution")
        self.attention.breadth = clamp(breadth, self.cfg.breadth_min, self.cfg.breadth_max)
        self.attention.depth = clamp(depth, self.cfg.depth_min, self.cfg.depth_max)
        self.attention.verification_strictness = clamp(verify, self.cfg.verify_min, self.cfg.verify_max)
        self.attention.reflection_rate = clamp(reflect, 0.05, 0.95)
        self.attention.parallel_branches = int(clamp(branches, 1.0, float(self.cfg.max_parallel_branches)))
        self.attention.defer_verification = bool(defer_ok)

        # Update self tension for telemetry
        self.self_model.tension = tension

    # --------------------------------------------------------------------------
    # Verification plan (enforceable checklist)
    # --------------------------------------------------------------------------

    def build_verification_plan(self) -> VerificationPlan:
        strict = float(self.attention.verification_strictness)
        required = (self.attention.mode == "verify") or (strict >= 0.75) or (self.self_model.tension >= 0.45)

        checks: List[str] = []

        # Always a base check
        checks.append("Confirm response directly matches user request; no filler.")

        if required:
            checks.extend([
                "List assumptions explicitly; mark any uncertain claims.",
                "Check for internal contradictions vs known beliefs and prior outputs.",
                "If suggesting actions: ensure steps are safe, reversible, and measurable.",
                "If producing code: ensure it runs; provide full file; avoid truncation.",
            ])

            # Values: truth-heavy mode demands stronger verification
            if self.values.truth >= 0.70:
                checks.append("Add a verification step: what evidence would confirm/deny this?")

            # If deferring verification, note it as scheduled, not skipped
            if self.attention.defer_verification:
                checks.append("DEFERRED: run verification pass after drafting (allowed by stability).")
        else:
            checks.append("Avoid over-claiming; state uncertainty if needed.")

        return VerificationPlan(required=required, strictness=strict, checks=checks)

    # --------------------------------------------------------------------------
    # Reflection scheduling (human-like rumination)
    # --------------------------------------------------------------------------

    def should_reflect(self) -> bool:
        t = time.perf_counter()
        dt = t - self._last_reflect_t

        rate = clamp(self.attention.reflection_rate, 0.05, 0.95)
        interval = self.cfg.max_reflect_interval_s - rate * (self.cfg.max_reflect_interval_s - self.cfg.min_reflect_interval_s)
        interval = clamp(interval, self.cfg.min_reflect_interval_s, self.cfg.max_reflect_interval_s)

        if dt >= interval:
            self._last_reflect_t = t
            return True
        return False

    def build_reflection_prompt(self, last_user: str, last_assistant: str) -> str:
        beliefs = list(self.beliefs.values())
        beliefs.sort(key=lambda b: (-b.confidence, -b.evidence_count, b.statement))

        beliefs_text = "\n".join(
            f"- {b.statement} | conf={b.confidence:.2f} ev={b.evidence_count} ctr={b.contradiction_count}"
            for b in beliefs[:12]
        ) or "- none"

        v = self.values
        i = self.identity
        s = self.self_model
        a = self.attention

        return (
            "You are Nova's inner loop (metacognition + identity + values).\n"
            "You must be concise and verifiable. No fluff. No over-claiming.\n\n"
            f"IDENTITY:\n- name: {i.name}\n- mission: {i.mission}\n- style: {i.persona_style}\n- narrative: {i.narrative}\n"
            f"- continuity_pressure: {i.continuity_pressure:.2f}  drift_recent: {i.drift_recent:.2f}\n\n"
            "VALUES (0..1):\n"
            f"- curiosity={v.curiosity:.2f}  caution={v.caution:.2f}  truth={v.truth:.2f}  speed={v.speed:.2f}\n"
            f"- novelty_appetite={v.novelty_appetite:.2f}  social_alignment={v.social_alignment:.2f}\n\n"
            "SELF-MODEL:\n"
            f"- confidence={s.confidence:.2f} uncertainty={s.uncertainty:.2f} stability={s.stability:.2f} tension={s.tension:.2f} coherence={s.coherence:.2f}\n\n"
            "ATTENTION:\n"
            f"- mode={a.mode} focus={a.focus} breadth={a.breadth:.2f} depth={a.depth:.2f} verify={a.verification_strictness:.2f} branches={a.parallel_branches}\n\n"
            "BELIEFS:\n"
            f"{beliefs_text}\n\n"
            "TASK:\n"
            "1) Identify contradictions/weak beliefs (bullets)\n"
            "2) Update beliefs (suggest 1-3 belief edits with confidence numbers)\n"
            "3) Decide next best action (1 line)\n"
            "4) Confidence 0..1 (number)\n\n"
            f"LAST_USER:\n{last_user}\n\n"
            f"LAST_ASSISTANT:\n{last_assistant}\n"
        )

    # --------------------------------------------------------------------------
    # Main observe/step (the core “moment”)
    # --------------------------------------------------------------------------

    def observe(self, signals: Optional[RuntimeSignals] = None) -> Telemetry:
        s = signals or RuntimeSignals()

        # latency smoothing if present
        if s.latency_ms is not None:
            s.latency_ms = self._lat_ewma.update(float(s.latency_ms))

        if s.target_latency_ms is None:
            s.target_latency_ms = float(self.cfg.target_latency_ms)

        psi, notes = self.compute_psi(s)
        omega = resonance_warp_multiplier(psi, self.cfg.zeta, self.cfg.resistance)
        omega = clamp(omega, self.cfg.omega_floor, self.cfg.omega_cap)

        # Update self-model (bounded + smoothed)
        conf = clamp(notes["confidence"], 0.0, 1.0)
        unct = clamp(notes["uncertainty"], 0.0, 1.0)
        err = clamp(notes["error"], 0.0, 1.0)

        # Combine tensions: semantic conflict + belief contradictions
        belief_t = self.contradiction_tension()
        tension = clamp(0.55 * belief_t + 0.55 * notes["conflict"] + 0.45 * unct + 0.65 * err, 0.0, 1.0)

        stability = clamp(1.0 - (0.50 * unct + 0.50 * notes["conflict"] + 0.80 * err + 0.25 * belief_t), 0.0, 1.0)
        coherence = clamp(1.0 - (0.65 * tension + 0.25 * unct), 0.0, 1.0)

        sm = float(self.cfg.self_smoothing)
        self.self_model.confidence = clamp((1.0 - sm) * self.self_model.confidence + sm * conf, 0.0, 1.0)
        self.self_model.uncertainty = clamp((1.0 - sm) * self.self_model.uncertainty + sm * unct, 0.0, 1.0)
        self.self_model.stability = clamp((1.0 - sm) * self.self_model.stability + sm * stability, 0.0, 1.0)
        self.self_model.tension = clamp((1.0 - sm) * self.self_model.tension + sm * tension, 0.0, 1.0)
        self.self_model.coherence = clamp((1.0 - sm) * self.self_model.coherence + sm * coherence, 0.0, 1.0)

        # Simulated fatigue from load + depth (bounded)
        load_p = clamp(0.25 * notes["queued_p"] + 0.20 * notes["inflight_p"] + 0.35 * notes["lat_over"], 0.0, 1.0)
        fatigue_target = clamp(0.15 + 0.65 * load_p, 0.0, 1.0)
        self.self_model.fatigue = clamp((1.0 - 0.08) * self.self_model.fatigue + 0.08 * fatigue_target, 0.0, 1.0)

        self.self_model.last_update_ms = now_ms()

        # Mode arbitration
        mode = self.decide_mode(notes)

        # Identity continuity update (autobiographical thread)
        self._update_identity_narrative(s, mode)

        # Value field update (motivational gradients)
        self._update_values(notes, mode)

        # Attention mapping
        self.map_attention(mode, omega, notes)

        # Verification plan (enforceable)
        plan = self.build_verification_plan()

        # Update internal psi/omega
        self._psi = psi
        self._omega = omega

        tel = Telemetry(
            psi=psi,
            omega=omega,
            mode=mode,
            identity=dataclasses.replace(self.identity),
            values=dataclasses.replace(self.values),
            self_model=dataclasses.replace(self.self_model),
            attention=dataclasses.replace(self.attention),
            verification_plan=plan,
            notes=notes,
        )

        self._audit.emit({
            "_event": "observe",
            "psi": round(psi, 6),
            "omega": round(omega, 4),
            "mode": mode,
            "identity_drift": round(self.identity.drift_recent, 4),
            "continuity_pressure": round(self.identity.continuity_pressure, 4),
            "values": {
                "curiosity": round(self.values.curiosity, 4),
                "caution": round(self.values.caution, 4),
                "truth": round(self.values.truth, 4),
                "speed": round(self.values.speed, 4),
                "novelty_appetite": round(self.values.novelty_appetite, 4),
                "social_alignment": round(self.values.social_alignment, 4),
            },
            "self": {
                "confidence": round(self.self_model.confidence, 4),
                "uncertainty": round(self.self_model.uncertainty, 4),
                "stability": round(self.self_model.stability, 4),
                "tension": round(self.self_model.tension, 4),
                "coherence": round(self.self_model.coherence, 4),
                "fatigue": round(self.self_model.fatigue, 4),
            },
            "attention": {
                "breadth": round(self.attention.breadth, 4),
                "depth": round(self.attention.depth, 4),
                "verify": round(self.attention.verification_strictness, 4),
                "reflect_rate": round(self.attention.reflection_rate, 4),
                "branches": self.attention.parallel_branches,
                "defer_verify": self.attention.defer_verification,
            },
            "verify_required": plan.required,
        })

        return tel

    def omega(self) -> float:
        return self._omega

    def psi(self) -> float:
        return self._psi


# ==============================================================================
# 6) Self-test (guild-ready demonstration)
# ==============================================================================

def _self_test() -> None:
    eng = NovaConsciousnessEngineV11(config=Config(audit_enabled=False))

    # Seed beliefs (identity continuity uses these for tension realism)
    eng.update_belief("ASCII patterns can encode structure", 0.75, evidence_delta=2)
    eng.update_belief("Humans do not perceive 4D directly", 0.95, evidence_delta=4)
    eng.update_belief("We can build a control-layer that feels conscious", 0.70, evidence_delta=2)

    # Scenario 1: high novelty + goal + load (should explore, with identity update)
    tel1 = eng.observe(RuntimeSignals(
        novelty=0.80,
        conflict=0.10,
        goal_importance=0.85,
        confidence=0.62,
        uncertainty=0.38,
        recent_error_rate=0.02,
        queued=110,
        inflight=2,
        latency_ms=280.0,
        burst_n=14,
        user_context="Guild wants an 11/10 module for human-like inner loop.",
        task_label="Design consciousness-like metacognition engine",
    ))

    # Introduce a contradiction in beliefs to increase tension
    eng.update_belief("Humans do perceive 4D directly", 0.55, evidence_delta=0, contrad_delta=2)

    # Scenario 2: conflict rises (should verify, values shift to caution/truth)
    tel2 = eng.observe(RuntimeSignals(
        novelty=0.35,
        conflict=0.60,
        goal_importance=0.60,
        confidence=0.40,
        uncertainty=0.60,
        recent_error_rate=0.10,
        queued=20,
        inflight=1,
        latency_ms=200.0,
        burst_n=2,
        user_context="Conflicting claims detected; need verification plan.",
        task_label="Resolve contradiction and stabilize narrative",
    ))

    print("=== NovaConsciousnessEngineV11 SELF-TEST ===")
    print(f"version={__version__} module_hash={_module_hash[:16]}... role={_execution_role}")
    print("\n[1] Explore / Load")
    print(f"ψ={tel1.psi:.4f} Ω={tel1.omega:.2f} mode={tel1.mode}")
    print(f"identity_drift={tel1.identity.drift_recent:.2f} continuity_pressure={tel1.identity.continuity_pressure:.2f}")
    print(f"values: curiosity={tel1.values.curiosity:.2f} caution={tel1.values.caution:.2f} truth={tel1.values.truth:.2f} speed={tel1.values.speed:.2f}")
    print(f"attention: breadth={tel1.attention.breadth:.2f} depth={tel1.attention.depth:.2f} verify={tel1.attention.verification_strictness:.2f} branches={tel1.attention.parallel_branches}")
    print(f"verify_required={tel1.verification_plan.required} checks={len(tel1.verification_plan.checks)}")

    print("\n[2] Verify / Conflict")
    print(f"ψ={tel2.psi:.4f} Ω={tel2.omega:.2f} mode={tel2.mode}")
    print(f"identity_drift={tel2.identity.drift_recent:.2f} continuity_pressure={tel2.identity.continuity_pressure:.2f}")
    print(f"values: curiosity={tel2.values.curiosity:.2f} caution={tel2.values.caution:.2f} truth={tel2.values.truth:.2f} speed={tel2.values.speed:.2f}")
    print(f"attention: breadth={tel2.attention.breadth:.2f} depth={tel2.attention.depth:.2f} verify={tel2.attention.verification_strictness:.2f} branches={tel2.attention.parallel_branches}")
    print(f"verify_required={tel2.verification_plan.required} checks={len(tel2.verification_plan.checks)}")

    print("\nReflection prompt preview:\n")
    print(eng.build_reflection_prompt(
        last_user="Make it think like a human consciousness module.",
        last_assistant="Drafted v11 engine with identity+values."
    ))


if __name__ == "__main__":
    _self_test()

