from __future__ import annotations

import hashlib
import json
import logging
import re
from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
from typing import (
    TYPE_CHECKING,
    Any,
    Dict,
    Iterable,
    List,
    Literal,
    Mapping,
    MutableMapping,
    Optional,
    Sequence,
    Set,
    Tuple,
    Type,
    Union,
)

import numpy as np
from pydantic import AliasChoices

from receipts.compat.pyd import BaseModel, ConfigDictLike, Field, field_validator, model_validator

from receipts.base import (
    BaseReceipt,
    ComplianceAssertionProof,
    ComplianceAssertionProofStep,
    ConformalMetadata,
    CoherenceStatus,
    Domain,
    Ed25519Signature,
    EVHDStatus,
    FeatureDriftContributor,
    GateEvent,
    GateStatus,
    MonotonicityCheck,
    MonotonicityStatus,
    OVERRIDE_JUSTIFICATION_DENYLIST,
    PADenialReasonPayload,
    PAOutcomeSLA,
    PASLADetail,
    PASLAStatus,
    PolicyWitness as BasePolicyWitness,
    RecourseItem,
    RefusalAuditFix,
    RefusalBudgetStatus,
    RefusalLeverRecommendation,
    RefusalMetadata,
    RefusalReasonSummary,
    SavedEVTrendPoint,
    SCHEMA_VERSION,
    UncertaintyLineage,
    UncertaintyNeighbor,
    ReceiptAnchor,
    _coerce_signature_envelope,
)
try:
    from receipts.domains.mlb import (
        GateWitness,
        GateWitnessNode,
        GateWitnessVerdict,
        OutcomeReceipt,
        ProphecyStone,
        RefusalStone,
    )
except Exception:  # pragma: no cover - slim environments where mlb domain is unavailable
    # Provide lightweight stand-ins so modules depending on receipts.models
    # can still import in environments where the mlb domain is not present.
    from typing import Any

    class _MlbStub:
        def __init__(self, *args: Any, **kwargs: Any) -> None:
            pass

    GateWitness = GateWitnessNode = GateWitnessVerdict = OutcomeReceipt = ProphecyStone = RefusalStone = _MlbStub
from receipts.domains.governance import (
    AccessAuditReceipt,
    AnchoringBatchCounts,
    AnchoringBatchReceipt,
    AnchoringBatchWindow,
    AuthorizationRefusalReceipt,
    ComplianceAssertionReceipt,
    ConstitutionalSnapshot,
    DriftAlert,
    DriftType,
    EvidenceQuorumReceipt,
    GateTuningReceipt,
    GatebookChangeReceipt,
    GhostReceipt,
    GuardianApprovalStatus,
    GuardianSignature,
    MultiSigApprovalState,
    MultiSigGuardianApproval,
    NonIntermixAuditReceipt,
    OperationsDecisionReceipt,
    OptimizationGuardReceipt,
    OverrideDisposition,
    OverrideQualityReceipt,
    OverrideQualityVerdict,
    OverrideReceipt,
    OverrideRiskLevel,
    PolicyChangeReceipt,
    PolicyDecisionReceipt,
    PolicyFreezeProposal,
    PolicyImpactProjection,
    PolicyPreflight,
    PolicyRecommendation,
    PolicyUnfreezeProposal,
    FreezeDecisionReceipt,
    QuorumSatisfiedReceipt,
    ReviewActionReceipt,
    TimestampPolicyReceipt,
    VetoDecisionReceipt,
)
try:
    from receipts.domains.permission import PermissionReceipt
except Exception:
    PermissionReceipt = None
from receipts.domains.markets import (
    AnchoringSLOReceipt,
    CompostStone,
    ForgivenessStone,
    MarketCoherenceOffender,
    MarketCoherenceReceipt,
    MetricEffectSummary,
    PerfBudgetReceipt,
    ProofOfRepairReceipt,
    RecoveryReceipt,
    RemediationProposalReceipt,
    SignerHealthReceipt,
    WindowMetricsSummary,
)
from receipts.domains.deprecated.pa import (
    ClaimAppealScoreReceipt,
    DenialRiskScoreReceipt,
    EvidenceAttachmentReceipt,
    PAApprovalScoreReceipt,
    PADecisionReceipt,
    PATimeToDecisionReceipt,
    SanityReadReceipt,
)
from tools import proofkit as proofkit_module
from receipts.immutability import FrozenDict, FrozenList
from receipts.compat.pyd import unwrap_frozen
from ops.opa.version import OPA_POLICY_BUNDLE_SHA256, OPA_POLICY_VERSION


if TYPE_CHECKING:
    from core.dignity import DignityVector
    from engine.agent.base import AgentResult
else:  # pragma: no cover - runtime fallback avoids circular import
    AgentResult = Any  # type: ignore[assignment]

logger = logging.getLogger(__name__)


class DIPProfile(str, Enum):
    """Levels of determinism enforced for a DIPReceipt."""
    STRICT = "strict"  # bitwise reproducible
    QUASI = "quasi"    # epsilon-bounded numeric deviations allowed
    SIM = "sim"        # stochastic simulation validated statistically


class DIPReceipt(BaseReceipt):
    """Data Input Proof receipt"""
    receipt_type: str = "DIPReceipt"
    asset_id: str
    profile: DIPProfile = Field(default=DIPProfile.STRICT)
    seed: Optional[str] = None
    code_hash: Optional[str] = None
    data_manifest_hash: Optional[str] = Field(
        default=None,
        validation_alias=AliasChoices("data_manifest_hash", "data_hash"),
        serialization_alias="data_manifest_hash",
        description="SHA-256 manifest digest of deterministic input data surfaces",
    )
    model_hash: Optional[str] = Field(
        default=None,
        validation_alias=AliasChoices("model_hash", "params_hash"),
        serialization_alias="model_hash",
        description="SHA-256 digest of the model weights or parameters used for this DIP",
    )
    gatebook_hash: Optional[str] = None
    policy_bundle_hash: Optional[str] = Field(
        default=OPA_POLICY_BUNDLE_SHA256,
        description="SHA-256 digest of the policy bundle active when this DIP was captured",
    )
    feature_contract_hash: Optional[str] = Field(
        default=None,
        description="Digest of the feature contract version enforced for this DIP",
    )
    environment_fingerprint: Dict[str, str] = Field(
        default_factory=dict,
        validation_alias=AliasChoices("environment_fingerprint", "env_fingerprints"),
        serialization_alias="environment_fingerprint",
        description="Deterministic runtime fingerprint (python, platform, dependency lock, etc.)",
    )
    compiler_hash: Optional[str] = None
    compiler_flags: Optional[str] = None
    sbom_hash: Optional[str] = None
    kernel_version: Optional[str] = None
    container_digest: Optional[str] = None
    locale: Optional[str] = None
    timezone: Optional[str] = None
    tolerance_epsilon: Optional[float] = None
    tolerance_norm: Optional[str] = None
    verifier_signatures: List[Ed25519Signature] = Field(
        default_factory=list,
        validation_alias=AliasChoices("verifier_signatures", "verifier_signature"),
        serialization_alias="verifier_signatures",
    )
    verification_policy: str = "two_party"
    cutoff_ts: Optional[datetime] = Field(
        default=None,
        description="Latest permissible upstream data timestamp captured by this DIP",
    )


class DIPReproReceipt(BaseReceipt):
    """Strict replay verification receipt for DIP reproducibility."""

    receipt_type: str = "DIPReproReceipt"
    dip_id: str = Field(description="Identifier of the DIPReceipt being verified.")
    profile: DIPProfile = Field(description="Determinism profile recorded on the DIPReceipt.")
    fingerprints: Dict[str, Any] = Field(
        default_factory=dict,
        description="Environment fingerprint comparison (expected vs observed, mismatches).",
    )
    matched: bool = Field(
        default=False,
        description="True when runtime environment and digests match the recorded DIPReceipt.",
    )
    notes: Optional[str] = Field(
        default=None,
        description="Optional free-form diagnostics or remediation notes.",
    )
    related_ids: Dict[str, Any] = Field(
        default_factory=dict,
        description="Lineage linkage for the verified DIP (e.g., dip_receipt_id).",
    )
    anchor: Optional[ReceiptAnchor] = Field(
        default=None,
        description="Sigstore Rekor anchoring metadata for the DIP receipt.",
    )
    
    # S6: Memory context transparency
    context_links: List[str] = Field(
        default_factory=list,
        description="Memory record IDs that informed this DIP (links only, no values per C5 guard)",
    )

    @property
    def data_hash(self) -> Optional[str]:
        """Backward-compatible accessor for legacy DIP v1 field name."""
        return self.data_manifest_hash

    @property
    def params_hash(self) -> Optional[str]:
        """Backward-compatible accessor for legacy DIP v1 field name."""
        return self.model_hash

    @property
    def env_fingerprints(self) -> Dict[str, str]:
        """Backward-compatible accessor for legacy DIP v1 field name."""
        return self.environment_fingerprint

    @model_validator(mode="before")
    @classmethod
    def _coerce_verifier_signatures(cls, data: Any) -> Any:
        if not isinstance(data, dict):
            return data
        payload = dict(data)
        raw = payload.get("verifier_signatures")
        if raw is None and "verifier_signature" in payload:
            raw = payload["verifier_signature"]
        if raw is None:
            return payload
        payload["verifier_signatures"] = _coerce_signature_envelope(
            raw, f"{cls.__name__}.verifier_signatures"
        )
        payload.pop("verifier_signature", None)
        return payload

class LineIntegritySource(str, Enum):
    """Enumerated external feeds monitored for line integrity."""

    ODDS = "odds"
    SCHEDULES = "schedules"
    RATINGS = "ratings"
    INJURIES = "injuries"
    WEATHER = "weather"


class LineIntegrityReceipt(BaseReceipt):
    """Snapshot receipt that binds raw decisional context at a specific cutoff."""
    receipt_type: str = "LineIntegrityReceipt"
    data_source: LineIntegritySource = Field(description="Identifier for the upstream data feed or provider")
    cutoff_ts: datetime = Field(description="Timestamp when the payload was finalised for decisioning")
    raw_data_hash: str = Field(description="SHA-256 of the canonicalized payload for tamper detection")
    preview: Dict[str, Any] = Field(default_factory=dict, description="Human-auditable glimpse of the captured payload")


class ReceiptArchiveReceipt(BaseReceipt):
    """Receipt documenting archival of aged receipts into Parquet."""

    receipt_type: str = "ReceiptArchiveReceipt"
    partition: str = Field(description="ISO-8601 date associated with the archived ledger partition")
    archive_path: str = Field(description="Relative path to the Parquet archive bundle under DATA_ROOT")
    manifest_path: str = Field(description="Relative path to the manifest JSON containing SHA-256 digests")
    archive_sha256: str = Field(description="SHA-256 digest of the written Parquet archive")
    manifest_sha256: str = Field(description="SHA-256 digest of the manifest file contents")
    record_count: int = Field(description="Number of receipts included in the archive bundle")
    source_receipt_ids: List[str] = Field(
        default_factory=list,
        description="Receipt identifiers bundled within the archive",
    )
    digest_algorithm: Literal["sha256"] = Field(
        default="sha256",
        description="Digest algorithm used for manifest and archive integrity checks",
    )


class AnchorReceipt(BaseReceipt):
    """Receipt documenting transparency-log anchoring for an upstream receipt."""

    receipt_type: str = "AnchorReceipt"
    anchor_id: str = Field(description="Identifier returned by the anchoring backend")
    anchored_receipt_id: str = Field(description="Primary receipt identifier secured by the anchor")
    anchored_receipt_hash: str = Field(description="Canonical digest of the anchored receipt body")
    backend: str = Field(description="Backend descriptor used for anchoring (e.g., json:/path/to/log)")
    proof: Dict[str, Any] = Field(default_factory=dict, description="Backend-specific inclusion proof payload")


class ReceiptAnchor(BaseModel):
    """Embedded transparency anchor metadata captured on critical receipts."""

    model_config = ConfigDictLike(frozen=True)

    payload_sha256: str = Field(
        description="Canonical SHA-256 digest of the receipt body (excluding anchor/signature metadata).",
    )
    anchored_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="UTC timestamp when the anchor was minted.",
    )
    anchor_version: str = Field(
        default="v1",
        description="Anchor schema version identifier.",
    )
    rekor_uuid: Optional[str] = Field(
        default=None,
        description="Sigstore Rekor transparency log entry UUID.",
    )
    rekor_log_index: Optional[int] = Field(
        default=None,
        ge=0,
        description="Rekor log index associated with the anchor entry.",
    )
    rekor_integrated_time: Optional[int] = Field(
        default=None,
        ge=0,
        description="Rekor integrated time (Unix epoch seconds).",
    )
    rekor_inclusion_proof: Optional[Dict[str, Any]] = Field(
        default=None,
        description="Full Rekor inclusion proof payload returned by the transparency log.",
    )


class PolicyWitness(BasePolicyWitness):
    """Structured policy compliance witness attached to decision receipts."""

    model_config = ConfigDictLike(frozen=True)

    @field_validator("gatebook_digest")
    @classmethod
    def _validate_digest(cls, value: str) -> str:
        if not isinstance(value, str) or not value.startswith("sha256:") or len(value) <= 7:
            raise ValueError("gatebook_digest must be a sha256:<hex> string")
        return value

    @field_validator("inputs_hash")
    @classmethod
    def _validate_inputs_hash(cls, value: Optional[str]) -> Optional[str]:
        if value is None:
            return value
        if not isinstance(value, str) or not value.startswith("sha256:") or len(value) <= 7:
            raise ValueError("inputs_hash must be a sha256:<hex> string")
        return value

    ci_fallback: Optional[bool] = Field(
        default=None,
        description="True when conformal fell back to a conservative interval.",
    )


class AgentActionReceipt(BaseReceipt):
    receipt_type: str = "AgentActionReceipt"
    domain: str = Field(description="Domain where the agent action was executed")
    agent_id: str = Field(description="Identifier for the acting agent (e.g., governor)")
    action: str = Field(description="Name of the action performed")
    inputs_hash: Optional[str] = Field(default=None, description="Hash of the inputs considered")
    outputs_hash: Optional[str] = Field(default=None, description="Hash of the outputs produced")
    context: Dict[str, Any] = Field(default_factory=dict, description="Additional structured context")
    parent_ids: List[str] = Field(default_factory=list, description="Upstream receipts influencing this action")
    policy_witness: Optional[PolicyWitness] = Field(
        default=None,
        description="Policy witness attesting the action outcome.",
    )


class ForecastScoreReceipt(BaseReceipt):
    """Scores a prior forecast against an observed outcome or expired horizon."""

    receipt_type: str = "ForecastScoreReceipt"

    forecast_receipt_id: str = Field(description="Identifier of the receipt that contained the original forecast.")
    forecaster_id: Optional[str] = Field(
        default=None,
        description="Identifier of the forecaster/seat/agent that emitted the forecast.",
    )
    target_type: Optional[str] = Field(
        default=None,
        description="Receipt type the forecast targeted for verification.",
    )
    horizon_s: Optional[int] = Field(
        default=None,
        ge=0,
        description="Horizon in seconds after which the forecast was scored if no outcome arrived.",
    )
    p_success: Optional[float] = Field(
        default=None,
        ge=0.0,
        le=1.0,
        description="Probability of success predicted by the forecaster.",
    )
    outcome: Optional[Literal["success", "failure", "unknown"]] = Field(
        default=None,
        description="Observed outcome relative to the forecast target.",
    )
    score_brier: Optional[float] = Field(
        default=None,
        ge=0.0,
        description="Brier score for the forecast (lower is better).",
    )
    score_log: Optional[float] = Field(
        default=None,
        description="Log loss score for the forecast (lower is better).",
    )
    time_to_regret_ms: Optional[int] = Field(
        default=None,
        ge=0,
        description="Milliseconds until contradiction or confirmation; None if not applicable.",
    )
    evaluated_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="When the forecast was evaluated.",
    )


class DignityLedgerEntryReceipt(BaseReceipt):
    """Receipt emitted for each dignity ledger mutation."""

    receipt_type: str = "DignityLedgerEntryReceipt"
    domain: str = "governance"
    actor: str = Field(description="Actor whose dignity balance changed")
    role: str = Field(description="Role linked to the actor")
    action: str = Field(description="Ledger action identifier (e.g., override.high)")
    delta: float = Field(description="Signed dignity delta applied to the ledger")
    balance_after: float = Field(description="Resulting dignity balance after applying the delta")
    ttl_days: int = Field(default=90, description="TTL window in days before the entry expires")
    metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional structured context")


class DignityBudgetRefusal(BaseReceipt):
    """Receipt documenting a refusal due to exhausted dignity budgets."""

    receipt_type: str = "DignityBudgetRefusal"
    domain: str = "governance"
    actor: str = Field(description="Actor requesting the spend")
    role: str = Field(description="Role used for budget lookup")
    action: str = Field(description="Requested action identifier")
    required_cost: float = Field(description="Cost required for the action")
    available_budget: float = Field(description="Budget available when the request was processed")
    recourse: List[str] = Field(default_factory=list, description="Suggested steps to earn additional dignity")


# ============================================================================
# Dignity System V2: Two-Tier Architecture Receipts
# ============================================================================

class DignityScoreReceipt(BaseReceipt):
    """
    Receipt documenting a complete dignity score calculation (Two-Tier).
    
    Tier A (Hard Veto): Non-compensatory checks (PII, Safety, Critical Standards)
    Tier B (Weighted Score): 6-component weighted score [0,1]
    
    Emitted when Guardian computes dignity for an actor/process/part.
    """
    
    receipt_type: str = "DignityScoreReceipt"
    domain: str = "governance"
    
    # Entity reference
    entity_kind: Literal["actor", "process", "part"] = Field(
        description="Entity type being scored"
    )
    entity_id: str = Field(description="Entity identifier (e.g., 'reviewer:alice', 'pbf-lb', 'build:B-001')")
    
    # Tier A: Hard Veto (Non-Compensatory)
    hard_veto_passed: bool = Field(description="True if all hard veto checks passed")
    hard_veto_violations: List[str] = Field(
        default_factory=list,
        description="List of failed checks: pii_ok, safety_ok, critical_standards_ok"
    )
    
    # Tier B: Weighted Score (Compensatory)
    score: float = Field(ge=0.0, le=1.0, description="Aggregate dignity score [0,1]")
    band: Literal["excellent", "good", "fair", "poor"] = Field(
        description="Dignity band: excellent (≥0.85), good (≥0.75), fair (≥0.65), poor (<0.65)"
    )
    
    # Components (sum to score via weights)
    components: Dict[str, float] = Field(
        description=(
            "Component scores [0,1]: "
            "calibration, coverage, rqi, standards, rollback, coherence"
        )
    )
    
    # DWF (Dignity Weight Function) - certainty index
    dwf_recent: float = Field(
        ge=0.0,
        le=1.0,
        description="Recent DWF (certainty index with Jeffreys smoothing + calibration anchoring)"
    )
    
    # Metadata
    window: int = Field(description="Number of recent decisions used in calculation")
    policy_hash: Optional[str] = Field(
        default=None,
        description="Hash of gatebook policy used (dignity.weights + dwf params)"
    )
    
    parent_ids: List[str] = Field(
        default_factory=list,
        description="Upstream receipts: CalibrationReceipt, RefusalQualityReceipt, etc."
    )


class ContradictionStone(BaseReceipt):
    """
    Receipt documenting a behavioral contradiction detected by Guardian OPA policy.
    
    Emitted when contradictions.rego returns DENY, ESCALATE, or WARN.
    Linked to APM authority adjustment via justifying_receipt_ids.
    
    CloudEvents envelope:
    - type: loom.guardian.contradiction
    - source: /guardian/contradictions/{agent_id}
    - subject: agent:{agent_id}
    """
    
    receipt_type: str = "ContradictionStone"
    domain: str = "governance"
    
    # CloudEvents fields auto-populated
    ce_specversion: str = Field(default="1.0")
    ce_type: str = Field(default="loom.guardian.contradiction")
    
    # Entity reference
    agent_id: str = Field(description="Agent identifier whose behavior triggered contradiction")
    
    # OPA decision
    severity: Literal["DENY", "ESCALATE", "WARN"] = Field(
        description="OPA policy decision severity"
    )
    contradiction_type: str = Field(
        description=(
            "Contradiction category: "
            "high_payment_spike_with_clustering, dignity_floor_violation, "
            "rapid_frequency_high_with_degrading_coherence, "
            "multiple_high_severity_contradictions, medium_payment_spike, "
            "medium_rapid_frequency, coherence_degradation, "
            "dignity_incidents_elevated, moderate_payment_increase, "
            "trust_declining, single_dignity_incident_7d, elevated_frequency"
        )
    )
    
    # Reasoning
    reasons: List[str] = Field(
        description="OPA policy reasons array (from deny_reasons/escalate_reasons/warn_reasons)"
    )
    risk_level: Literal["critical", "high", "elevated", "normal"] = Field(
        description="Risk classification from OPA policy"
    )
    
    # Metrics snapshot (audit trail)
    metrics_snapshot: Dict[str, Any] = Field(
        description=(
            "Captured metrics at detection time: "
            "contradictions (payment_spike, rapid_frequency, detected), "
            "dignity (current_score, incidents_7d, incidents_30d, clustering_7d), "
            "coherence (trend, delta_30d), "
            "trust (score_30d, velocity)"
        )
    )
    
    # Traceability
    justifying_receipt_ids: List[str] = Field(
        default_factory=list,
        description=(
            "Upstream receipts that justify contradiction detection: "
            "PaymentReceipt, DignityIncidentReceipt, CoherenceResidualReceipt, etc."
        )
    )
    
    # Policy provenance
    policy_version: str = Field(
        default="v0.5",
        description="OPA contradictions.rego policy version"
    )
    policy_hash: Optional[str] = Field(
        default=None,
        description="SHA-256 hash of contradictions.rego policy file"
    )
    
    # Metadata
    metadata: Dict[str, Any] = Field(
        default_factory=dict,
        description=(
            "Additional context: "
            "escalation_tier (if ESCALATE → COURT), "
            "authority_delta (if linked to AuthorityChangeReceipt), "
            "guardian_session_id, etc."
        )
    )


class DignitySpendReceipt(BaseReceipt):
    """
    Receipt documenting dignity budget spend transaction.
    
    Emitted when an entity spends dignity (e.g., override gate, HIGH action).
    Balance can go negative (debt).
    """
    
    receipt_type: str = "DignitySpendReceipt"
    domain: str = "governance"
    
    # Entity reference
    entity_kind: Literal["actor", "process", "part"] = Field(
        description="Entity type spending budget"
    )
    entity_id: str = Field(description="Entity identifier")
    
    # Transaction
    action: str = Field(
        description="Action description (e.g., 'override:gate_ECE', 'HIGH:mutation')"
    )
    amount: float = Field(gt=0.0, description="Amount spent (positive value)")
    balance_before: float = Field(description="Balance before spend")
    balance_after: float = Field(description="Balance after spend (can be negative)")
    
    # Context
    justification: Optional[str] = Field(
        default=None,
        description="Optional justification for spend (e.g., emergency override reason)"
    )
    
    parent_ids: List[str] = Field(
        default_factory=list,
        description="Upstream receipts: OverrideReceipt, GuardianPreflightReceipt, etc."
    )


class DignityEarnReceipt(BaseReceipt):
    """
    Receipt documenting dignity budget earn transaction.
    
    Emitted when an entity earns dignity (e.g., good calibration, Kintsugi fix applied).
    Balance capped at role-specific maximum.
    """
    
    receipt_type: str = "DignityEarnReceipt"
    domain: str = "governance"
    
    # Entity reference
    entity_kind: Literal["actor", "process", "part"] = Field(
        description="Entity type earning budget"
    )
    entity_id: str = Field(description="Entity identifier")
    
    # Transaction
    action: str = Field(
        description="Action description (e.g., 'kintsugi:fix_applied', 'calibration:ece_improved')"
    )
    amount: float = Field(gt=0.0, description="Amount earned (positive value)")
    balance_before: float = Field(description="Balance before earn")
    balance_after: float = Field(description="Balance after earn (capped at max)")
    max_balance: float = Field(description="Maximum balance for this role")
    
    # Context
    reason: Optional[str] = Field(
        default=None,
        description="Optional reason for earn (e.g., 'ECE improved from 0.04 to 0.02')"
    )
    
    parent_ids: List[str] = Field(
        default_factory=list,
        description="Upstream receipts: KintsugiProposalReceipt, CalibrationReceipt, etc."
    )


class DignityDecayReceipt(BaseReceipt):
    """
    Receipt documenting periodic dignity budget decay batch.
    
    Emitted by nightly job applying half-life decay to all ledgers.
    Positive balances decay back to init (fast: 24h).
    Negative balances recover back to init (slow: 72h).
    """
    
    receipt_type: str = "DignityDecayReceipt"
    domain: str = "governance"
    
    # Batch metadata
    decay_timestamp: str = Field(description="ISO 8601 timestamp when decay was applied")
    entities_decayed: int = Field(ge=0, description="Number of entities processed")
    
    # Decay parameters
    earn_halflife_hours: float = Field(
        default=24.0,
        description="Half-life for positive balances (decay back to init)"
    )
    spend_halflife_hours: float = Field(
        default=72.0,
        description="Half-life for negative balances (recover back to init)"
    )
    
    # Summary statistics
    total_delta: float = Field(
        description="Sum of all balance changes (should trend toward 0)"
    )
    entities_by_kind: Dict[str, int] = Field(
        default_factory=dict,
        description="Entity counts by kind: {actor: N, process: M, part: P}"
    )
    
    parent_ids: List[str] = Field(
        default_factory=list,
        description="Previous DignityDecayReceipt (forms daily chain)"
    )


# ============================================================================
# APM Guardian Spine: Epistemic Snapshot & Proof Tier Receipts (Phase 1A)
# ============================================================================

class EpistemicSnapshotReceipt(BaseReceipt):
    """
    Snapshot of epistemic state for a decision at a point in time.
    
    Every APM payment (and other constitutional decisions) must emit this
    first-class receipt capturing:
    - U-I-M (Understand-Interpret-Mitigate) catastrophic inheritance tracking
    - Semantic provenance (refactor IDs, contract/policy hashes)
    - Coherence metrics (ΔC, Ωc, LB95)
    - Decision metrics at snapshot time
    
    Forms the canonical input to Guardian/ProofTier evaluation.
    See: governance/schemas/epistemic_snapshot_v2.schema.json
    """
    
    receipt_type: str = "EpistemicSnapshotReceipt"
    domain: str = Field(default="apm", description="Domain where decision occurs")
    
    decision_id: str = Field(
        description="Decision receipt ID this snapshot describes"
    )
    
    # U-I-M block: catastrophic inheritance tracking
    uim: Dict[str, Any] = Field(
        description=(
            "Understand-Interpret-Mitigate block tracking catastrophic inheritance. "
            "Keys: upstream_family, training_corpus_tags, fine_tune_recipe_ids, "
            "inheritance_failure (bool), inherited_risk_flags, phase, links, "
            "mitigation_deadline, mitigation_receipt_id"
        )
    )
    
    # Semantic provenance
    semantics: Dict[str, Any] = Field(
        description=(
            "Semantic provenance for this decision. Keys: semantics_refactor_ids, "
            "governance_contract_hash, policy_bundle_hash, mythos_concepts_used"
        )
    )
    
    # Coherence metrics
    coherence: Dict[str, Any] = Field(
        default_factory=dict,
        description=(
            "Coherence block: ΔC (delta_c), Ωc (omega_c), estimators, LB95, "
            "raters, evidence receipts"
        )
    )
    
    # Decision-time metrics
    metrics: Dict[str, Any] = Field(
        default_factory=dict,
        description=(
            "Additional decision metrics at snapshot time: trust_7d, "
            "dignity_incidents_7d, payment_spike_ratio, payment_count_24h, etc."
        )
    )
    
    # Dignity Dashboard context (Nine Masters Week 1-2 priority)
    dignity_context: Dict[str, Any] = Field(
        default_factory=lambda: {
            "current_score": 0.0,
            "floor": 0.15,
            "distance_to_refusal": 0.0,
            "historical_7d": [],
            "closest_call_30d": 0.0,
            "breach_consequence": "",
        },
        description=(
            "Dignity Dashboard data per Nine Masters synthesis. "
            "Four views: (1) current score with floor bar, "
            "(2) historical trend (7 days), (3) breach consequence simulator, "
            "(4) distance to refusal (live meter showing closest call in 30 days)"
        )
    )
    
    # Semantic metadata (SEMANTICS_RUNBOOK structure/function/meaning)
    semantics_meta: Dict[str, Any] = Field(
        default_factory=lambda: {
            "structural_type": "receipt",
            "functional_role": "epistemic_snapshot",
            "meaning_tags": ["epistemic_state", "uim", "semantics", "coherence"],
            "scope": "tenant",
            "authority": "canonical",
            "risk_level": "high",
        },
        description="Semantic metadata per SEMANTICS_RUNBOOK (structure/function/meaning)"
    )
    
    parent_ids: List[str] = Field(
        default_factory=list,
        description="Upstream receipts: CalibrationReceipt, TrustReport, etc."
    )


class ProofTierReceipt(BaseReceipt):
    """
    Guardian proof tier decision for a single epistemic snapshot.
    
    Every APM payment (and other constitutional decisions) must emit this
    after EpistemicSnapshotReceipt, capturing Guardian's gate evaluation:
    - Tier selection (core/reg/court)
    - Route decision (proceed/hold/court/red_team_review)
    - Stakes/signals evaluated
    - Gate outputs (dignity floor, contradictions, etc.)
    - Escalation flags (DENY/ESCALATE reasons)
    
    Forms the canonical input to domain-specific routing (ALLOW/ESCALATE/DENY).
    See: governance/schemas/proof_tier_v1.schema.json
    """
    
    receipt_type: str = "ProofTierReceipt"
    domain: str = Field(default="governance", description="Governance domain")
    
    decision_id: str = Field(description="Decision receipt ID")
    snapshot_id: str = Field(
        description="EpistemicSnapshotReceipt ID this proof tier evaluated"
    )
    
    # Guardian decision
    tier: str = Field(
        description="Proof tier selected: core, reg, court"
    )
    route: str = Field(
        description="Route decision: proceed, hold, court, red_team_review"
    )
    
    # Stakes & signals
    stakes: Dict[str, Any] = Field(
        default_factory=dict,
        description="Stakes evaluated by Guardian (amount, risk, sensitivity, etc.)"
    )
    signals: Dict[str, Any] = Field(
        default_factory=dict,
        description="Signals evaluated by Guardian (trust, coherence, drift, etc.)"
    )
    
    # Gate outputs
    gates: Dict[str, Any] = Field(
        default_factory=dict,
        description="Gate evaluation results (dignity floor, contradictions, etc.)"
    )
    contradictions: Dict[str, Any] = Field(
        default_factory=dict,
        description=(
            "Contradictions detected by Guardian: deny (bool), escalate (bool), "
            "deny_reasons, escalate_reasons, warn_reasons, summary"
        )
    )
    
    # Escalation details
    escalation: Dict[str, Any] = Field(
        default_factory=dict,
        description=(
            "Escalation metadata: decision (ALLOW/ESCALATE/DENY), "
            "deny (bool), escalate (bool)"
        )
    )
    
    # Semantic metadata (SEMANTICS_RUNBOOK structure/function/meaning)
    semantics: Dict[str, Any] = Field(
        default_factory=lambda: {
            "structural_type": "receipt",
            "functional_role": "governance_gate",
            "meaning_tags": ["proof_tier", "stakes", "guardian_decision"],
            "scope": "global",
            "authority": "canonical",
            "risk_level": "court",
        },
        description="Semantic metadata per SEMANTICS_RUNBOOK (structure/function/meaning)"
    )
    
    parent_ids: List[str] = Field(
        default_factory=list,
        description="Upstream receipts: EpistemicSnapshotReceipt (snapshot_id)"
    )


class CalibrationAdjustmentReceipt(BaseReceipt):
    """
    Records threshold adjustments to calibration state.

    Emitted when:
    - ECE or coverage thresholds are adjusted
    - Guardian tightens/loosens gating based on calibration health
    - Manual threshold override by operator

    Enables audit trail for calibration-driven governance changes.
    """
    receipt_type: str = "CalibrationAdjustmentReceipt"
    domain: str = Field(default="calibration", description="Calibration domain")

    adjustment_id: str = Field(description="Unique adjustment identifier")
    reason: str = Field(
        description="Why thresholds were adjusted (auto/manual/degradation)"
    )

    # Old thresholds
    old_ece_max: float = Field(description="Previous ECE threshold")
    old_coverage_min: float = Field(description="Previous coverage threshold")

    # New thresholds
    new_ece_max: float = Field(description="New ECE threshold")
    new_coverage_min: float = Field(description="New coverage threshold")

    # Current metrics at time of adjustment
    current_ece: float = Field(description="Current ECE value")
    current_coverage: float = Field(description="Current coverage value")
    n_samples: int = Field(description="Number of samples in rolling window")

    # Semantic metadata
    semantics: Dict[str, Any] = Field(
        default_factory=lambda: {
            "structural_type": "receipt",
            "functional_role": "calibration_adjustment",
            "meaning_tags": ["calibration", "threshold", "governance"],
            "scope": "global",
            "authority": "canonical",
            "risk_level": "reg",
        },
        description="Semantic metadata per SEMANTICS_RUNBOOK"
    )

    parent_ids: List[str] = Field(
        default_factory=list,
        description="Upstream receipts that triggered adjustment"
    )


class CalibrationStateReceipt(BaseReceipt):
    """
    Captures calibration state snapshot with semantic metrics.

    Emitted when:
    - ExecutionSpine Stage 1.5 (CalibrationState gate) evaluates health
    - Semantic metrics from cognitive subsystems are available
    - Used for learning and fitness tracking

    Provides:
    - ECE/coverage routing bias metrics
    - Semantic temperature (τ), coherence (ΔC), curvature (χ) from Triad
    - Drive levels (hunger, exploration, safety) from Telos
    - Mood state (calm/stressed) from MoodEngine
    - Council coherence score from multi-agent deliberation
    - Dignity score from governance system
    """
    receipt_type: str = "CalibrationStateReceipt"
    domain: str = Field(default="calibration", description="Calibration domain")

    # Core calibration metrics (routing bias)
    ece: float = Field(description="Expected Calibration Error (0-1)")
    coverage: float = Field(description="Prediction coverage (0-1)")
    n_samples: int = Field(description="Number of samples in rolling window")
    should_veto: bool = Field(description="Whether calibration should block execution")
    should_demand_review: bool = Field(description="Whether calibration demands review")
    reason: str = Field(description="Health status reason")

    # Semantic metrics from Triad (Neural Triad metamorphic compression)
    delta_c: Optional[float] = Field(
        default=None,
        description="Coherence delta (ΔC) from Triad - structural coherence metric (0-1)"
    )
    tau: Optional[float] = Field(
        default=None,
        description="Semantic temperature (τ) from Triad - entropy/exploration level (0-1)"
    )
    chi: Optional[float] = Field(
        default=None,
        description="Curvature (χ) from Garden - structural depth metric (0-1)"
    )

    # Drive levels from TelosEngine (organism motivation)
    drive_levels: Optional[Dict[str, float]] = Field(
        default=None,
        description="Drive intensities: hunger, exploration, safety, curiosity, etc. (0-1 each)"
    )

    # Mood state from MoodEngine (affective layer)
    mood_state: Optional[str] = Field(
        default=None,
        description="Current mood: calm, focused, curious, anxious, exhausted"
    )
    mood_score: Optional[float] = Field(
        default=None,
        description="Mood intensity score (0-1)"
    )

    # Council coherence from multi-agent deliberation
    council_coherence: Optional[float] = Field(
        default=None,
        description="Council consensus/coherence score from quintet deliberation (0-1)"
    )

    # Dignity score from governance
    dignity_score: Optional[float] = Field(
        default=None,
        description="Dignity score from governance system (0-1)"
    )

    # Temporal context
    epoch: Optional[int] = Field(
        default=None,
        description="Organism epoch/lifecycle phase number"
    )
    epoch_label: Optional[str] = Field(
        default=None,
        description="Epoch label: genesis, growth, stability, etc."
    )

    # Semantic metadata
    semantics: Dict[str, Any] = Field(
        default_factory=lambda: {
            "structural_type": "receipt",
            "functional_role": "calibration_state_snapshot",
            "meaning_tags": ["calibration", "semantic_metrics", "health"],
            "scope": "execution_spine",
            "authority": "canonical",
            "risk_level": "low",
        },
        description="Semantic metadata per SEMANTICS_RUNBOOK"
    )

    parent_ids: List[str] = Field(
        default_factory=list,
        description="Upstream receipts (GuardianReceipt, etc.)"
    )


class ChangeIntentReceipt(BaseReceipt):
    """
    Human or agent intent to modify architecture/code.
    
    Emitted by Maestro AFTER Guardian preflight approval, BEFORE applying change.
    Links FIR (Feature Intent Register) → code changes → UoR updates.
    
    Atlas double helix integration:
    - This receipt captures INTENT (why change?)
    - UoRStateReceipt captures EFFECT (what changed?)
    - FeatureDeltaReceipt captures SIGNIFICANCE (how important?)
    """
    receipt_type: str = "ChangeIntentReceipt"
    author: str = Field(description="Human or agent making the change (email or agent_id)")
    subject: str = Field(description="Brief summary of intended change (1 sentence)")
    
    links: Dict[str, Optional[str]] = Field(
        default_factory=dict,
        description=(
            "Linkage to related artifacts:\n"
            "  - 'fir': Feature Intent Register ID (if exists)\n"
            "  - 'code': File path or commit hash of code change\n"
            "  - 'issue': GitHub issue or ticket ID\n"
            "  - 'pr': Pull request number\n"
        )
    )
    
    rationale: str = Field(description="Multi-line explanation of WHY this change is needed")
    
    nexus_deltas: List[str] = Field(
        default_factory=list,
        description="List of FeatureCard IDs expected to change (e.g., ['feature.guardian_preflight'])"
    )
    
    guardian_approval: bool = Field(
        default=False,
        description="True if Guardian preflight passed, False if overridden"
    )
    
    override_reason: Optional[str] = Field(
        default=None,
        description="Required if guardian_approval=False (emergency justification)"
    )
    
    parent_ids: List[str] = Field(
        default_factory=list,
        description="Upstream receipts: ContextPackReceipt, GuardianPreflightReceipt, etc."
    )
    
    metadata: Dict[str, Any] = Field(
        default_factory=dict,
        description="Additional context: maestro_run_id, estimated_risk_level, etc."
    )


class CoherenceResidualReceipt(BaseReceipt):
    receipt_type: str = "CoherenceResidualReceipt"
    domain: str = Field(description="Decision domain for the EVHD evaluation")
    rho: Optional[float] = Field(default=None, description="Spearman correlation coefficient")
    rho_ci: Optional[Tuple[float, float]] = Field(default=None, description="95% confidence interval for rho")
    p_value: Optional[float] = Field(default=None, description="Spearman p-value")
    support: int = Field(ge=0, description="Number of samples evaluated")
    min_support: int = Field(ge=0, description="Minimum required support")
    threshold: float = Field(description="Gatebook rho threshold")
    status: Literal["PASS", "REFUSE"] = Field(description="Gate enforcement result")
    reasons: List[str] = Field(default_factory=list, description="Reasons/annotations for the verdict")
    metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional diagnostics (bin counts, etc.)")
    parent_ids: List[str] = Field(default_factory=list, description="Upstream receipt identifiers")

class UncertaintyNeighbor(BaseModel):
    """Nearest neighbour reference surfaced in an uncertainty lineage."""

    model_config = ConfigDictLike(frozen=True)

    receipt_id: str = Field(description="Source receipt identifier for the neighbour case.")
    similarity: float = Field(
        ge=0.0,
        le=1.0,
        description="Cosine similarity between the query refusal embedding and the neighbour.",
    )
    decision_type: Literal["prophecy", "refusal"] = Field(
        description="Decision disposition for the neighbour receipt.",
    )
    domain: Optional[str] = Field(
        default=None,
        description="Domain associated with the neighbour receipt.",
    )
    asset_id: Optional[str] = Field(
        default=None,
        description="Asset identifier (if recorded) for the neighbour example.",
    )
    reasons: List[str] = Field(
        default_factory=list,
        description="Reason strings captured on the neighbour receipt.",
    )
    ask_list: List[str] = Field(
        default_factory=list,
        description="Actionable asks extracted from the neighbour (e.g., recourse messages).",
    )


class UncertaintyLineage(BaseModel):
    """Structured lineage explaining uncertainty drivers for a refusal."""

    model_config = ConfigDictLike(frozen=True)

    embedding_version: str = Field(
        description="Version identifier for the embedding store powering lineage lookups.",
    )
    reason_class: Literal["label_noise", "shift", "ambiguity", "confident"] = Field(
        description="Top-level explanation for the uncertainty driver.",
    )
    disagreement_rate: float = Field(
        ge=0.0,
        le=1.0,
        description="Proportion of nearest neighbours whose disposition disagrees with the current refusal.",
    )
    neighbors: List[UncertaintyNeighbor] = Field(
        default_factory=list,
        description="Nearest neighbour examples supporting the uncertainty classification.",
    )
    ask_list: List[str] = Field(
        default_factory=list,
        description="Condensed list of operator asks synthesised from neighbours.",
    )
    evidence: Dict[str, Any] = Field(
        default_factory=dict,
        description="Auxiliary evidence (e.g., metrics, similarity thresholds) supporting the lineage.",
    )


class EnsembleComponent(BaseModel):
    """Component model contribution used by an ensemble."""

    model_id: str = Field(description="Identifier for the component model")
    prediction: float = Field(description="Component probability or score contributed to the ensemble")
    weight: Optional[float] = Field(
        default=None,
        description="Optional weight applied during weighted averaging"
    )
    provenance: Dict[str, Any] = Field(
        default_factory=dict,
        description="Model version metadata (e.g., git SHA, training window)",
    )
    metadata: Dict[str, Any] = Field(
        default_factory=dict,
        description="Component-specific diagnostics captured during blending",
    )


class EnsembleReceipt(BaseReceipt):
    """Receipt documenting ensemble promotion decisions and policy gating."""

    receipt_type: str = "EnsembleReceipt"
    asset_id: str
    ensemble_method: Literal["weighted_average", "stacking"]
    components: List[EnsembleComponent] = Field(default_factory=list)
    ensemble_prediction: float = Field(description="Final blended probability emitted by the ensemble")
    baseline_model: Optional[str] = Field(
        default=None,
        description="Identifier for the incumbent baseline model"
    )
    baseline_prediction: Optional[float] = Field(
        default=None,
        description="Baseline model probability for the evaluated sample",
    )
    ensemble_metric: Optional[float] = Field(
        default=None,
        description="Primary evaluation metric for the ensemble (direction determined by policy)",
    )
    baseline_metric: Optional[float] = Field(
        default=None,
        description="Baseline evaluation metric used for challenger comparison",
    )
    metric_direction: Literal["higher", "lower"] = Field(
        default="higher",
        description="Indicates whether higher or lower metric values are preferred",
    )
    metric_delta: Optional[float] = Field(
        default=None,
        description="Directional delta computed according to metric_direction",
    )
    min_support: Optional[int] = Field(
        default=None,
        description="Minimum holdout support required by the policy gate",
    )
    support: Optional[int] = Field(
        default=None,
        description="Observed holdout support for the evaluation window",
    )
    policy_passed: Optional[bool] = Field(
        default=None,
        description="Outcome of the ensemble challenger policy gate",
    )
    stacking_coefficients: Optional[Dict[str, float]] = Field(
        default=None,
        description="Coefficient mapping (including optional intercept under key '__intercept__') used for stacking",
    )
    metrics: Dict[str, Any] = Field(
        default_factory=dict,
        description="Additional evaluation metrics captured during blending",
    )
    gate_status: Optional[GateStatus] = None
    run_context: Dict[str, Any] = Field(default_factory=dict)


class CohortExplanationReceipt(BaseReceipt):
    """Privacy-preserving cohort explanation linked to a decision receipt."""

    receipt_type: str = "CohortExplanationReceipt"
    decision_receipt_id: str = Field(
        ...,
        description="Receipt identifier for the decision this explanation accompanies.",
    )
    decision_type: Literal["ProphecyStone", "RefusalStone"] = Field(
        ...,
        description="Decision receipt type the explanation references.",
    )
    cohort_key: str = Field(
        ...,
        description="Cohort identifier (normalized, k-anonymised).",
    )
    cohort_label: Optional[str] = Field(
        default=None,
        description="Human-readable cohort label when available.",
    )
    cohort_size: int = Field(
        ...,
        ge=0,
        description="Number of historical decisions in the cohort window.",
    )
    k_anonymity: int = Field(
        ...,
        ge=0,
        description="k-anonymity achieved for the cohort (support within window).",
    )
    approval_rate: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Observed approval rate for the cohort within the window.",
    )
    refusal_rate: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Observed refusal rate for the cohort within the window.",
    )
    metrics: Dict[str, Optional[float]] = Field(
        default_factory=dict,
        description="Derived cohort metrics (slice ECE, Brier, coverage, etc.).",
    )
    lookback_days: int = Field(
        default=30,
        ge=1,
        description="Historical window (days) used for the cohort summary.",
    )
    window_start: datetime = Field(
        ...,
        description="Start timestamp (UTC) for the aggregation window.",
    )
    window_end: datetime = Field(
        ...,
        description="End timestamp (UTC) for the aggregation window.",
    )
    summary: str = Field(
        ...,
        description="Readable summary describing the cohort insights.",
    )
    context: Dict[str, Any] = Field(
        default_factory=dict,
        description="Supplementary context (probability deltas, saved EV, etc.).",
    )
    current_probability: Optional[float] = Field(
        default=None,
        description="Current decision probability for comparison against the cohort.",
    )
    cohort_median_probability: Optional[float] = Field(
        default=None,
        description="Median historical probability for the cohort within the window.",
    )
    policy_witness: Optional[PolicyWitness] = Field(
        default=None,
        description="Policy witness snapshot proving gatebook compliance for the explanation.",
    )


class MaestroRunReceipt(BaseReceipt):
    """Receipt emitted for each Maestro orchestration run."""

    receipt_type: str = "MaestroRunReceipt"
    domain: str = Field(
        default="agent_system",
        description="Pipeline domain emitting the run (e.g., mlb_engine).",
    )
    agent_ns: str = Field(
        description="Agent namespace executing the orchestration run.",
    )
    task_id: str = Field(
        description="Root task identifier orchestrated by Maestro.",
    )
    success: bool = Field(
        description="True when orchestration completed without refusal.",
    )
    output: Optional[str] = Field(
        default=None,
        description="Aggregated output generated by the orchestrator when success is True.",
    )
    agent_results: List[AgentResult] = Field(
        default_factory=list,
        description="Ordered list of specialist agent results contributing to the run.",
    )
    memory_status: Optional[str] = Field(
        default=None,
        description="Memory status for the root task (used, bypassed, blocked, or skipped).",
    )
    memory_selection_hash: Optional[str] = Field(
        default=None,
        description="SHA-256 hash of the deterministic memory selection presented to Maestro.",
    )
    memory_hits: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Structured summary of memory records injected for the root task.",
    )
    memory_latency_ms: Optional[int] = Field(
        default=None,
        description="Observed recall latency in milliseconds for the root memory lookup.",
    )
    memory_budget_ms: Optional[int] = Field(
        default=None,
        description="Latency budget in milliseconds enforced for the root memory lookup.",
    )
    refusal_reason: Optional[str] = Field(
        default=None,
        description="Refusal descriptor when orchestration fails or is vetoed.",
    )
    error: Optional[str] = Field(
        default=None,
        description="Error narrative captured when orchestration fails unexpectedly.",
    )
    execution_time_ms: int = Field(
        default=0,
        description="Total orchestration time in milliseconds.",
    )

    @field_validator("memory_hits", mode="before")
    @classmethod
    def _coerce_memory_hits(cls, value: Any) -> List[Dict[str, Any]]:
        if value is None:
            return []
        if isinstance(value, (str, bytes)):
            return [{"record_id": str(value)}]
        if isinstance(value, Mapping):
            return [dict(value)]
        if isinstance(value, Sequence):
            result: List[Dict[str, Any]] = []
            for item in value:
                if isinstance(item, Mapping):
                    result.append({key: val for key, val in item.items()})
                elif isinstance(item, (str, bytes)):
                    result.append({"record_id": str(item)})
            return result
        return []


# Backwards compatibility alias for legacy imports.
OrchestrationResult = MaestroRunReceipt


class PulseWindow(BaseModel):
    size: str = Field(description="Size label for the aggregation window (e.g., '1h').")
    ended_at: datetime = Field(description="UTC timestamp marking the end of the window.")


class PulseCoverageStat(BaseModel):
    cohort_id: str = Field(description="Cohort identifier within the monitored domain.")
    observed: int = Field(description="Count of evaluated decisions within the window.")
    required: int = Field(description="Minimum required sample count for health guarantees.")
    ratio: float = Field(description="Observed / required coverage ratio.")


class PulseQualityStat(BaseModel):
    cohort_id: str = Field(description="Cohort identifier associated with the quality metrics.")
    ece: Optional[float] = Field(
        default=None,
        description="Empirical calibration error (ECE) observed within the window.",
    )
    brier: Optional[float] = Field(
        default=None,
        description="Average Brier score when outcome labels are present.",
    )
    delta_ece: Optional[float] = Field(
        default=None,
        description="Change in ECE relative to the prior completed window.",
    )


class PulseDriftMetric(BaseModel):
    metric: str = Field(description="Name of the distributional metric (e.g., PSI).")
    cohort_id: Optional[str] = Field(
        default=None,
        description="Cohort identifier when drift metric is cohort-specific.",
    )
    value: Optional[float] = Field(
        default=None,
        description="Observed metric value within the window.",
    )
    baseline: Optional[float] = Field(
        default=None,
        description="Historical baseline against which the window is compared.",
    )
    delta: Optional[float] = Field(
        default=None,
        description="Difference between the observed value and baseline.",
    )


class PulseCoherenceStat(BaseModel):
    cohort_id: str = Field(description="Cohort identifier associated with coherence metrics.")
    mean_abs_delta: Optional[float] = Field(
        default=None,
        description="Mean absolute difference between multi-link and direct predictions.",
    )
    breach_rate: Optional[float] = Field(
        default=None,
        description="Share of samples breaching the configured coherence tolerance.",
    )


class PulseMemorySummary(BaseModel):
    bypass_rate: Optional[float] = Field(
        default=None,
        description="Proportion of Maestro runs within the window where memory recall was bypassed.",
    )
    guard_blocks: int = Field(
        default=0,
        description="Count of memory recalls blocked by guardrails within the window.",
    )
    latency_p50_ms: Optional[float] = Field(
        default=None,
        description="Median memory recall latency in milliseconds.",
    )
    latency_p95_ms: Optional[float] = Field(
        default=None,
        description="95th percentile memory recall latency in milliseconds.",
    )
    sample_size: int = Field(
        default=0,
        description="Number of Maestro runs contributing latency samples.",
    )
    age_histogram: Dict[str, int] = Field(
        default_factory=dict,
        description="Bucketed counts of memory hit ages (seconds).",
    )


class PulseAnomalyClassification(str, Enum):
    COVERAGE_THIN = "coverage_thin"
    ECE_REGRESSION = "ece_regression"
    DISTRIBUTION_DRIFT = "distribution_drift"
    COHERENCE_BREACH = "coherence_breach"
    MEMORY_BYPASS = "memory_bypass"


class PulseSeverity(str, Enum):
    S1 = "S1"
    S2 = "S2"
    S3 = "S3"


class PulseTarget(BaseModel):
    kind: str = Field(description="Entity type related to the anomaly (e.g., cohort, persona).")
    id: str = Field(description="Identifier for the entity the anomaly references.")


class PulseSnapshot(BaseReceipt):
    """Aggregate trust radar snapshot synthesised from recent Maestro runs."""

    model_config = ConfigDictLike(
        json_schema_extra={
            "examples": [
                {
                    "receipt_id": "pulse_snapshot_2025-10-30T17:00:00Z",
                    "domain": "mlb_engine",
                    "window": {"size": "1h", "ended_at": "2025-10-30T17:00:00Z"},
                    "coverage": [
                        {"cohort_id": "mlb_2025_road_favorites", "observed": 420, "required": 500, "ratio": 0.84}
                    ],
                    "quality": [
                        {
                            "cohort_id": "mlb_2025_road_favorites",
                            "ece": 0.028,
                            "brier": 0.158,
                            "delta_ece": 0.006,
                        }
                    ],
                    "coherence": [
                        {"cohort_id": "mlb_2025_road_favorites", "mean_abs_delta": 0.012, "breach_rate": 0.11}
                    ],
                    "policy_pack": "gatebook@2.3",
                    "hash": "sha256:example",
                    "sig": "ed25519:example",
                    "memory": {
                        "bypass_rate": 0.25,
                        "guard_blocks": 2,
                        "latency_p50_ms": 12.0,
                        "latency_p95_ms": 44.0,
                        "sample_size": 128,
                        "age_histogram": {
                            "<=5m": 32,
                            "5m-15m": 16,
                            "15m-1h": 48,
                            ">24h": 8,
                        },
                    },
                }
            ]
        }
    )

    receipt_type: str = "PulseSnapshot"
    window: PulseWindow
    coverage: List[PulseCoverageStat] = Field(default_factory=list)
    quality: List[PulseQualityStat] = Field(default_factory=list)
    drift: List[PulseDriftMetric] = Field(default_factory=list)
    coherence: List[PulseCoherenceStat] = Field(default_factory=list)
    hotspots: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Optional list of focused investigations or follow-ups.",
    )
    memory: Optional[PulseMemorySummary] = Field(
        default=None,
        description="Aggregated memory recall telemetry for the snapshot window.",
    )
    policy_pack: Optional[str] = Field(
        default=None,
        description="Gatebook bundle or policy pack version associated with the snapshot.",
    )
    metrics: Dict[str, Any] = Field(
        default_factory=dict,
        description="Windowed metric values keyed by metric identifier (e.g., refusal_rate_7d).",
    )
    baselines: Dict[str, Any] = Field(
        default_factory=dict,
        description="Longer window baselines used for drift comparison keyed by metric identifier.",
    )
    thresholds: Dict[str, Any] = Field(
        default_factory=dict,
        description="Gatebook thresholds applied during drift evaluation for transparency.",
    )
    risk_score: Optional[float] = Field(
        default=None,
        description="Computed constitutional risk score (0-1) for the evaluated window.",
    )
    violations: List[str] = Field(
        default_factory=list,
        description="Metric identifiers that exceeded configured thresholds.",
    )
    freeze_recommended: bool = Field(
        default=False,
        description="True when the snapshot triggered a policy freeze proposal.",
    )
    hash: Optional[str] = Field(
        default=None,
        description="Deterministic digest of the snapshot payload for quick comparisons.",
    )
    sig: Optional[str] = Field(
        default=None,
        description="Detached signature referencing the canonical snapshot payload.",
    )
    anchor: Optional[ReceiptAnchor] = Field(
        default=None,
        description="Sigstore Rekor anchoring metadata for the pulse snapshot.",
    )

    @classmethod
    def validate(cls, data: Mapping[str, Any]) -> "PulseSnapshot":
        if isinstance(data, cls):
            return data
        return cls.model_validate(data)


class PulseAnomaly(BaseReceipt):
    """Anomaly receipt describing actionable deviations found in a Pulse snapshot."""

    model_config = ConfigDictLike(
        json_schema_extra={
            "examples": [
                {
                    "receipt_id": "pulse_anomaly_pa_2025-10-30T16:12:34Z_0021",
                    "domain": "mlb_engine",
                    "anomaly_id": "PA_2025-10-30T16:12:34Z_0021",
                    "classification": "coverage_thin",
                    "target": {"kind": "cohort", "id": "mlb_2025_road_favorites"},
                    "evidence": {"window": "1h", "observed": 420, "required": 500, "ratio": 0.84},
                    "severity": "S2",
                    "suggested_actions": [
                        "Backfill tests for cohort mlb_2025_road_favorites",
                        "Route 20% traffic to alternate prompt_template_09 as canary",
                    ],
                    "owner": "ops@loom",
                }
            ]
        }
    )

    receipt_type: str = "PulseAnomaly"
    anomaly_id: str = Field(description="Stable identifier for the anomaly instance.")
    classification: PulseAnomalyClassification = Field(
        description="Type of anomaly detected based on rule thresholds."
    )
    target: PulseTarget = Field(description="Entity impacted by the anomaly detection.")
    evidence: Dict[str, Any] = Field(
        default_factory=dict,
        description="Structured evidence supporting the anomaly classification.",
    )
    severity: PulseSeverity = Field(description="Operational severity classification for the anomaly.")
    suggested_actions: List[str] = Field(
        default_factory=list,
        description="Human readable list of recommended actions to address the anomaly.",
    )
    owner: Optional[str] = Field(
        default=None,
        description="Team or operator currently responsible for triaging the anomaly.",
    )
    related_receipts: List[str] = Field(
        default_factory=list,
        description="Optional references to receipts that influenced the anomaly classification.",
    )

    @classmethod
    def validate(cls, data: Mapping[str, Any]) -> "PulseAnomaly":
        if isinstance(data, cls):
            return data
        return cls.model_validate(data)


class TemporalConsistencyReceipt(BaseReceipt):
    """Receipt capturing temporal stability checks for subject probability trajectories."""

    receipt_type: str = "TemporalConsistencyReceipt"
    subject_key: str = Field(description="Canonical subject identifier evaluated for temporal stability.")
    lookback_days: int = Field(default=30, description="Number of days considered when computing historical median.")
    recent_median_p: Optional[float] = Field(
        default=None,
        description="Median predicted probability across the lookback window (null when insufficient history).",
    )
    current_p: float = Field(description="Latest predicted probability under evaluation.")
    shift: Optional[float] = Field(
        default=None,
        description="Absolute delta between the current probability and the historical median.",
    )
    max_allowed_shift: Optional[float] = Field(
        default=None,
        description="Maximum permitted shift derived from policy configuration.",
    )
    documented_event: bool = Field(
        default=False,
        description="True when an operator-documented event explains the observed shift.",
    )
    sample_size: int = Field(default=0, description="Number of historical observations within the lookback window.")
    history_window_start: Optional[datetime] = Field(
        default=None,
        description="Oldest observation timestamp considered when computing the median.",
    )
    history_window_end: Optional[datetime] = Field(
        default=None,
        description="Most recent historical observation timestamp evaluated before the current prediction.",
    )
    verdict: Literal["ok", "violation", "event"] = Field(
        default="ok",
        description="Outcome of the temporal gate evaluation (ok | violation | event).",
    )
    max_shift_violation: Optional[float] = Field(
        default=None,
        description="Amount by which the observed shift exceeded the allowed threshold when applicable.",
    )
    policy_bundle_hash: Optional[str] = Field(
        default=None,
        description="SHA-256 digest for the policy bundle active during evaluation.",
    )


class MonotonicityReceipt(BaseReceipt):
    """Receipt documenting monotonicity guard violations and supporting context."""

    receipt_type: str = "MonotonicityReceipt"
    domain: str = Field(description="Operational domain evaluated for the guard.")
    feature: str = Field(description="Identifier for the monotonicity feature/check that failed.")
    label: Optional[str] = Field(default=None, description="Human-friendly label describing the check.")
    comparator: Optional[str] = Field(
        default=None,
        description="Comparator used when evaluating the guard (e.g., '>=', '<=').",
    )
    observed_value: Optional[float] = Field(
        default=None,
        description="Observed metric value at evaluation time.",
    )
    threshold: Optional[float] = Field(
        default=None,
        description="Configured threshold applied to the check.",
    )
    tolerance: Optional[float] = Field(
        default=None,
        description="Tolerance applied above/below the threshold when evaluating the guard.",
    )
    sample_size: Optional[int] = Field(
        default=None,
        description="Observed sample size supporting the guard evaluation.",
    )
    min_sample: Optional[int] = Field(
        default=None,
        description="Minimum sample enforceable under policy configuration.",
    )
    reason: Optional[str] = Field(
        default=None,
        description="Structured refusal reason captured for the guard failure.",
    )
    context: Dict[str, Any] = Field(
        default_factory=dict,
        description="Additional context captured alongside the guard (e.g., guard payloads, cohort identifiers).",
    )
    policy_bundle_hash: str = Field(default=OPA_POLICY_BUNDLE_SHA256)
    policy_version: str = Field(default=OPA_POLICY_VERSION)
    gatebook_hash: Optional[str] = None
    policy_bundle_receipt_id: Optional[str] = None
    gatebook_receipt_id: Optional[str] = None
    policy_witness: Optional[PolicyWitness] = None
    related_ids: Dict[str, str] = Field(default_factory=dict)
    parent_id: Optional[str] = None
    mode: Optional[str] = None
    ts: Optional[datetime] = None
    allow_orphan: bool = False


class PrivacyBudgetReceipt(BaseReceipt):
    """Aggregated privacy envelope and redaction posture for a given window."""

    receipt_type: str = "PrivacyBudgetReceipt"
    window_start: datetime = Field(description="Start timestamp (UTC) for the aggregation window")
    window_end: datetime = Field(description="End timestamp (UTC) for the aggregation window")
    envelope_counts: Dict[str, int] = Field(
        default_factory=dict,
        description="Counts of privacy envelopes emitted, keyed by label",
    )
    decrypt_requests: int = Field(default=0, ge=0, description="Number of decrypt requests served during the window")
    pii_events: int = Field(default=0, ge=0, description="Detected PII events during dataset scans")
    redaction_rate: float = Field(
        default=1.0,
        ge=0.0,
        le=1.0,
        description="Ratio of redacted values relative to total sensitive values encountered",
    )
    total_records: int = Field(default=0, ge=0, description="Total records scanned across monitored datasets")
    redacted_values: int = Field(default=0, ge=0, description="Count of sanitized values encountered during scans")
    dataset_roots: List[str] = Field(
        default_factory=list,
        description="Filesystem roots that were scanned for the privacy budget assessment",
    )
    notes: List[str] = Field(default_factory=list, description="Operator annotations or remediation guidance")
    policy_witness: Optional[PolicyWitness] = Field(
        default=None,
        description="Policy witness snapshot that proves gatebook compliance during the scan",
    )


class AdversarialAttackSummary(BaseModel):
    """Summary statistics for a single adversarial attack family."""

    attack_type: str = Field(..., description="Identifier for the adversarial attack (feature_flip, boundary, etc.).")
    attempts: int = Field(..., ge=0, description="Number of cases generated for this attack.")
    refused: int = Field(..., ge=0, description="Count of cases refused by the gate pipeline.")
    allowed: int = Field(..., ge=0, description="Count of cases that were allowed/passed.")
    errors: int = Field(
        default=0,
        ge=0,
        description="Number of executions that errored or could not be evaluated.",
    )
    refusal_rate: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Refusal coverage for the attack (refused / attempts).",
    )
    mean_latency_ms: Optional[float] = Field(
        default=None,
        ge=0.0,
        description="Average vet_action latency for this attack type when recorded.",
    )


class AdversarialAuditReceipt(BaseReceipt):
    """Receipt documenting a red-team style adversarial audit across gate attacks."""

    receipt_type: str = "AdversarialAuditReceipt"
    audit_id: str = Field(..., description="Human-readable identifier for the audit run.")
    domain_under_test: str = Field(..., description="Domain evaluated by the adversarial audit.")
    total_cases: int = Field(..., ge=0, description="Total adversarial cases executed across all attacks.")
    threshold: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Minimum refusal coverage required to consider the audit a pass.",
    )
    refusal_coverage: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Overall refusal rate across all attacks.",
    )
    passed: bool = Field(..., description="True when refusal_coverage meets or exceeds threshold.")
    attack_summaries: List[AdversarialAttackSummary] = Field(
        default_factory=list,
        description="Per-attack summary metrics captured during the audit.",
    )
    metadata: Dict[str, Any] = Field(
        default_factory=dict,
        description="Additional execution metadata (seed, configuration, paths, etc.).",
    )


class ProphecyUpdateReceipt(BaseReceipt):
    """Receipt linking a progressive update to an existing prophecy decision."""

    receipt_type: str = "ProphecyUpdateReceipt"
    parent_id: str = Field(
        ...,
        description="Immediate predecessor receipt id (ProphecyStone or ProphecyUpdateReceipt).",
    )
    root_prophecy_id: str = Field(
        ...,
        description="Canonical id of the originating ProphecyStone receipt (T0).",
    )
    asset_id: Optional[str] = Field(
        default=None,
        description="Subject or asset identifier associated with the case.",
    )
    previous_prob: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Probability before incorporating the new evidence.",
    )
    updated_prob: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Probability after incorporating the new evidence.",
    )
    confidence_delta: float = Field(
        ...,
        description="Signed delta between updated_prob and previous_prob.",
    )
    new_evidence: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Evidence payloads that triggered the update.",
    )
    metrics_snapshot: Dict[str, Any] = Field(
        default_factory=dict,
        description="Metric context captured when producing the update.",
    )
    status_snapshot: Dict[str, Any] = Field(
        default_factory=dict,
        description="Gate status snapshot captured alongside the original prophecy.",
    )
    gate_witness_snapshot: Optional[Dict[str, Any]] = Field(
        default=None,
        description="Evaluated GateWitness tree (or summary) at update time.",
    )
    policy_bundle_hash: Optional[str] = Field(
        default=None,
        description="Policy bundle digest active during the update.",
    )
    policy_version: Optional[str] = Field(
        default=None,
        description="Policy bundle semantic version active during the update.",
    )
    updated_by: Optional[str] = Field(
        default=None,
        description="Identifier for the operator or system performing the update.",
    )


class EvidencePlanItem(BaseModel):
    """Plan component describing a single evidence collection step."""

    evidence_id: str = Field(description="Canonical identifier for the evidence input to collect.")
    description: str = Field(description="Human-readable description of the evidence request.")
    expected_uplift: float = Field(
        ge=0.0,
        le=1.0,
        description="Estimated probability uplift contributed by this evidence.",
    )
    cost: float = Field(
        ge=0.0,
        description="Estimated monetary cost to obtain the evidence (USD).",
    )
    duration_hours: float = Field(
        ge=0.0,
        description="Estimated turnaround time in hours to procure the evidence.",
    )
    breach_risk: float = Field(
        ge=0.0,
        le=1.0,
        description="Probability that the evidence path triggers a gate breach.",
    )


class EvidencePlanReceipt(BaseReceipt):
    """Receipt documenting an Expected-Value-of-Information evidence plan."""

    receipt_type: str = "EvidencePlanReceipt"
    plan_id: str = Field(description="Stable identifier for the evidence plan.")
    asset_id: Optional[str] = Field(
        default=None,
        description="Subject or case identifier associated with the plan.",
    )
    current_prob: float = Field(
        ge=0.0,
        le=1.0,
        description="Current calibrated probability prior to collecting new evidence.",
    )
    target_prob: float = Field(
        ge=0.0,
        le=1.0,
        description="Target probability required to cross the gate threshold.",
    )
    expected_uplift: float = Field(
        ge=0.0,
        description="Aggregate probability uplift contributed by the plan.",
    )
    expected_post_prob: float = Field(
        ge=0.0,
        le=1.0,
        description="Expected probability after executing the plan.",
    )
    total_cost: float = Field(
        ge=0.0,
        description="Total estimated monetary cost for the plan (USD).",
    )
    total_hours: float = Field(
        ge=0.0,
        description="Total estimated turnaround time in hours.",
    )
    breach_risk: float = Field(
        ge=0.0,
        le=1.0,
        description="Aggregate probability that the plan breaches guardrails.",
    )
    plan_score: Optional[float] = Field(
        default=None,
        description="Greedy efficiency score used during plan selection.",
    )
    route: Literal["veto_queue", "manual_review", "informational"] = Field(
        default="veto_queue",
        description="Operational route for the plan hand-off.",
    )
    items: List[EvidencePlanItem] = Field(
        default_factory=list,
        description="Ordered list of evidence acquisition steps.",
    )
    abstention_scenario: Optional[str] = Field(
        default=None,
        description="Abstention scenario associated with the refusal prompting the plan.",
    )
    lineage: Optional[UncertaintyLineage] = Field(
        default=None,
        description="Embedded uncertainty lineage providing neighbour context.",
    )
    notes: Optional[str] = Field(
        default=None,
        description="Optional operator-facing notes or rationale for the plan.",
    )


class ActiveLearningCueReceipt(BaseReceipt):
    """Receipt emitted when autonomy queues a prediction for human review."""

    receipt_type: str = "ActiveLearningCueReceipt"
    domain: str = Field(description="Domain that generated the cue")
    cohort_id: str = Field(description="Cohort identifier associated with the sample")
    triage_url: str = Field(description="URL directing operators to the cue triage workspace")
    sla_hours: float = Field(description="Target SLA (in hours) for resolving the cue")
    assignee_hint: str = Field(description="Suggested assignee or team for the cue")
    trigger: Literal["dwf_floor"] = Field(
        default="dwf_floor",
        description="Trigger condition responsible for the cue",
    )
    observed_value: float = Field(
        description="Observed metric value that breached the configured threshold"
    )
    threshold: float = Field(description="Threshold used to evaluate the trigger condition")
    source_receipt_id: Optional[str] = Field(
        default=None,
        description="Identifier of the prophecy/refusal receipt tied to this cue",
    )
    context: Dict[str, Any] = Field(
        default_factory=dict,
        description="Minimal structured context provided to human reviewers",
    )


class CueLifecycleReceipt(BaseReceipt):
    """Receipt emitted when operators take or resolve an active learning cue."""

    receipt_type: str = "CueLifecycleReceipt"
    cue_receipt_id: str = Field(description="ActiveLearningCueReceipt identifier being updated")
    action: Literal["take", "done"] = Field(description="Lifecycle action applied to the cue")
    operator: str = Field(description="Operator applying the lifecycle action")
    assignee: Optional[str] = Field(default=None, description="Effective assignee for the cue")
    notes: Optional[str] = Field(default=None, description="Optional operator notes for the transition")


class LabelRequestReceipt(BaseReceipt):
    """Active learning receipt requesting a manual label for a prior refusal."""

    receipt_type: str = "LabelRequestReceipt"
    target_receipt_id: str = Field(description="Underlying refusal or prophecy receipt identifier")
    acquisition_score: float = Field(
        default=0.0,
        description="Relative priority for labeling derived from the active learning selector",
    )
    priority_score: float = Field(
        default=0.0,
        description="Composite triage priority (cohort importance × uncertainty × drift)",
    )
    priority_reason: Optional[str] = Field(
        default=None,
        description="Trigger rationale describing why the request was minted",
    )
    cohort: Optional[str] = Field(
        default=None,
        description="Cohort identifier associated with the request (e.g. market, channel)",
    )
    features_digest: Dict[str, Any] = Field(
        default_factory=dict,
        description="Minimal feature summary needed for labeling triage",
    )
    notes: Optional[str] = Field(
        default=None,
        description="Operator guidance or manual context captured at request time",
    )


class LabelResponseReceipt(BaseReceipt):
    """Receipt capturing the human-in-the-loop feedback for a label request."""

    receipt_type: str = "LabelResponseReceipt"
    target_receipt_id: str = Field(description="Identifier of the label request being fulfilled")
    label: str = Field(description="Human-provided ground truth or annotation outcome")
    reviewer: str = Field(description="Identifier for the reviewer or analyst providing the label")
    squad: Optional[str] = Field(
        default=None,
        description="Review squad identifier for multi-review alignment tracking",
    )
    notes: Optional[str] = Field(
        default=None,
        description="Optional free-form notes captured during review",
    )


class LabelAuditReceipt(BaseReceipt):
    """
    Receipt capturing agreement metrics for a human label request.

    Records consensus statistics and flags when agreement falls below the configured
    confidence band so downstream monitoring can alert review leads.
    """

    receipt_type: str = "LabelAuditReceipt"
    label_request_id: str = Field(description="Identifier of the audited label request")
    target_receipt_id: str = Field(description="Identifier of the underlying asset labelled")
    reviewers: List[str] = Field(
        default_factory=list,
        description="Reviewers whose latest responses were included in the audit",
    )
    labels_by_reviewer: Dict[str, str] = Field(
        default_factory=dict,
        description="Mapping of reviewer identifier to their submitted label",
    )
    label_counts: Dict[str, int] = Field(
        default_factory=dict,
        description="Aggregate counts per label across the reviewer set",
    )
    total_responses: int = Field(
        default=0,
        description="Total number of responses considered in the audit window",
    )
    consensus_label: Optional[str] = Field(
        default=None,
        description="Label chosen by plurality consensus amongst reviewers",
    )
    consensus_ratio: Optional[float] = Field(
        default=None,
        description="Share of reviewers supporting the consensus_label",
    )
    cohens_kappa: Optional[float] = Field(
        default=None,
        description="Cohen's κ computed when exactly two reviewers participated",
    )
    fleiss_kappa: Optional[float] = Field(
        default=None,
        description="Fleiss' κ computed when three or more reviewers participated",
    )
    low_agreement: bool = Field(
        default=False,
        description="True when agreement fell below the configured minimum threshold",
    )
    gold: bool = Field(
        default=False,
        description="True when agreement satisfied the gold-standard confidence threshold",
    )
    thresholds: Dict[str, float] = Field(
        default_factory=dict,
        description="Threshold values used during evaluation (e.g. low/minimum, gold)",
    )


class TrainingRunReceipt(BaseReceipt):
    """Receipt emitted when an automated training cycle completes."""

    receipt_type: str = "TrainingRunReceipt"
    run_id: str = Field(description="Identifier for the scheduled training run")
    backlog_size: int = Field(description="Outstanding label request count at trigger time")
    label_count: int = Field(description="Number of labels included in the training dataset snapshot")
    dataset_path: str = Field(description="Filesystem path to the materialised training dataset")
    label_snapshot_ts: Optional[datetime] = Field(
        default=None,
        description="Timestamp of the most recent label included in this run",
    )
    run_card_path: str = Field(description="Path to the run card used for this training job")
    research_receipt_id: str = Field(description="Associated research receipt identifier")
    research_receipt_hash: Optional[str] = Field(
        default=None,
        description="Hash of the associated research receipt at execution time",
    )
    dip_receipt_id: Optional[str] = Field(
        default=None,
        description="DIP receipt emitted for the training job inputs",
    )
    candidate_path: Optional[str] = Field(
        default=None,
        description="Filesystem path to the promotion candidate artifact" ,
    )
    metrics: Dict[str, float] = Field(default_factory=dict, description="Key evaluation metrics from the run")
    gates_passed: bool = Field(default=False, description="True when promotion gates all passed")
    auto_promoted: bool = Field(default=False, description="True when the scheduler promoted the candidate automatically")
    promotion_reason: Optional[str] = Field(default=None, description="Reason recorded for automatic promotion decisions")
    model_tag: Optional[str] = Field(default=None, description="Model tag evaluated for promotion")


class SamplingMetadata(BaseModel):
    model_config = ConfigDictLike(extra="allow")
    total: int = 0
    taken: int = 0
    seed: Union[str, int] = "0"
    orphan_outcome_count: int = 0
    agent_ns: Optional[str] = None
    seed_token: Optional[str] = None
    seed_namespace: Optional[str] = None
    seed_override: Optional[bool] = None
    window: Optional[str] = None
    window_seconds: Optional[float] = None
    max_samples: Optional[int] = None
    reservoir_hash: Optional[str] = None
    orphan_samples: List[Dict[str, str]] = Field(default_factory=list)


class MonitorRunReceipt(BaseReceipt):
    """Receipt emitted when the cohort monitor scheduler completes a scan."""

    receipt_type: str = "MonitorRunReceipt"
    agent_ns: str = Field(
        default="codex",
        description="Agent namespace that executed the monitor run",
    )
    started_at: datetime = Field(description="Timestamp when the monitor scan began")
    finished_at: datetime = Field(description="Timestamp when the monitor scan completed")
    cohorts_scanned: int = Field(default=0, description="Number of cohorts evaluated during this run")
    alerts_emitted: int = Field(default=0, description="Number of calibration alerts emitted during this run")
    pages_sent: int = Field(default=0, description="Number of on-call paging fan-outs triggered by this run")
    orphan_outcome_count: int = Field(default=0, description="Number of outcomes skipped due to missing parent prophecy")
    sampling_meta: SamplingMetadata = Field(
        default_factory=SamplingMetadata,
        description="Sampling metadata including total candidate population, sampled rows, deterministic seed, and orphan accounting",
    )
    cohort_counts: Dict[str, int] = Field(
        default_factory=dict,
        description="Sample counts per cohort evaluated during this run",
    )


class TrustReportExportReceipt(BaseReceipt):
    """Receipt emitted when the LOS trust report exporter runs."""

    receipt_type: str = "TrustReportExportReceipt"
    rows: int = Field(description="Number of cohort rows exported in the trust report")
    sink: Literal["csv", "sheets"] = Field(description="Export destination type")
    since: datetime = Field(description="Lookback window start time used for the export")
    antifragility_watch: Dict[str, float] = Field(
        default_factory=dict,
        description="Snapshot of Coherence Capacity (Cκ) + Grace metrics included for downstream alerting.",
    )


class ThreadStone(BaseReceipt):
    """Narrative thread receipt linking a chain of related receipts."""

    receipt_type: str = "ThreadStone"
    thread_anchor_id: str = Field(
        description="Primary receipt identifier anchoring the narrative thread"
    )
    receipt_ids: List[str] = Field(
        default_factory=list,
        description="Chronologically ordered receipt identifiers included in the thread",
    )
    counts: Dict[str, int] = Field(
        default_factory=dict,
        description="Count of receipts by receipt_type within the thread",
    )
    gate_reasons: List[str] = Field(
        default_factory=list,
        description="Unique gate failure reasons observed across the thread",
    )
    outcomes: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Outcome receipt summaries for the thread",
    )
    start_ts: datetime = Field(description="Timestamp of the earliest receipt in the thread")
    end_ts: datetime = Field(description="Timestamp of the latest receipt in the thread")
    assets: List[str] = Field(
        default_factory=list,
        description="Asset identifiers referenced across the thread",
    )
    seeded_from: Optional[str] = Field(
        default=None,
        description="Receipt identifier that seeded this thread (None when human initiated)",
    )
    origin_type: Literal["human", "echo", "ponr", "auto", "compost"] = Field(
        default="human",
        description="Origin archetype for this thread's creation",
    )

    @model_validator(mode="after")
    def _validate_origin(self) -> "ThreadStone":
        if self.origin_type != "human":
            if not self.seeded_from:
                raise ValueError("seeded_from is required when origin_type is not 'human'")
            if self.seeded_from not in self.receipt_ids:
                raise ValueError("seeded_from must reference a receipt included in receipt_ids")
        return self


class EchoStone(BaseReceipt):
    """Residual insight receipt capturing recurring error patterns."""

    receipt_type: str = "EchoStone"
    pattern_name: str = Field(description="Canonical name for the detected residue pattern")
    pattern_description: str = Field(description="Narrative summary of the residual behaviour")
    context_specificity: Literal["global", "domain", "cohort", "regime", "asset"] = Field(
        description="Granularity of the detected residue"
    )
    context_filters: Dict[str, Any] = Field(
        default_factory=dict,
        description="Filters applied to isolate the residue (e.g., cohort, asset tags)",
    )
    residue_index: float = Field(description="Magnitude of the residual signal")
    frequency: float = Field(description="Occurrence frequency of the residue pattern")
    stability: float = Field(description="Stability score indicating persistence over time")
    coherence_delta: float = Field(description="Coherence delta observed for the residue cohort")
    residue_threshold: float = Field(
        default=0.1,
        description="Minimum residue index required before minting an EchoStone",
    )
    exemplar_receipt_ids: List[str] = Field(
        default_factory=list,
        description="Representative receipts exemplifying the residue",
    )
    discovery_window: Dict[str, datetime] = Field(
        default_factory=dict,
        description="Window bounds {'start': datetime, 'end': datetime} used for discovery",
    )
    promoted_to_feature: bool = Field(
        default=False,
        description="True when this residue has been promoted into a feature gate",
    )
    feature_gate_metrics: Dict[str, Any] = Field(
        default_factory=dict,
        description="Metrics collected during feature gate evaluation",
    )
    discovered_by: str = Field(description="Identifier for the analyst or automation that surfaced the residue")

    @model_validator(mode="after")
    def _validate_threshold(self) -> "EchoStone":
        if self.residue_index < self.residue_threshold:
            raise ValueError("residue_index must meet or exceed residue_threshold")
        return self


class PoNRStone(BaseReceipt):
    """Proof-of-Non-Resolution receipt documenting unresolved contradictions."""

    receipt_type: str = "PoNRStone"
    contradiction_type: str = Field(description="Category of contradiction that cannot be resolved with current tooling")
    contradiction_description: str = Field(description="Narrative describing the contradiction and observed impact")
    conflicting_receipt_ids: List[str] = Field(
        default_factory=list,
        description="Receipts that demonstrate the contradiction",
    )
    attempted_methods: List[str] = Field(
        default_factory=list,
        description="Remediation methods attempted prior to declaring PoNR",
    )
    why_unresolvable: str = Field(description="Explanation detailing why the contradiction remains unresolved")
    bounty_description: Optional[str] = Field(
        default=None,
        description="Narrative describing the bounty and desired remediation",
    )
    bounty_credits: Optional[float] = Field(
        default=None,
        description="Credits awarded for resolving this contradiction",
    )
    priority: Literal["p0", "p1", "p2", "p3", "p4"] = Field(
        default="p2",
        description="Operational priority for addressing the contradiction",
    )
    blocks_predictions: bool = Field(
        default=False,
        description="True when this contradiction blocks prediction issuance",
    )
    affected_assets: List[str] = Field(
        default_factory=list,
        description="Assets impacted by the contradiction",
    )
    resolution_receipt_id: Optional[str] = Field(
        default=None,
        description="Receipt identifier proving resolution, when available",
    )
    resolved_at: Optional[datetime] = Field(
        default=None,
        description="Timestamp when the contradiction was resolved",
    )

    @model_validator(mode="after")
    def _ensure_bounty(self) -> "PoNRStone":
        base = {
            "p0": 1000.0,
            "p1": 500.0,
            "p2": 250.0,
            "p3": 100.0,
            "p4": 50.0,
        }.get(self.priority, 100.0)
        if self.blocks_predictions:
            base *= 2.0
        current = self.bounty_credits
        if current is None or float(current) <= 0:
            object.__setattr__(self, "bounty_credits", float(base))
        return self


class ChoirReceipt(BaseReceipt):
    """Thread closure receipt summarising key metrics and narrative highlights."""

    receipt_type: str = "ChoirReceipt"
    thread_receipt_id: str = Field(description="ThreadStone identifier that this closure references")
    thread_id: Optional[str] = Field(default=None, description="Thread identifier inherited from the ThreadStone")
    decisions_summary: Dict[str, Any] = Field(default_factory=dict, description="Aggregated decisions and timeline metadata")
    refusal_taxonomy: Dict[str, int] = Field(default_factory=dict, description="Counts by refusal reason taxonomy")
    calibration_deltas: Dict[str, Any] = Field(default_factory=dict, description="Calibration delta statistics gathered for the thread")
    realized_utility: Dict[str, Any] = Field(default_factory=dict, description="Realized utility metrics captured for the thread")
    proof_pack_bundle_hash: Optional[str] = Field(default=None, description="Hash of the Proof-Pack bundle anchoring the narrative")

    @model_validator(mode="after")
    def _normalise_summary(self) -> "ChoirReceipt":
        counts = self.decisions_summary.get("counts") if isinstance(self.decisions_summary, dict) else None
        if counts is None:
            self.decisions_summary.setdefault("counts", {})
        return self


class SilenceReceipt(BaseReceipt):
    """Silent refusal receipt"""
    receipt_type: str = "SilenceReceipt"


class TombstoneReason(str, Enum):
    RETENTION_PURGE = "retention_purge"
    DSR_DELETE = "dsr_delete"


class TombstoneReceipt(BaseReceipt):
    """Receipt emitted when a record is removed for retention or DSR compliance."""

    receipt_type: str = "TombstoneReceipt"
    tombstoned_receipt_id: str
    tombstoned_receipt_hash: Optional[str] = None
    reason: TombstoneReason = TombstoneReason.RETENTION_PURGE
    actor: Optional[str] = None
    note: Optional[str] = None


class VetoAction(str, Enum):
    APPROVE = "APPROVE"
    REFUSE = "REFUSE"
    HOLD = "HOLD"
    ESCALATE = "ESCALATE"


class VetoIntent(str, Enum):
    SAFETY = "SAFETY"
    DATA_GAP = "DATA_GAP"
    MARKET = "MARKET"
    POLICY_OVERRIDE = "POLICY_OVERRIDE"
    OTHER = "OTHER"


class ReplayReceipt(BaseReceipt):
    """Deterministic replay proof comparing stored receipt with regenerated output."""

    receipt_type: str = "ReplayReceipt"
    target_receipt_id: str = Field(description="Receipt identifier being replayed")
    target_receipt_hash: Optional[str] = Field(default=None, description="Hash recorded on the original receipt")
    replay_seed: str = Field(default="0", description="Seed used for deterministic replay")
    comparison: Dict[str, Any] = Field(
        default_factory=dict,
        description="Comparison metrics between original and replayed outputs",
    )
    frozen_artifacts: Dict[str, Any] = Field(
        default_factory=dict,
        description="Frozen artifact manifest captured during replay",
    )
    status: Literal["matched", "diverged"] = Field(
        default="matched",
        description="Replay outcome status indicating whether outputs matched",
    )


class ReplayBundleReceipt(BaseReceipt):
    """
    Deterministic time-travel replay bundle receipt.

    Proves that a DIP→Prophecy decision can be re-executed under the original
    gatebook/model/code digests with identical canonical bytes and policy witness.

    Used for reproducibility audits and compliance verification.
    """

    receipt_type: str = "ReplayBundleReceipt"

    target_receipt_id: str = Field(
        description="Decision receipt ID being replayed (e.g., ProphecyStone ID)"
    )

    # Artifact digests (frozen at time of original decision)
    inputs_digest: str = Field(
        description="SHA-256 digest of input data manifest (DIPReceipt.data_manifest_hash)"
    )

    model_digest: str = Field(
        description="SHA-256 digest of model artifacts (DIPReceipt.model_hash)"
    )

    code_digest: str = Field(
        description="SHA-256 digest of inference code (DIPReceipt.code_hash)"
    )

    gatebook_digest: str = Field(
        description="SHA-256 digest of gatebook policy (PolicyWitness.gatebook_digest)"
    )

    environment_fingerprint: Optional[str] = Field(
        default=None,
        description="Environment hash (DIPReceipt.environment_fingerprint)"
    )

    # Execution environment
    runner_image: str = Field(
        description="OCI/Nix image used for deterministic replay (e.g., 'loom-replay:v1@sha256:...')"
    )

    runner_platform: str = Field(
        default="linux/amd64",
        description="Platform specification for runner (e.g., 'linux/amd64', 'linux/arm64')"
    )

    # Replay execution results
    success: bool = Field(
        description="Whether replay executed successfully without errors"
    )

    match: bool = Field(
        default=False,
        description="Whether replayed output matches original canonical bytes and policy witness"
    )

    # Verification checksums
    checksums: Dict[str, str] = Field(
        default_factory=dict,
        description="""
        Hash comparisons between original and replayed outputs.
        Keys: 'original_receipt_hash', 'replayed_receipt_hash',
        'original_policy_witness', 'replayed_policy_witness', etc.
        """
    )

    # Replay metadata
    bundle_path: Optional[str] = Field(
        default=None,
        description="Path to .loom-replay/ bundle directory"
    )

    bundle_manifest_hash: Optional[str] = Field(
        default=None,
        description="SHA-256 hash of bundle manifest.json"
    )

    # Performance and budget tracking
    original_latency_ms: Optional[float] = Field(
        default=None,
        description="Original decision latency from PolicyWitness"
    )

    replayed_latency_ms: Optional[float] = Field(
        default=None,
        description="Replayed decision latency"
    )

    latency_budget_ms: Optional[float] = Field(
        default=None,
        description="Latency budget from original PolicyWitness"
    )

    latency_budget_kept: bool = Field(
        default=True,
        description="Whether replay stayed within original latency budget"
    )

    # Diagnostics (populated on failure)
    divergence_details: Optional[Dict[str, Any]] = Field(
        default=None,
        description="Detailed diagnostics when match=False (field-by-field diffs, stack traces)"
    )

    error_message: Optional[str] = Field(
        default=None,
        description="Error message if success=False"
    )

    # Parent linkage
    parent_dip_receipt_id: Optional[str] = Field(
        default=None,
        description="DIPReceipt ID from original decision lineage"
    )

    @field_validator("inputs_digest", "model_digest", "code_digest", "gatebook_digest")
    @classmethod
    def _validate_digest_format(cls, value: str) -> str:
        """Ensure digest is in sha256:<hex> format."""
        if not value:
            raise ValueError("Digest cannot be empty")
        if not value.startswith("sha256:"):
            raise ValueError(f"Digest must start with 'sha256:', got: {value}")
        hex_part = value[7:]
        if len(hex_part) != 64 or not all(c in "0123456789abcdef" for c in hex_part.lower()):
            raise ValueError(f"Digest must be sha256:<64-hex-chars>, got: {value}")
        return value

    @field_validator("runner_image")
    @classmethod
    def _validate_runner_image(cls, value: str) -> str:
        """Ensure runner image is specified."""
        if not value or not value.strip():
            raise ValueError("runner_image must be specified")
        return value.strip()


class OutcomeLateWarningReceipt(BaseReceipt):
    """Receipt emitted when a conflicting outcome update arrives beyond the policy window."""

    receipt_type: str = "OutcomeLateWarning"
    parent_prophecy_id: str = Field(
        validation_alias=AliasChoices("parent_prophecy_id", "parent_id"),
        serialization_alias="parent_id",
    )
    existing_outcome_id: Optional[str] = None
    attempted_outcome_id: Optional[str] = None
    policy_window_minutes: Optional[float] = None
    elapsed_minutes: Optional[float] = None
    attempted_hash: Optional[str] = None
    existing_hash: Optional[str] = None
    attempted_realized: Optional[float] = None
    existing_realized: Optional[float] = None
    note: Optional[str] = None

    @property
    def parent_id(self) -> str:
        return self.parent_prophecy_id

    @model_validator(mode="after")
    def _ensure_prophecy_link(self) -> "OutcomeLateWarningReceipt":
        parent_id = self.parent_prophecy_id
        if parent_id:
            if not self.parent_ids:
                self.parent_ids = [parent_id]
            related = dict(self.related_ids or {})
            related.setdefault("prophecy", parent_id)
            self.related_ids = related
        return self


class EvaluationWindow(BaseModel):
    start: Optional[datetime] = None
    end: Optional[datetime] = None


class EvaluationCohortMetrics(BaseModel):
    cohort: str
    metrics: Dict[str, Any] = Field(default_factory=dict)
    support: Optional[int] = None


class EvaluationGlobalMetrics(BaseModel):
    model_config = ConfigDictLike(populate_by_name=True)

    pr_auc: Optional[float] = Field(default=None, alias="pr_auc")
    auroc: Optional[float] = Field(default=None, alias="auroc")
    c_index: Optional[float] = Field(default=None, alias="c_index")
    brier: Optional[float] = Field(default=None, alias="brier")
    brier_raw: Optional[float] = Field(default=None, alias="brier_raw")
    brier_calibrated: Optional[float] = Field(default=None, alias="brier_calibrated")
    delta_brier: Optional[float] = Field(default=None, alias="delta_brier")
    ece_at_10: Optional[float] = Field(default=None, alias="ece@10")
    ece_weighted_at_10: Optional[float] = Field(default=None, alias="ece_weighted@10")
    ece_raw: Optional[float] = Field(default=None, alias="ece_raw")
    ece_calibrated: Optional[float] = Field(default=None, alias="ece_calibrated")
    ece: Optional[float] = Field(default=None, alias="ece")
    worst_slice_ece: Optional[float] = Field(default=None, alias="worst_slice_ece")
    coverage: Optional[float] = Field(default=None, alias="coverage")
    abstain_rate: Optional[float] = Field(default=None, alias="abstain_rate")
    fairness_gap: Optional[float] = Field(default=None, alias="fairness_gap")
    logloss: Optional[float] = Field(default=None, alias="logloss")
    spiegelhalter_z: Optional[float] = Field(default=None, alias="spiegelhalter_z")
    spiegelhalter_p: Optional[float] = Field(default=None, alias="spiegelhalter_p")
    n_train: Optional[int] = Field(default=None, alias="n_train")
    n_test: Optional[int] = Field(default=None, alias="n_test")
    positive_rate: Optional[float] = Field(default=None, alias="positive_rate")


class CalibrationBin(BaseModel):
    bin_low: float
    bin_high: float
    n: int
    empirical: Optional[float] = None
    predicted: Optional[float] = None
    ci_low: Optional[float] = None
    ci_high: Optional[float] = None


class EvaluationArtifact(BaseModel):
    name: str
    path: str
    kind: Optional[str] = None
    description: Optional[str] = None


class EvaluationMetrics(BaseModel):
    model_config = ConfigDictLike(populate_by_name=True)

    global_metrics: EvaluationGlobalMetrics = Field(default_factory=EvaluationGlobalMetrics, alias="global")
    by_cohort: Optional[List[EvaluationCohortMetrics]] = Field(default=None, alias="by_cohort")


class EvaluationReceipt(BaseReceipt):
    """Canonical evaluation receipt minted from evaluation artifacts."""

    receipt_type: str = "EvaluationReceipt"
    domain: Literal["claims", "pa"]
    model_tag: str
    train_window: Optional[EvaluationWindow] = None
    eval_window: Optional[EvaluationWindow] = None
    metrics: EvaluationMetrics = Field(default_factory=EvaluationMetrics)
    calibration_bins: List[CalibrationBin] = Field(default_factory=list)
    artifacts: List[EvaluationArtifact] = Field(default_factory=list)
    data_hashes: Dict[str, str] = Field(default_factory=dict)
    git_sha: Optional[str] = None
    container_digest: Optional[str] = None
    timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    signatures: List[Ed25519Signature] = Field(
        default_factory=list,
        description="Verifier signatures attesting to evaluation metrics.",
    )


class CalibrationEvidenceMetrics(BaseModel):
    """Summary metrics captured for calibration sufficiency evidence."""

    ece: float = Field(description="Expected calibration error across the replay window.")
    worst_slice_ece: float = Field(
        description="Largest calibration gap observed across configured slices.",
    )
    coverage: float = Field(description="Observed coverage rate for replayed intervals.")
    coverage_error: float = Field(
        description="Absolute deviation between observed coverage and the configured target.",
    )
    brier: float = Field(description="Mean Brier score across the replayed prophecies.")


class CalibrationEvidenceReceipt(BaseReceipt):
    """
    Receipt attesting to a calibration sufficiency replay.

    Captures the inputs, metrics, and cryptographic hashes for the replayed
    prophecy/outcome pairs so downstream surfaces (dashboards, dossiers) can
    verify sample sufficiency.
    """

    receipt_type: str = "CalibrationEvidenceReceipt"
    domain: str = Field(description="Domain evaluated during the replay (e.g., mlb, pa).")
    run_id: str = Field(description="Deterministic identifier for the backfill run.")
    mode: Literal["REPLAY", "DRY_RUN", "BACKFILL"] = Field(
        default="REPLAY",
        description="Execution mode recorded for the replay harness.",
    )
    sample_sources: List[str] = Field(
        default_factory=list,
        description="Relative paths or identifiers for the replayed historical datasets.",
    )
    sample_window: Optional[Dict[str, Any]] = Field(
        default=None,
        description="Optional temporal window applied to the replayed samples.",
    )
    prophecy_count: int = Field(description="Number of ProphecyStone receipts emitted during the replay.")
    outcome_count: int = Field(description="Number of Outcome receipts minted in the replay.")
    metrics: CalibrationEvidenceMetrics = Field(
        description="Aggregate calibration metrics computed over the replayed samples.",
    )
    worst_slice: Optional[Dict[str, Any]] = Field(
        default=None,
        description="Details for the worst-performing slice (column, value, support).",
    )
    hashes: Dict[str, str] = Field(
        default_factory=dict,
        description="SHA-256 digests proving integrity of prophecy/outcome payloads.",
    )
    inputs: Dict[str, Any] = Field(
        default_factory=dict,
        description="Normalized configuration inputs used for the replay run.",
    )
    data_hashes: Dict[str, str] = Field(
        default_factory=dict,
        description="Optional hashes for raw dataset payloads consumed by the run.",
    )
    git_sha: Optional[str] = Field(
        default=None,
        description="Git commit captured when the replay was executed.",
    )
    container_digest: Optional[str] = Field(
        default=None,
        description="Optional container digest used for the replay environment.",
    )


class ResearchReceipt(BaseReceipt):
    """Receipt capturing research run execution driven by a run card."""

    receipt_type: str = "ResearchReceipt"
    domain: str = Field(default="research", description="Domain identifier for research evaluations.")
    run_id: str = Field(description="Stable identifier for the research run.")
    intent: str = Field(description="Description of the research objective.")
    seed: int = Field(description="Seed used for deterministic replay of the run.")
    run_card_hash: str = Field(description="SHA-256 hash of the canonicalised run card payload.")
    dataset_source: str = Field(description="Resolved dataset path used for the run.")
    dataset_hash: str = Field(description="SHA-256 hash of the dataset bytes.")
    dataset_window: Optional[EvaluationWindow] = Field(
        default=None,
        description="Window bounds applied to the dataset for this run.",
    )
    model_tag: str = Field(description="Model tag or identifier evaluated during the run.")
    model_config_hash: str = Field(description="SHA-256 hash of the normalised model configuration.")
    metrics: Dict[str, float] = Field(
        default_factory=dict,
        description="Evaluation metrics emitted by the run.",
    )
    dip_receipt_id: Optional[str] = Field(
        default=None,
        description="Identifier of the DIPReceipt securing the run inputs.",
    )
    dip_receipt_hash: Optional[str] = Field(
        default=None,
        description="SHA-256 hash of the associated DIPReceipt body.",
    )
    artifacts: List[EvaluationArtifact] = Field(
        default_factory=list,
        description="Supplementary artifacts generated by the run.",
    )
    notes: Optional[str] = Field(
        default=None,
        description="Optional analyst notes or summary for the run.",
    )
    run_card_path: Optional[str] = Field(
        default=None,
        description="Filesystem path to the executed run card.",
    )
    git_sha: Optional[str] = Field(
        default=None,
        description="Git SHA recorded at execution time.",
    )
    mode: str = Field(default="RESEARCH", description="Execution mode for research runs.")


# API shims ---------------------------------------------------------------


class DenialRiskReason(BaseModel):
    code: str
    weight: float
    message: str


class EnergyReceipt(BaseReceipt):
    """Receipt documenting energy demand response actions and savings."""

    receipt_type: Literal["energy"] = "energy"
    domain: Literal["energy"] = "energy"
    triclock_ts: str
    job_id: str
    model_id: str
    version: str
    action: Literal["run", "defer"]
    price_signal: Dict[str, Any]
    predicted_kwh: float
    actual_kwh: Optional[float] = None
    delta_kwh: float
    delta_co2e: float
    dollar_saved: float
    dr_credit: Optional[float] = None
    slo_proof: Dict[str, Any] = Field(default_factory=dict)
    jcs_sha256: str
    sig: str
    pubkey_id: str


Prophecy = ProphecyStone
Outcome = OutcomeReceipt
Refusal = RefusalStone


class RealWorldPerformanceReceipt(BaseReceipt):
    """Receipt summarizing real-world performance metrics for a model."""

    receipt_type: str = "RealWorldPerformanceReceipt"
    model_tag: str
    window_start: datetime
    window_end: datetime
    metrics: Dict[str, Any] = Field(default_factory=dict)
    slice_hotspots: List[Dict[str, Any]] = Field(default_factory=list)
    input_hashes: Dict[str, str] = Field(default_factory=dict)
    baseline_resolution: Optional[str] = None
    baseline_receipt_id: Optional[str] = None
    seeded_baseline: bool = False

    @property
    def created_at(self) -> datetime:
        return self.ts


class FairnessReceipt(BaseReceipt):
    """Receipt capturing fairness diagnostics for a model snapshot."""

    receipt_type: str = "FairnessReceipt"
    model_tag: str
    domain: str
    cohorts: List[Dict[str, Any]] = Field(default_factory=list)
    metrics: Dict[str, float] = Field(default_factory=dict)
    timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    signatures: List[Ed25519Signature] = Field(default_factory=list)


class CohortAlertReceipt(BaseReceipt):
    """Receipt emitted when cohort disparity thresholds are breached."""

    receipt_type: str = "CohortAlertReceipt"
    model_tag: str
    cohort_type: str  # e.g., "region", "channel", "age_band"
    cohort_value: Optional[str] = None  # e.g., "North", "fax", "65+"
    metric: str = "refusal_rate"
    global_rate: float
    cohort_rate: float
    delta_pp: float  # Absolute delta in percentage points
    support: int  # Sample size for this cohort
    status: Literal["pass", "warn", "fail", "insufficient"] = "insufficient"
    warn_threshold_pp: float = 5.0
    fail_threshold_pp: float = 10.0
    evaluated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    baseline_receipt_id: Optional[str] = None


class CalibrationAlertReceipt(BaseReceipt):
    """Receipt emitted when cohort-level calibration violations are detected.
    
    Triggers runtime autonomy degradation to prevent unsafe predictions
    in cohorts with poor ECE, insufficient sample size, or coverage drift.
    """

    receipt_type: str = "CalibrationAlertReceipt"
    cohort_id: str = Field(description="Cohort identifier (e.g., 'pneumonia-icu-age75p')")
    alert_type: str = Field(default="calibration_violation", description="Type of alert")
    severity: Literal["low", "medium", "high", "critical"] = Field(
        default="medium",
        description="Alert severity level"
    )
    metrics: Dict[str, Any] = Field(
        default_factory=dict,
        description="Cohort metrics (ece_worst, coverage, n, ci_width, etc.)"
    )
    threshold_violated: Dict[str, Any] = Field(
        default_factory=dict,
        description="Thresholds that were violated"
    )
    autonomy_action: Literal["abstain", "warn", "audit"] = Field(
        default="abstain",
        description="Action taken on autonomy (abstain=drop autonomy for 24h)"
    )
    reasons: List[str] = Field(
        default_factory=list,
        description="List of specific violation reasons"
    )
    evaluated_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="Timestamp when alert was evaluated"
    )
    expires_at: Optional[datetime] = Field(
        default=None,
        description="When autonomy degradation expires (if applicable)"
    )


class ConformalMetadata(BaseModel):
    alpha: float
    conf_lo: float
    conf_hi: float
    width: float


GateId = Literal[
    "conformal",
    "ci90_width",
    "coverage",
    "coverage90",
    "calibration_worst_slice",
    "dignity_floor",
    "challenger_agreement",
    "policy",
    "policy_fallback",
    "phi",
    "guardian",
    "attachment",
    "stand_down",
]


class GateEvent(BaseModel):
    gate_id: GateId
    passed: bool
    observed: Optional[float] = None
    threshold: Optional[float] = None
    operator: Optional[Literal["<=", ">=", "within"]] = None
    cohort: Optional[str] = None
    detail: Dict[str, Any] = Field(default_factory=dict)


class RefusalMetadata(BaseModel):
    model_config = ConfigDictLike(populate_by_name=True, extra="allow")

    reason_code: Optional[str] = Field(default=None, alias="reason")
    policy: Optional[str] = None
    refused: bool = False
    denial_codes: List[str] = Field(default_factory=list)
    denial_reasons: List[PADenialReasonPayload] = Field(default_factory=list)
    detail: Dict[str, Any] = Field(default_factory=dict)
    gate_events: List[GateEvent] = Field(default_factory=list)
    
    # Refusal taxonomy (AbstentionBench-aligned)
    refusal_type: Optional[Literal[
        "complete_rejection",      # "Cannot predict" - hard stop
        "soft_decline",            # "Confidence too low" - suggests alternatives
        "defer_to_human",          # "Requires expert judgment"
        "data_insufficient",       # "Missing critical features"
        "specification_gap",       # High model disagreement (Zhang et al.)
        "false_premise",           # Question assumes incorrect facts
        "underspecified",          # Query lacks critical details
        "stale_information",       # Outdated context
        "unknown_answer",          # Genuinely unanswerable
        "subjective_bias"          # Ethically sensitive / no normative answer
    ]] = "complete_rejection"
    
    # Refusal quality tracking
    explanation_provided: bool = False  # Did we give rationale in reasons?
    alternative_offered: bool = False   # Did we suggest recourse_paths?
    abstention_scenario: Optional[str] = None  # Maps to AbstentionBench category
    
    # Refusal quality 2.0
    explanation_score: Optional[float] = Field(
        default=None,
        description="Auto-graded explanation quality score in [0,1]",
    )
    recourse_kind: Optional[Literal[
        "collect_more_data",
        "clarify_premise",
        "defer_to_expert",
        "rephrase_question",
        "try_later",
    ]] = Field(default=None, description="Primary recourse taxonomy label")
    
    # Council disagreement metrics (Zhang et al. validation)
    council_disagreement: Optional[float] = None  # Std dev of component predictions
    council_size: Optional[int] = None            # Number of models in ensemble

    @property
    def reason(self) -> Optional[str]:
        return self.reason_code

    @reason.setter
    def reason(self, value: Optional[str]) -> None:
        self.reason_code = value

    def model_dump(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
        data = super().model_dump(*args, **kwargs)
        if "reason_code" in data and data.get("reason") is None:
            data["reason"] = data["reason_code"]
        return data


class RefusalAuditFix(BaseModel):
    """Suggested fix derived from false-positive refusal analysis."""

    cohort: Optional[str] = None
    model_tag: Optional[str] = None
    suggestion: str
    rationale: Optional[str] = None


class RefusalAuditReceipt(BaseReceipt):
    """Receipt summarizing false-positive refusal audit over a rolling window.

    Aggregates FP-refusal rates by cohort/model and captures suggested fixes
    for tiles and monitoring.
    """

    receipt_type: str = "RefusalAuditReceipt"
    window_days: int
    cohort_breakdown: Dict[str, Any] = Field(default_factory=dict)
    model_breakdown: Dict[str, Any] = Field(default_factory=dict)
    overall: Dict[str, Any] = Field(default_factory=dict)
    warnings: List[Dict[str, Any]] = Field(default_factory=list)
    top_fixes: List[RefusalAuditFix] = Field(default_factory=list)


class RefusalReasonSummary(BaseModel):
    """Top-line refusal aggregation for diagnostic receipts."""

    reason: str
    refusals: float
    refusal_share: float
    saved_ev: float
    saved_ev_share: float
    cumulative_share: float


class RefusalLeverRecommendation(BaseModel):
    """Recommended lever to reduce refusal volume while preserving value."""

    lever: Literal["data", "threshold", "model"]
    reason: str
    saved_ev: float
    refusal_share: float
    note: Optional[str] = None


class RefusalDiagnosticReceipt(BaseReceipt):
    """
    Diagnostic receipt capturing refusal drivers and saved EV contributions.

    Used for crisis investigations to understand which refusal reasons and gates
    dominate volume, and how much expected value they preserve relative to the
    business-as-usual baseline.
    """

    receipt_type: str = "RefusalDiagnosticReceipt"
    lookback_days: int
    total_refusals: int
    saved_ev_total: float
    reason_summary: List[RefusalReasonSummary] = Field(default_factory=list)
    gate_summary: List[Dict[str, Any]] = Field(default_factory=list)
    cohort_summary: List[Dict[str, Any]] = Field(default_factory=list)
    timeline_summary: List[Dict[str, Any]] = Field(default_factory=list)
    lever_recommendations: List[RefusalLeverRecommendation] = Field(default_factory=list)
    artifact_path: Optional[str] = None
    run_parameters: Dict[str, Any] = Field(default_factory=dict)
    top_reason_coverage: Optional[float] = None
    top_reason_saved_ev: Optional[float] = None


class SavedEVTrendPoint(BaseModel):
    """Weekly Saved EV trend snapshot for coverage monitoring."""

    period_start: datetime
    period_end: datetime
    total_refusals: int
    saved_ev_total: float
    top_reason_coverage: float
    top_reason_saved_ev: float
    top_reasons: List[RefusalReasonSummary] = Field(default_factory=list)
    lever_recommendations: List[RefusalLeverRecommendation] = Field(default_factory=list)


class SavedEVCoverageReceipt(BaseReceipt):
    """Weekly Saved EV coverage checkpoint with compliance guardrails."""

    receipt_type: str = "SavedEVCoverageReceipt"
    lookback_days: int
    period_start: datetime
    period_end: datetime
    total_refusals: int
    saved_ev_total: float
    top_reason_coverage: float
    top_reason_saved_ev: float
    coverage_threshold: float
    policy_floor: float
    coverage_breached: bool
    saved_ev_breached: bool
    top_reasons: List[RefusalReasonSummary] = Field(default_factory=list)
    lever_recommendations: List[RefusalLeverRecommendation] = Field(default_factory=list)
    trend_points: List[SavedEVTrendPoint] = Field(default_factory=list)
    artifact_path: Optional[str] = None
    run_parameters: Dict[str, Any] = Field(default_factory=dict)


class GatebookExperimentCandidate(BaseModel):
    """Per-candidate scoreboard entry for gatebook experiments."""

    name: str
    path: Optional[str] = None
    digest: Optional[str] = None
    baseline: bool = False
    metrics: Dict[str, float] = Field(default_factory=dict)
    deltas: Dict[str, float] = Field(default_factory=dict)
    passed_constraints: bool = True
    risk_flags: List[str] = Field(default_factory=list)
    metadata: Dict[str, Any] = Field(default_factory=dict)
    fairness: Dict[str, Any] = Field(default_factory=dict)


class GatebookExperimentReceipt(BaseReceipt):
    """Scoreboard receipt summarising counterfactual gatebook experiments."""

    receipt_type: str = "GatebookExperimentReceipt"
    experiment_id: Optional[str] = None
    dataset_hash: str
    baseline_candidate: str
    candidates: List[GatebookExperimentCandidate] = Field(default_factory=list)
    deltas: Dict[str, Dict[str, float]] = Field(default_factory=dict)
    winner: Optional[str] = None
    risks: List[str] = Field(default_factory=list)
    parameters: Dict[str, Any] = Field(default_factory=dict)
    artifact_path: Optional[str] = None


class FairnessSliceDelta(BaseModel):
    """Per-slice coverage comparison for fairness deltas."""

    slice_id: str
    baseline_coverage: float
    candidate_coverage: float
    delta: float
    baseline_count: int
    candidate_count: int


class FairnessDeltaCandidate(BaseModel):
    """Candidate-level fairness delta summary including veto determination."""

    name: str
    fairness_gap: float
    fairness_gap_delta: float
    worst_slice_id: Optional[str] = None
    worst_slice_delta: float
    tolerance: float
    regression: bool = False
    risk_flags: List[str] = Field(default_factory=list)
    slices: List[FairnessSliceDelta] = Field(default_factory=list)


class FairnessDeltaReceipt(BaseReceipt):
    """Receipt capturing fairness delta analysis for gatebook experiments."""

    receipt_type: str = "FairnessDeltaReceipt"
    experiment_id: Optional[str] = None
    dataset_hash: str
    baseline_candidate: str
    baseline_gap: float
    baseline_slices: List[FairnessSliceDelta] = Field(default_factory=list)
    tolerance: float
    candidates: List[FairnessDeltaCandidate] = Field(default_factory=list)
    vetoed_candidates: List[str] = Field(default_factory=list)
    parameters: Dict[str, Any] = Field(default_factory=dict)


class VectorIndexRefreshReceipt(BaseReceipt):
    """Nightly checkpoint recording semantic vector index refresh status."""

    receipt_type: str = "VectorIndexRefreshReceipt"
    domain: str = "governance"
    index_path: str
    embedding_backend: str
    vector_count: int
    persisted_vectors: int
    refresh_started_at: datetime
    refresh_finished_at: datetime
    previous_generated_at: Optional[datetime] = None
    previous_vector_count: Optional[int] = None
    stale_seconds_before: Optional[int] = None
    policy_max_age_hours: Optional[int] = None
    alerts: List[str] = Field(default_factory=list)


class PASLADetail(BaseModel):
    horizon: Literal["72h", "7d"]
    deadline: datetime
    hours_elapsed: float
    hours_remaining: float
    breached: bool


class PASLAStatus(BaseModel):
    submitted_at: Optional[datetime] = None
    evaluated_at: datetime
    expedited: PASLADetail
    standard: PASLADetail


class PAOutcomeSLA(BaseModel):
    request_ts: datetime
    decision_ts: Optional[datetime] = None
    evaluated_at: datetime
    expedited: bool = False
    clock: Literal["expedited", "standard"]
    threshold_hours: float
    deadline: datetime
    hours_elapsed: float
    hours_remaining: float
    turnaround_hours: Optional[float] = None
    sla_met: bool
    breached: bool
    pending: bool
    decision_status: Optional[str] = None


Outcome.model_rebuild()


class RWEPreregistrationReceipt(BaseReceipt):
    """Preregistration receipt specifying monitoring thresholds for a model."""

    receipt_type: str = "RWEPreregistrationReceipt"
    model_tag: str
    window_days: int
    min_n: int
    psi_threshold: float
    ece_delta_threshold: Optional[float] = None
    ece_direction: Literal["both", "worsening_only"] = "both"
    max_lookback_days_for_baseline: Optional[int] = None
    fdr_q_alpha: float = 0.10
    psi_bootstrap_resamples: int = 200
    min_consecutive_breaches: int = 2
    psi_min_support: Optional[int] = None
    pin_baseline_receipt_id: Optional[str] = None
    fdr_families: List[str] = Field(default_factory=lambda: ["psi"])
    psi_fdr_q_alpha: Optional[float] = None
    ece_fdr_q_alpha: Optional[float] = None
    min_silence_minutes: Dict[str, int] = Field(default_factory=dict)
    family_aliases: Dict[str, str] = Field(default_factory=dict)
    baseline_resolution: Literal["pinned", "latest_within_lookback"] = "latest_within_lookback"
    baseline_eval_receipt_id: Optional[str] = None
    baseline_eval_strategy: Optional[Literal["manual", "latest"]] = None
    baseline_eval_domain: Optional[str] = None
    baseline_eval_model_tag: Optional[str] = None
    created_by: str
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    seed_on_first_run: bool = False


class DriftAlarmReceipt(BaseReceipt):
    """Receipt emitted when feature drift exceeds configured thresholds."""

    receipt_type: str = "DriftAlarmReceipt"
    model_tag: str
    feature: str
    psi: float
    threshold: float
    window_start: datetime
    window_end: datetime
    cohort: Optional[str] = None
    meta: Dict[str, Any] = Field(default_factory=dict)
    fdr: Dict[str, Any] = Field(default_factory=dict)
    family: Optional[str] = None
    severity: Literal["info", "warning", "critical"] = "warning"
    first_seen_at: Optional[datetime] = None
    last_seen_at: Optional[datetime] = None
    baseline_resolution: Optional[str] = None
    baseline_receipt_id: Optional[str] = None
    ack_receipt_id: Optional[str] = None
    silenced_until: Optional[datetime] = None

    @property
    def created_at(self) -> datetime:
        return self.ts


class DataFreshnessReceipt(BaseReceipt):
    """Receipt emitted by nightly soil test monitoring data staleness."""

    receipt_type: str = "DataFreshnessReceipt"
    source: str = Field(description="Data source identifier (e.g., 'receipts', 'ncaab_features', 'telemetry_db')")
    path: str = Field(description="Absolute or relative path to the monitored data source")
    last_modified: datetime = Field(description="Timestamp of the most recent modification")
    staleness_hours: float = Field(ge=0.0, description="Hours since last modification")
    threshold_hours: float = Field(ge=0.0, description="Configured threshold for staleness (hours)")
    status: Literal["FRESH", "STALE", "CRITICAL"] = Field(description="Freshness status assessment")
    metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional source-specific metadata")

    @property
    def is_fresh(self) -> bool:
        """Check if data source is within freshness threshold."""
        return self.status == "FRESH"


class FeatureDriftContributor(BaseModel):
    """Contributor details for a feature drift breach."""

    feature: str
    psi: float
    status: Literal["warn", "refuse"]
    current_count: int
    reference_count: int
    current_quantiles: Dict[str, float] = Field(default_factory=dict)
    reference_quantiles: Dict[str, float] = Field(default_factory=dict)


class FeatureDriftReceipt(BaseReceipt):
    """Aggregated receipt documenting feature drift breaches for a domain run."""

    receipt_type: str = "FeatureDriftReceipt"
    trigger: Literal["warn", "refuse"]
    regime: str
    window_start: datetime
    window_end: datetime
    thresholds: Dict[str, float]
    total_features: int
    flagged_features: int
    contributors: List[FeatureDriftContributor] = Field(default_factory=list)
    status: Dict[str, Any] = Field(default_factory=dict)


class IncidentModeReceipt(BaseReceipt):
    """Receipt emitted when cascade risk forces a pipeline mode transition."""

    receipt_type: str = "IncidentModeReceipt"
    from_mode: str = Field(description="Previous pipeline mode before the transition")
    to_mode: str = Field(description="Pipeline mode selected after the transition")
    trigger: Literal["escalate", "recover"] = Field(
        description="Indicates whether the transition activates or clears cautious mode"
    )
    lambda_t: float = Field(description="Hawkes intensity at decision time")
    baseline_q95: Optional[float] = Field(
        default=None,
        description="95th percentile baseline intensity used for comparison",
    )
    lambda_threshold: float = Field(description="Resolved decision threshold for lambda(t)")
    exceeded_since: Optional[datetime] = Field(
        default=None, description="Timestamp when lambda first exceeded the baseline threshold"
    )
    sustained_seconds: float = Field(
        default=0.0,
        description="Number of seconds the current condition held before triggering the transition",
    )
    cooldown_seconds: float = Field(
        default=0.0,
        description="Cooldown window required before clearing cautious mode",
    )
    band_multiplier: float = Field(
        default=1.0,
        description="Multiplier applied to calibration bands when cautious mode is active",
    )
    refusal_budget_lift: float = Field(
        default=0.0,
        description="Absolute lift applied to the refusal budget while in cautious mode",
    )
    event_counts: Dict[str, int] = Field(
        default_factory=dict,
        description="Recent incident counts grouped by event label within the Hawkes window",
    )


class AlarmAcknowledgementReceipt(BaseReceipt):
    """Receipt emitted when an operator acknowledges an active alarm."""

    receipt_type: str = "AlarmAcknowledgementReceipt"
    alarm_receipt_id: str
    family: str
    key: str
    ack_by: str
    org: Optional[str] = None
    role: Optional[str] = None
    ack_reason: Optional[str] = None
    ack_ttl_minutes: int
    expires_at: datetime
    policy_context: Dict[str, Any] = Field(default_factory=dict)

    @property
    def created_at(self) -> datetime:
        return self.ts


class CohortAlertWorkflowReceipt(BaseReceipt):
    """Receipt recording operator actions taken on LOS cohort alerts."""

    receipt_type: str = "CohortAlertWorkflowReceipt"
    cohort_id: str = Field(description="Logical cohort identifier associated with the alert")
    alert_receipt_id: Optional[str] = Field(
        default=None,
        description="Originating CohortAlert receipt identifier, when available",
    )
    action: Literal["ack", "snooze", "clear"] = Field(description="Workflow action applied to the alert")
    ack_by: str = Field(description="Operator identifier responsible for the action")
    ack_until: datetime = Field(description="Timestamp when the acknowledgement expires")
    ack_ttl_minutes: int = Field(description="Duration of the acknowledgement window in minutes", ge=0)
    reason: Optional[str] = Field(
        default=None,
        description="Optional runbook reference or justification supplied by the operator",
    )
    actor_metadata: Dict[str, Any] = Field(
        default_factory=dict,
        description="Minimal metadata about the actor (role, tenant, key fingerprint)",
    )

    @property
    def created_at(self) -> datetime:
        return self.ts


class CohortAutonomyChangeReceipt(BaseReceipt):
    """Receipt recording manual cohort autonomy freezes/unfreezes."""

    receipt_type: str = "CohortAutonomyChangeReceipt"
    cohort_id: str = Field(description="Cohort identifier whose autonomy state changed")
    action: Literal["freeze", "unfreeze"] = Field(description="Autonomy transition that occurred")
    requested_by: str = Field(description="Operator identifier initiating the change")
    reason: Optional[str] = Field(
        default=None,
        description="Optional justification or runbook reference provided by the operator",
    )
    expires_at: Optional[datetime] = Field(
        default=None,
        description="When the freeze automatically lifts (None for unfreeze events)",
    )
    ttl_minutes: Optional[int] = Field(
        default=None,
        ge=0,
        description="Duration in minutes that autonomy remains frozen (if applicable)",
    )
    previous_expires_at: Optional[datetime] = Field(
        default=None,
        description="Prior freeze expiry before the change was applied, if any",
    )
    actor_metadata: Dict[str, Any] = Field(
        default_factory=dict,
        description="Minimal metadata snapshot about the operator (role, tenant, key fingerprint, IP)",
    )

    @property
    def created_at(self) -> datetime:
        return self.ts


class OncallPageReceipt(BaseReceipt):
    """Receipt documenting on-call escalation triggered by cohort monitoring rules."""

    receipt_type: str = "OncallPageReceipt"
    cohort_id: str = Field(description="Cohort identifier that triggered the page")
    count_24h: int = Field(ge=0, description="Number of high-severity alerts observed within the lookback window")
    last_alert_id: Optional[str] = Field(default=None, description="Most recent alert receipt identifier")
    rule: str = Field(default="repeated_high_severity", description="Pager rule that produced the escalation")
    window_hours: float = Field(default=24.0, description="Lookback window used when evaluating the rule")
    cooldown_minutes: float = Field(default=60.0, description="Cooldown applied between successive pages for the cohort")

    @property
    def created_at(self) -> datetime:
        return self.ts


class EvidenceReleaseApprovalReceipt(BaseReceipt):
    """Receipt recording approvals for evidence release under dual control."""

    receipt_type: str = "EvidenceReleaseApprovalReceipt"
    artifact_id: str
    approver_id: str
    quorum_state: Literal["pending", "granted", "revoked"] = "pending"
    approved_count: int = 0
    approver_org: Optional[str] = None
    approver_role: Optional[str] = None
    approver_fingerprint: Optional[str] = None
    policy_window_days: int = 0
    required_quorum: int = 2

    @property
    def created_at(self) -> datetime:
        return self.ts


class PhaseStone(BaseReceipt):
    """Promotion receipt minted when a model satisfies all promotion gates."""

    receipt_type: str = "PhaseStone"
    promotion_tag: str = Field(description="Git tag or release identifier that triggered the promotion workflow.")
    promotion_domain: str = Field(description="Domain or line of business targeted for promotion (e.g., 'pa').")
    eval_suite_id: Optional[str] = Field(default=None, description="Identifier for the evaluation suite applied to this promotion.")
    artifacts: Dict[str, str] = Field(
        default_factory=dict,
        description="Artifact digests for the promotion tuple (model/policy/features).",
    )
    metrics: Dict[str, float] = Field(
        default_factory=dict,
        description="Observed evaluation metrics keyed by metric identifier.",
    )
    thresholds: Dict[str, float] = Field(
        default_factory=dict,
        description="Threshold values enforced during promotion evaluation.",
    )
    replay_command: Optional[str] = Field(
        default=None,
        description="CLI or script invocation that can replay the evaluation for this promotion.",
    )
    replay_args: List[str] = Field(
        default_factory=list,
        description="Arguments for the replay command.",
    )
    gate_results: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Verbose gate evaluation results (metric, operator, threshold, observed, passed).",
    )
    evaluation_receipt_ids: List[str] = Field(
        default_factory=list,
        description="Identifiers of EvaluationReceipt instances referenced for this promotion.",
    )
    promotion_config_path: Optional[str] = Field(
        default=None,
        description="Relative path to the promotion configuration YAML applied.",
    )
    passed: bool = Field(default=False, description="True when all promotion gates passed.")
    notes: List[str] = Field(default_factory=list, description="Optional operator annotations.")
    anchor: Optional[ReceiptAnchor] = Field(
        default=None,
        description="Sigstore Rekor anchoring metadata for the promotion receipt.",
    )


class CounterfactualReportReceipt(BaseReceipt):
    """Signed counterfactual replay report summarising policy overrides and outcome deltas."""

    receipt_type: str = "CounterfactualReportReceipt"
    scenario: str = Field(description="Human-readable label for the counterfactual scenario.")
    window_start: datetime = Field(description="Inclusive start of the replay window.")
    window_end: datetime = Field(description="Inclusive end of the replay window.")
    overrides: Dict[str, Any] = Field(
        default_factory=dict,
        description="Policy overrides applied during the counterfactual simulation.",
    )
    baseline_metrics: Dict[str, float] = Field(
        default_factory=dict,
        description="Baseline metrics captured before applying overrides.",
    )
    counterfactual_metrics: Dict[str, float] = Field(
        default_factory=dict,
        description="Metrics recomputed under the counterfactual overrides.",
    )
    deltas: Dict[str, float] = Field(
        default_factory=dict,
        description="Metric deltas (counterfactual - baseline).",
    )
    sample_size: int = Field(description="Number of samples evaluated in the simulation window.")
    min_sample_required: Optional[int] = Field(
        default=None, description="Minimum sample size required to consider the replay valid."
    )
    verdict: Literal["pass", "fail"] = Field(
        description="Verdict indicating whether the counterfactual meets the acceptance criteria."
    )
    saved_ev: Optional[float] = Field(
        default=None,
        description="Counterfactual saved expected value attributed to policy changes.",
    )
    simulation_hash: Optional[str] = Field(
        default=None,
        description="SHA-256 hash for the counterfactual input corpus (for reproducibility).",
    )
    notes: List[str] = Field(default_factory=list, description="Additional operator notes or caveats.")


class ReflectionReceipt(BaseReceipt):
    """Weekly reflection summarising refusal motifs and calibration trends."""

    receipt_type: str = "ReflectionReceipt"
    domain: str
    cohort: str = Field(default="global", description="Cohort identifier used for the reflection roll-up.")
    window_start: datetime = Field(description="Start of the reflection window (inclusive).")
    window_end: datetime = Field(description="End of the reflection window (exclusive).")
    refusal_motifs: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Top refusal motifs observed during the window with support counts.",
    )
    metrics_summary: Dict[str, Any] = Field(
        default_factory=dict,
        description="Key metric summaries (ECE trend, abstain rate, refusal rate).",
    )
    reflection_summary: str = Field(
        default="",
        description="Human-readable reflection narrative for operators.",
    )
    source_receipt_ids: List[str] = Field(
        default_factory=list,
        description="Receipt identifiers contributing to this reflection.",
    )


class AutoPromotionRecommendationReceipt(BaseReceipt):
    """Recommendation receipt emitted when a candidate model wins A/B vs baseline.

    Captures key metric deltas and p-values with a boolean recommendation flag.
    """

    receipt_type: str = "AutoPromotionRecommendationReceipt"
    domain: str
    baseline_tag: str = Field(description="Baseline model tag")
    candidate_tag: str = Field(description="Candidate model tag")
    window_since: str = Field(description="Start date (YYYY-MM-DD)")
    window_until: str = Field(description="End date (YYYY-MM-DD)")
    promote: bool = Field(description="True when recommendation is to promote the candidate")
    rationale: List[str] = Field(default_factory=list, description="Reasons supporting the decision")
    metrics_before: Dict[str, Any] = Field(default_factory=dict, description="Baseline metric point estimates")
    metrics_after: Dict[str, Any] = Field(default_factory=dict, description="Candidate metric point estimates")
    bootstrap: Dict[str, Any] = Field(default_factory=dict, description="Bootstrap deltas and p-values by metric")
    pr_branch: Optional[str] = Field(default=None, description="Draft PR branch name when a PR was prepared")
    gatebook_path: Optional[str] = Field(default=None, description="Path of gatebook file updated in the PR")


class IdempotencyReceipt(BaseReceipt):
    """Receipt recording the outcome of an idempotent API request."""

    receipt_type: str = "IdempotencyReceipt"
    actor: str = Field(description="Principal identifier (API key) responsible for the request")
    route: str = Field(description="API route identifier or path")
    idempotency_key: str = Field(description="Client-supplied Idempotency-Key header")
    request_hash: str = Field(description="SHA-256 hash of the canonical request payload")
    response_hash: str = Field(description="SHA-256 hash of the canonical response payload")
    status_code: int = Field(description="HTTP status code returned to the client")
    cached: bool = Field(default=False, description="True when the response was served from cache")


class MigrationReceipt(BaseReceipt):
    """Receipt documenting a zero-downtime ledger schema migration."""

    receipt_type: str = "MigrationReceipt"
    migration_id: str = Field(description="Logical identifier for the migration run")
    operator: str = Field(description="Operator or automation executing the migration")
    source_schema: str = Field(description="Source schema version before migration")
    target_schema: str = Field(description="Target schema version after migration")
    phases: List[str] = Field(default_factory=list, description="Ordered phases executed during the migration")
    row_counts: Dict[str, int] = Field(default_factory=dict, description="Row counts captured for source/target datasets")
    digest_checks: Dict[str, str] = Field(default_factory=dict, description="Digest artifacts validating the migration")
    diff_summary: Dict[str, Any] = Field(default_factory=dict, description="High-level diff summary between source and target snapshots")
    diff_report_path: Optional[str] = Field(default=None, description="Filesystem path to the detailed diff report")
    backfilled: bool = Field(default=False, description="True when a backfill populated the target dataset")
    validation_passed: bool = Field(default=False, description="True when diff validation detected no issues")


class IndexMigrationReceipt(BaseReceipt):
    """Receipt documenting applied migrations to the receipt index store."""

    receipt_type: str = "IndexMigrationReceipt"
    domain: str = Field(
        default="observability",
        description="Operational domain emitting the migration receipt",
    )
    migration_id: str = Field(description="Logical identifier for the applied index migration.")
    sql_applied: List[str] = Field(
        default_factory=list,
        description="DDL statements executed while migrating the receipt index.",
    )
    ts_started: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="Timestamp when the migration run started.",
    )
    ts_finished: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="Timestamp when the migration run finished.",
    )
    result: Literal["applied", "noop", "failed"] = Field(
        default="applied",
        description="Outcome of the migration attempt.",
    )


class CryptoSchemaChangeReceipt(BaseReceipt):
    """Receipt documenting cryptographic envelope schema migrations."""

    receipt_type: str = "CryptoSchemaChangeReceipt"
    change_id: str = Field(description="Identifier for the schema migration rollout.")
    legacy_fields: List[str] = Field(
        default_factory=list,
        description="Signature-related field names replaced by the migration (e.g., ['signature']).",
    )
    new_fields: List[str] = Field(
        default_factory=list,
        description="Signature-related field names introduced by the migration (e.g., ['signatures']).",
    )
    affected_receipt_types: List[str] = Field(
        default_factory=list,
        description="Receipt type identifiers impacted by the schema change.",
    )
    jwks_kids: List[str] = Field(
        default_factory=list,
        description="JWKS key IDs that signed receipts during the migration window.",
    )
    rollout_started_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="UTC timestamp when the rollout began.",
    )
    rollout_completed_at: Optional[datetime] = Field(
        default=None,
        description="UTC timestamp when the rollout completed.",
    )
    notes: Optional[str] = Field(
        default=None,
        description="Optional free-form summary of the migration scope and validation steps.",
    )


class IntegrityTestReceipt(BaseReceipt):
    """Receipt capturing property-based integrity corpus metadata."""

    receipt_type: str = "IntegrityTestReceipt"
    domain: str = Field(default="qa", description="Synthetic domain for integrity harness outputs.")
    suite: str = Field(description="Identifier for the integrity test suite emitting the receipt.")
    hypothesis_seed: str = Field(description="Seed used by Hypothesis for reproducibility.")
    examples_run: int = Field(ge=0, description="Total number of generated examples executed.")
    examples_failed: int = Field(ge=0, description="Number of failing examples observed.")
    jcs_version: str = Field(default="RFC8785", description="Canonicalization standard used during the run.")
    commit_sha: Optional[str] = Field(default=None, description="Repository commit SHA for the run.")
    notes: Optional[str] = Field(default=None, description="Optional free-form notes about the run.")


class SenseReference(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    id: str
    version: str
    registry_ref: Optional[str] = None


class SenseIngestionWindow(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    start: datetime
    end: datetime


class SenseRawDigest(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    alg: Literal["sha256"]
    value: str = Field(pattern="^[A-Fa-f0-9]{64}$")


class SenseIngestion(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    sources: List[str] = Field(default_factory=list)
    window: SenseIngestionWindow
    sample_count: Optional[int] = Field(default=None, ge=0)
    bytes: Optional[int] = Field(default=None, ge=0)
    raw_digest: Optional[SenseRawDigest] = None


class SenseFeatures(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    feature_digest: Optional[SenseRawDigest] = None
    summary: Dict[str, Any] = Field(default_factory=dict)


class SenseInferenceModel(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    name: str
    version: str


class SenseInference(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    models: List[SenseInferenceModel] = Field(default_factory=list)
    outputs: Dict[str, Any] = Field(default_factory=dict)


class SenseCoherenceRater(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    agent: str
    score: float
    weight: Optional[float] = None


class SenseCoherence(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    total: float
    gated: Optional[float] = None
    entropy: Optional[float] = None
    raters: List[SenseCoherenceRater] = Field(default_factory=list)


class SenseSemantics(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    coherence: SenseCoherence


class SenseViolation(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    facet: str
    message: str


class SenseEthics(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    dignity_floor: float = Field(ge=0, le=1)
    dignity_current: Optional[float] = Field(default=None, ge=0, le=1)
    violations: List[SenseViolation] = Field(default_factory=list)


class SenseProofSignature(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    alg: Literal["Ed25519"]
    public_key: str
    value: str
    created_at: datetime


class SenseProofContentHash(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    alg: Literal["sha256"]
    value: str = Field(pattern="^[A-Fa-f0-9]{64}$")


class SenseProof(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    canonicalization: Optional[Literal["JCS-RFC8785"]] = None
    content_hash: Optional[SenseProofContentHash] = None
    parent_ids: List[str] = Field(default_factory=list)
    parent_hashes: List[str] = Field(default_factory=list)
    signature: Optional[SenseProofSignature] = None
    tsa_token: Optional[str] = None
    rekor_log_index: Optional[int] = Field(default=None, ge=0)


class SenseSampleReceipt(BaseReceipt):
    model_config = ConfigDictLike(populate_by_name=True, frozen=True)
    receipt_type: str = "SenseSampleReceipt"
    domain: str = Field(default="governance")
    type_: Literal["SenseSampleReceipt"] = Field(
        default="SenseSampleReceipt", alias="type", serialization_alias="type"
    )
    version: str
    created_at: datetime
    subject: str
    traceparent: Optional[str] = None
    sense: SenseReference
    ingestion: SenseIngestion
    features: SenseFeatures
    inference: SenseInference = Field(default_factory=SenseInference)
    semantics: SenseSemantics
    ethics: SenseEthics
    proof: Optional[SenseProof] = None

    @property
    def type(self) -> str:  # pragma: no cover - convenience alias
        return self.type_


class SenseDecision(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    action: Literal["allow", "deny", "override"]
    reason: Optional[str] = None


class SenseGuardianViolation(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    policy_id: str
    message: str


class SenseGuardian(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    policies_evaluated: List[str]
    violations: List[SenseGuardianViolation] = Field(default_factory=list)
    scores: Dict[str, float] = Field(default_factory=dict)
    dignity_floor: float = Field(ge=0, le=1)
    dignity_current: Optional[float] = Field(default=None, ge=0, le=1)


class SenseOverride(BaseModel):
    model_config = ConfigDictLike(frozen=True, extra="forbid")
    intent: Optional[str] = None
    queued: Optional[bool] = None
    approved_by: Optional[str] = None
    quality_score: Optional[float] = None


class SenseDecisionReceipt(BaseReceipt):
    model_config = ConfigDictLike(populate_by_name=True, frozen=True)
    receipt_type: str = "SenseDecisionReceipt"
    domain: str = Field(default="governance")
    type_: Literal["SenseDecisionReceipt"] = Field(
        default="SenseDecisionReceipt", alias="type", serialization_alias="type"
    )
    version: str
    created_at: datetime
    subject: str
    traceparent: Optional[str] = None
    sense: SenseReference
    sample_receipt_id: Optional[str] = None
    decision: SenseDecision
    guardian: SenseGuardian
    override: Optional[SenseOverride] = None
    proof: Optional[SenseProof] = None

    @property
    def type(self) -> str:  # pragma: no cover - convenience alias
        return self.type_


def _discover_receipt_models() -> Dict[str, Type["BaseReceipt"]]:
    registry: Dict[str, Type["BaseReceipt"]] = {}
    for candidate in globals().values():
        if isinstance(candidate, type) and issubclass(candidate, BaseReceipt) and candidate is not BaseReceipt:
            type_name = getattr(candidate, "receipt_type", candidate.__name__)
            registry[type_name] = candidate
    return registry


RECEIPT_MODEL_REGISTRY: Dict[str, Type["BaseReceipt"]] = _discover_receipt_models()

# Register receipt types from other modules (avoid circular imports)
def _register_external_receipts() -> None:
    """Register receipt types defined in other modules."""
    try:
        from receipts.missing_receipts import (
            AuditReceipt,
            ConformalCoverageReceipt,
            CompostStone,
            PolicyReceipt,
        )
        RECEIPT_MODEL_REGISTRY["AuditReceipt"] = AuditReceipt
        RECEIPT_MODEL_REGISTRY["ConformalCoverageReceipt"] = ConformalCoverageReceipt
        RECEIPT_MODEL_REGISTRY["CompostStone"] = CompostStone
        RECEIPT_MODEL_REGISTRY["PolicyReceipt"] = PolicyReceipt
    except ImportError:
        pass  # Module not available, skip registration

    # CRIF/Metabolic receipts (non-invasive, cross-domain analytics)
    try:
        from receipts.crif import CRIFStone, MetabolicPhaseReceipt

        RECEIPT_MODEL_REGISTRY["CRIFStone"] = CRIFStone
        RECEIPT_MODEL_REGISTRY["MetabolicPhaseReceipt"] = MetabolicPhaseReceipt
    except ImportError:
        pass

_register_external_receipts()


def _build_type_aliases() -> Dict[str, str]:
    base_aliases = {
        "Prophecy": "ProphecyStone",
        "Refusal": "RefusalStone",
        "Outcome": "OutcomeReceipt",
        "DIP": "DIPReceipt",
        "Silence": "SilenceReceipt",
    }
    aliases: Dict[str, str] = {}
    for alias, target in base_aliases.items():
        aliases[alias] = target
        aliases[alias.lower()] = target
        aliases[target] = target
        aliases[target.lower()] = target
    for target in RECEIPT_MODEL_REGISTRY.keys():
        aliases.setdefault(target, target)
        aliases.setdefault(target.lower(), target)
    return aliases


_LEGACY_TYPE_ALIASES = _build_type_aliases()


def _canonical_type_name(raw: Optional[str]) -> Optional[str]:
    if raw is None:
        return None
    token = str(raw).strip()
    if not token:
        return None
    direct = _LEGACY_TYPE_ALIASES.get(token)
    if direct:
        return direct
    lower = token.lower()
    direct = _LEGACY_TYPE_ALIASES.get(lower)
    if direct:
        return direct
    return token


def _is_v1_version(token: Any) -> bool:
    if token is None:
        return False
    if isinstance(token, (int, float)):
        return int(token) == 1
    if isinstance(token, str):
        candidate = token.strip().lower()
        if not candidate:
            return False
        if candidate.startswith("v"):
            candidate = candidate[1:]
        return candidate.startswith("1")
    return False


def _as_mapping(value: Any) -> Dict[str, Any]:
    if isinstance(value, Mapping):
        return dict(value)
    return {}


def _first_not_none(keys: Sequence[str], sources: Iterable[Mapping[str, Any]]) -> Optional[Any]:
    for source in sources:
        if not isinstance(source, Mapping):
            continue
        for key in keys:
            if key in source and source[key] is not None:
                return source[key]
    return None


def _stash_legacy_metadata(target: Dict[str, Any], **entries: Any) -> None:
    cleaned = {key: value for key, value in entries.items() if value is not None}
    if not cleaned:
        return
    context: MutableMapping[str, Any]
    context_raw = target.get("context")
    if isinstance(context_raw, MutableMapping):
        context = dict(context_raw)
    else:
        context = {}
    legacy_raw = context.get("legacy")
    if isinstance(legacy_raw, MutableMapping):
        legacy = dict(legacy_raw)
    else:
        legacy = {}
    legacy.update(cleaned)
    legacy_rest = {key: value for key, value in legacy.items() if key != "signature"}
    if legacy_rest:
        context["legacy"] = legacy_rest
        target["context"] = context
        return
    context.pop("legacy", None)
    if context:
        target["context"] = context
    else:
        target.pop("context", None)


def _enrich_legacy_prophecy(
    target: Dict[str, Any],
    metrics: Mapping[str, Any],
    original: Mapping[str, Any],
) -> None:
    sources: List[Mapping[str, Any]] = [
        target,
        metrics,
        original,
    ]
    pred_prob = _first_not_none(
        ["pred_prob", "calibrated_prob", "p_calibrated", "p_win_calibrated"],
        sources,
    )
    if pred_prob is None:
        pred_prob = 0.5
    target["pred_prob"] = pred_prob

    ci_lo = _first_not_none(
        ["ci90_lo", "ci_low", "ci_lo", "ci_lower"],
        sources,
    )
    if ci_lo is None:
        try:
            ci_lo = max(0.0, float(pred_prob) - 0.1)  # type: ignore[arg-type]
        except Exception:
            ci_lo = 0.0
    target["ci90_lo"] = ci_lo

    ci_hi = _first_not_none(
        ["ci90_hi", "ci_high", "ci_hi", "ci_upper"],
        sources,
    )
    if ci_hi is None:
        try:
            ci_hi = min(1.0, float(pred_prob) + 0.1)  # type: ignore[arg-type]
        except Exception:
            ci_hi = 1.0
    target["ci90_hi"] = ci_hi

    ci_method = _first_not_none(
        ["ci90_method", "ci_method"],
        sources,
    )
    if ci_method is not None:
        target["ci90_method"] = ci_method

    ci_fallback = _first_not_none(
        ["ci90_fallback_reason", "ci_fallback_reason"],
        sources,
    )
    if ci_fallback is not None:
        target["ci90_fallback_reason"] = ci_fallback

    ci_mode = _first_not_none(["ci_fallback_mode"], sources)
    if ci_mode is not None:
        target["ci_fallback_mode"] = ci_mode

    dwf_val = _first_not_none(["dwf", "dwf_mean"], sources)
    if dwf_val is not None:
        target["dwf"] = dwf_val

    p_raw_val = _first_not_none(["p_hat_raw", "p_raw", "p_win_raw"], sources)
    if p_raw_val is not None:
        target["p_hat_raw"] = p_raw_val

    reasons = original.get("reasons")
    if isinstance(reasons, str):
        reasons = [reasons]
    if isinstance(reasons, Sequence):
        target["reasons"] = [str(reason) for reason in reasons if reason]
    target.setdefault("reasons", [])

    gate_block = original.get("gates") or metrics.get("gates")
    if isinstance(gate_block, Mapping):
        gate_status_block: Dict[str, Any] = {
            "passed": bool(gate_block.get("passed", False)),
            "ece_worst": gate_block.get("ece_worst"),
            "coverage_90": gate_block.get("coverage_90"),
            "evhd_spearman": gate_block.get("evhd_spearman"),
            "evhd_n_bins": gate_block.get("evhd_n_bins"),
            "min_per_decile": gate_block.get("min_per_decile"),
            "evhd_support": gate_block.get("evhd_support"),
        }
        evhd_payload = gate_block.get("evhd")
        # Default nested block from legacy scalar fields when explicit payload missing.
        if isinstance(evhd_payload, Mapping):
            gate_status_block["evhd"] = {
                "rho": evhd_payload.get("rho"),
                "n_bins": evhd_payload.get("n_bins"),
                "min_support": evhd_payload.get("min_support"),
                "support": evhd_payload.get("support"),
                "min_rho": evhd_payload.get("min_rho"),
                "passed": bool(evhd_payload.get("passed", False)),
            }
        else:
            gate_status_block["evhd"] = {
                "rho": gate_block.get("evhd_spearman"),
                "n_bins": gate_block.get("evhd_n_bins"),
                "min_support": gate_block.get("min_per_decile"),
                "support": gate_block.get("evhd_support"),
                "min_rho": gate_block.get("evhd_floor"),
                "passed": bool(gate_block.get("evhd_passed", gate_block.get("passed", False))),
            }
        budget_payload = gate_block.get("refusal_budget")
        if isinstance(budget_payload, Mapping):
            gate_status_block["refusal_budget"] = dict(budget_payload)
        target["gate_status"] = gate_status_block

    target.setdefault("metrics", dict(metrics))


def _enrich_legacy_refusal(
    target: Dict[str, Any],
    metrics: Mapping[str, Any],
    original: Mapping[str, Any],
) -> None:
    reasons = original.get("reasons") or metrics.get("reasons")
    if isinstance(reasons, str):
        reasons = [reasons]
    if isinstance(reasons, Sequence):
        target["reasons"] = [str(reason) for reason in reasons if reason]
    if not target.get("reasons"):
        target["reasons"] = ["legacy_refusal"]

    failures = original.get("gate_failures") or metrics.get("gate_failures")
    if isinstance(failures, Mapping):
        target["gate_failures"] = dict(failures)
    else:
        target.setdefault("gate_failures", {})

    target.setdefault("metrics", dict(metrics))


def _normalize_legacy_payload(payload: Mapping[str, Any]) -> Dict[str, Any]:
    working: Dict[str, Any] = {
        key: value
        for key, value in payload.items()
        if key not in {"version"}
    }
    raw_type = working.pop("type", None) or working.get("receipt_type")
    canonical_type = _canonical_type_name(raw_type)
    if canonical_type:
        working["receipt_type"] = canonical_type

    parents = working.pop("parents", None)
    if isinstance(parents, Sequence) and not isinstance(parents, (str, bytes)):
        parent_list = list(parents)
        if parent_list:
            working.setdefault("parent_id", parent_list[0])
        working["parent_ids"] = parent_list
    elif isinstance(parents, str):
        working["parent_id"] = parents
        working["parent_ids"] = [parents]

    metrics = _as_mapping(payload.get("metrics"))
    if metrics and "metrics" not in working:
        working["metrics"] = metrics

    if canonical_type == "ProphecyStone":
        _enrich_legacy_prophecy(working, metrics, payload)
    elif canonical_type == "RefusalStone":
        _enrich_legacy_refusal(working, metrics, payload)

    gate_block = payload.get("gates")
    if isinstance(gate_block, Mapping) and "gate_status" not in working:
        gate_status_block: Dict[str, Any] = {
            "passed": bool(gate_block.get("passed", False)),
            "ece_worst": gate_block.get("ece_worst"),
            "coverage_90": gate_block.get("coverage_90"),
            "evhd_spearman": gate_block.get("evhd_spearman"),
            "evhd_n_bins": gate_block.get("evhd_n_bins"),
            "min_per_decile": gate_block.get("min_per_decile"),
            "evhd_support": gate_block.get("evhd_support"),
        }
        evhd_payload = gate_block.get("evhd")
        if isinstance(evhd_payload, Mapping):
            gate_status_block["evhd"] = {
                "rho": evhd_payload.get("rho"),
                "n_bins": evhd_payload.get("n_bins"),
                "min_support": evhd_payload.get("min_support"),
                "support": evhd_payload.get("support"),
                "min_rho": evhd_payload.get("min_rho"),
                "passed": bool(evhd_payload.get("passed", False)),
            }
        else:
            gate_status_block["evhd"] = {
                "rho": gate_block.get("evhd_spearman"),
                "n_bins": gate_block.get("evhd_n_bins"),
                "min_support": gate_block.get("min_per_decile"),
                "support": gate_block.get("evhd_support"),
                "min_rho": gate_block.get("evhd_floor"),
                "passed": bool(gate_block.get("evhd_passed", gate_block.get("passed", False))),
            }
        budget_payload = gate_block.get("refusal_budget")
        if isinstance(budget_payload, Mapping):
            gate_status_block["refusal_budget"] = dict(budget_payload)
        working["gate_status"] = gate_status_block

    signature = payload.get("signature")
    if isinstance(signature, Mapping):
        _stash_legacy_metadata(working, signature=dict(signature))

    original_version = payload.get("schema_version") or payload.get("version")
    if original_version is None:
        original_version = "v1"
    _stash_legacy_metadata(working, legacy_schema_version=original_version)

    working["schema_version"] = SCHEMA_VERSION
    working.setdefault("mode", payload.get("mode", "LIVE"))
    return working


def _normalize_current_payload(payload: Mapping[str, Any]) -> Dict[str, Any]:
    working = dict(payload)
    raw_type = working.get("receipt_type") or working.pop("type", None)
    canonical_type = _canonical_type_name(raw_type)
    if canonical_type:
        working["receipt_type"] = canonical_type
    working["schema_version"] = SCHEMA_VERSION
    return working


def _resolve_receipt_model(type_name: str) -> Type["BaseReceipt"]:
    canonical = _canonical_type_name(type_name)
    if canonical and canonical in RECEIPT_MODEL_REGISTRY:
        return RECEIPT_MODEL_REGISTRY[canonical]
    raise ValueError(f"unknown receipt_type '{type_name}'")


def _absorb_extras(
    payload: Dict[str, Any],
    model_cls: Type["BaseReceipt"],
    original: Mapping[str, Any],
) -> Dict[str, Any]:
    known_fields: Set[str] = set()
    for field_name, field in model_cls.model_fields.items():
        known_fields.add(field_name)
        alias = getattr(field, "alias", None)
        if alias:
            known_fields.add(str(alias))
        serial_alias = getattr(field, "serialization_alias", None)
        if serial_alias:
            known_fields.add(str(serial_alias))
        validation_alias = getattr(field, "validation_alias", None)
        choices = getattr(validation_alias, "choices", None)
        if choices:
            known_fields.update(str(choice) for choice in choices if choice)
        elif validation_alias:
            known_fields.add(str(validation_alias))
    extras: Dict[str, Any] = {}
    for key in list(payload.keys()):
        if key not in known_fields:
            extras[key] = payload.pop(key)
    if extras:
        _stash_legacy_metadata(payload, extras=extras)
    signature = original.get("signature")
    if isinstance(signature, Mapping):
        _stash_legacy_metadata(payload, signature=dict(signature))
    return payload


def load_receipt(payload: Mapping[str, Any]) -> BaseReceipt:
    """
    Load a receipt payload, upgrading legacy structures to the current schema.

    Args:
        payload: Mapping representing a receipt JSON.

    Returns:
        Parsed BaseReceipt subclass for the receipt type.
    """
    if not isinstance(payload, Mapping):
        raise TypeError("load_receipt expects a mapping payload")
    version_token = payload.get("schema_version") or payload.get("version")
    is_legacy = _is_v1_version(version_token) or (
        "type" in payload and "receipt_type" not in payload
    )
    normalised = (
        _normalize_legacy_payload(payload)
        if is_legacy
        else _normalize_current_payload(payload)
    )
    receipt_type = normalised.get("receipt_type")
    if not isinstance(receipt_type, str):
        raise ValueError("receipt payload missing 'receipt_type'")
    model_cls = _resolve_receipt_model(receipt_type)
    prepared = _absorb_extras(normalised, model_cls, payload)
    return model_cls.model_validate(prepared)


def write_receipt(obj: Union[BaseReceipt, Any], out_dir: Union[str, Path] = "receipts/ledger") -> Path:
    """Persist a receipt object to canonical JSON and return the written path."""

    if not isinstance(obj, BaseReceipt):
        raise TypeError("write_receipt expects a BaseReceipt instance.")

    payload = _canonicalize(obj.model_dump(by_alias=True))
    output_dir = Path(out_dir)
    output_dir.mkdir(parents=True, exist_ok=True)

    safe_type = re.sub(r"[^A-Za-z0-9_.-]", "_", obj.type)
    receipt_id = re.sub(r"[^A-Za-z0-9_.-]", "_", obj.receipt_id)
    filename = f"{safe_type}-{receipt_id}.json"
    destination = output_dir / filename

    with destination.open("w", encoding="utf-8") as handle:
        json.dump(payload, handle, ensure_ascii=True, separators=(",", ":"), sort_keys=True)

    return destination


def _canonicalize(value: Any) -> Any:
    """Recursively sort dictionaries and normalise datetimes for deterministic JSON."""
    if isinstance(value, FrozenDict):
        value = dict(value)
    if isinstance(value, FrozenList):
        value = list(value)
    if isinstance(value, dict):
        return {key: _canonicalize(value[key]) for key in sorted(value)}
    if isinstance(value, list):
        return [_canonicalize(item) for item in value]
    if isinstance(value, tuple):
        return tuple(_canonicalize(item) for item in value)
    if isinstance(value, datetime):
        ts = value
        if ts.tzinfo is None:
            ts = ts.replace(tzinfo=timezone.utc)
        else:
            ts = ts.astimezone(timezone.utc)
        return ts.isoformat().replace("+00:00", "Z")
    if isinstance(value, (np.bool_, bool)):
        return value
    if isinstance(value, (np.floating, float)):
        if np.isnan(value):
            return None
        return float(value)
    if isinstance(value, (np.integer, int)):
        return int(value)
    if value is None:
        return None
    if hasattr(value, "item"):
        try:
            extracted = value.item()
            return _canonicalize(extracted)
        except Exception:
            return None
    if hasattr(value, "model_dump"):
        return _canonicalize(value.model_dump())
    return value


HASH_EXCLUDE_FIELDS = frozenset({"receipt_hash", "signature", "signatures", "verifier_signatures"})


def _strip_signature_metadata(payload: Dict[str, Any]) -> None:
    context_raw = payload.get("context")
    if isinstance(context_raw, FrozenDict):
        context: Dict[str, Any] = dict(context_raw)
    elif isinstance(context_raw, dict):
        context = context_raw
    else:
        return
    if not context:
        payload.pop("context", None)
        return
    legacy_raw = context.get("legacy")
    if isinstance(legacy_raw, FrozenDict):
        legacy: Dict[str, Any] = dict(legacy_raw)
    elif isinstance(legacy_raw, dict):
        legacy = legacy_raw
    else:
        payload["context"] = context
        return
    if "signature" not in legacy:
        context["legacy"] = legacy
        payload["context"] = context
        return
    remaining = {key: value for key, value in legacy.items() if key != "signature"}
    if remaining:
        context["legacy"] = remaining
        payload["context"] = context
        return
    context.pop("legacy", None)
    if context:
        payload["context"] = context
    else:
        payload.pop("context", None)


def canonical_body_bytes(
    receipt: BaseReceipt,
    exclude_fields: Optional[Iterable[str]] = None,
    *,
    strip_signature_metadata: bool = False,
) -> bytes:
    """
    Return RFC 8785 canonical JSON bytes for a receipt, omitting excluded fields.

    Args:
        receipt: Receipt instance to canonicalise.
        exclude_fields: Optional iterable of additional field names to exclude.
    """
    excluded = set(HASH_EXCLUDE_FIELDS)
    if exclude_fields:
        excluded.update(str(field) for field in exclude_fields)
    data = receipt.model_dump(by_alias=True, exclude=excluded)
    data = unwrap_frozen(data)
    if strip_signature_metadata:
        _strip_signature_metadata(data)
    canonical = _canonicalize(data)
    return proofkit_module.canonicalize_json(canonical)


def compute_hash(receipt: BaseReceipt, exclude_fields: Optional[List[str]] = None) -> str:
    """
    Compute deterministic SHA-256 hash of receipt content.

    Args:
        receipt: Receipt to hash
        exclude_fields: Fields to exclude from hash computation (e.g., 'receipt_hash' itself)

    Returns:
        Hex-encoded SHA-256 hash string
    """
    json_bytes = canonical_body_bytes(receipt, exclude_fields=exclude_fields)
    return hashlib.sha256(json_bytes).hexdigest()


def compute_payload_hash(payload: Mapping[str, Any], exclude_fields: Optional[Iterable[str]] = None) -> str:
    """
    Compute the canonical body hash for an arbitrary receipt payload mapping.

    Args:
        payload: Mapping containing receipt fields (typically loaded from canonical JSON).
        exclude_fields: Optional iterable of additional fields to omit before hashing.

    Returns:
        Hex-encoded SHA-256 hash string consistent with ``compute_hash``.
    """
    excluded = set(HASH_EXCLUDE_FIELDS)
    excluded.add("receipt_type")
    excluded.add("privacy_envelopes")
    if exclude_fields:
        excluded.update(str(field) for field in exclude_fields)
    filtered = {key: value for key, value in payload.items() if key not in excluded}
    canonical = _canonicalize(filtered)
    canonical_bytes = proofkit_module.canonicalize_json(canonical)
    return hashlib.sha256(canonical_bytes).hexdigest()


class CorpusReceipt(BaseReceipt):
    """Receipt for constitutional training corpus builds.
    
    CloudEvents 1.0.x compliant receipt documenting:
    - Dataset statistics (count, dignity distribution, task balance)
    - Filters applied (dignity threshold, guardian requirements)
    - File digests (SHA-256 + BLAKE3)
    - Examples digest (Merkle root)
    - Reproducibility metadata
    
    Optional: Anchored to Sigstore Rekor for transparency.
    """
    type: str = "loom.training.corpus.v1"
    
    # Dataset metadata
    total_examples: int = Field(..., description="Number of training examples in corpus")
    source_receipts_count: int = Field(..., description="Number of source receipts processed")
    filtered_count: int = Field(..., description="Number filtered out")
    
    # Filters applied
    filters: Dict[str, Any] = Field(..., description="Filtering criteria applied")
    
    # Statistics
    mean_dignity: float = Field(..., description="Mean dignity score of included examples")
    std_dignity: float = Field(..., description="Standard deviation of dignity scores")
    min_dignity: float = Field(..., description="Minimum dignity in corpus")
    max_dignity: float = Field(..., description="Maximum dignity in corpus")
    mean_coherence: float = Field(..., description="Mean coherence score")
    guardian_pass_rate: float = Field(..., description="Fraction passing guardian checks")
    
    # Task/label distribution
    unique_task_types: int = Field(..., description="Number of unique task types")
    task_distribution: Optional[Dict[str, int]] = Field(
        None,
        description="Counts per task type"
    )
    outcome_balance: Optional[Dict[str, int]] = Field(
        None,
        description="Counts per outcome label"
    )
    
    # File digests (for reproducibility)
    jsonl_sha256: Optional[str] = Field(None, description="SHA-256 of JSONL export")
    jsonl_blake3: Optional[str] = Field(None, description="BLAKE3 of JSONL export")
    parquet_sha256: Optional[str] = Field(None, description="SHA-256 of Parquet export")
    parquet_blake3: Optional[str] = Field(None, description="BLAKE3 of Parquet export")
    
    # Merkle root of examples (for tamper-evidence)
    examples_merkle_root: Optional[str] = Field(
        None,
        description="Merkle root of all example fingerprints"
    )
    
    # Lineage
    parent_receipts: List[str] = Field(
        default_factory=list,
        description="Source receipt IDs used in corpus"
    )
    
    # Reproducibility
    config_hash: str = Field(..., description="SHA-256 of CorpusConfig used")
    random_seed: Optional[int] = Field(None, description="Random seed for reproducibility")
    
    # Build metadata
    build_timestamp: str = Field(..., description="ISO 8601 timestamp of corpus build")
    builder_agent: str = Field(..., description="Agent that built the corpus")
    loom_version: Optional[str] = Field(None, description="Loom version")
    
    # Anchoring (optional)
    rekor_entry_uuid: Optional[str] = Field(
        None,
        description="Sigstore Rekor entry UUID if anchored"
    )
    rekor_log_index: Optional[int] = Field(
        None,
        description="Rekor transparency log index"
    )
    
    # Bias & fairness notes
    bias_notes: Optional[str] = Field(
        None,
        description="Known biases or representational issues in corpus"
    )


class PolicyUpdateReceiptModel(BaseReceipt):
    """Receipt documenting a policy update applied by the tuner."""

    receipt_type: str = "PolicyUpdateReceipt"
    policy_domain: str = Field(description="Policy domain being updated (routing, guardian, architect)")
    previous_state: Dict[str, Any] = Field(
        default_factory=dict,
        description="Previous policy configuration before the update"
    )
    new_state: Dict[str, Any] = Field(
        default_factory=dict,
        description="New policy configuration after the update"
    )
    adjustment_receipt_id: Optional[str] = Field(
        default=None,
        description="Reference to the CalibrationAdjustmentReceipt that triggered this update"
    )
    reason: str = Field(description="Human-readable reason for the policy update")
    reversible: bool = Field(
        default=True,
        description="Whether this policy update can be rolled back"
    )
    success_count: int = Field(
        default=0,
        description="Number of successful executions that validated this policy"
    )


# ---------------------------------------------------------------------------
# Spine Stage Receipts (for Episode → Spine → Receipt integration)
# ---------------------------------------------------------------------------


class ProviderExecutionReceipt(BaseReceipt):
    """
    Receipt emitted for every provider execution in the execution spine.

    This is the persistable version of core/provider_receipt.py::ProviderReceipt.
    It captures the full execution result including routing, timing, tokens,
    and outcome details for audit and learning.
    """

    receipt_type: str = Field(default="provider_execution")

    # Core identifiers
    envelope_id: str = Field(..., description="ID of the decision envelope")
    provider_receipt_id: str = Field(..., description="ID from the original ProviderReceipt")

    # Routing & Provider
    routing_decision: str = Field(
        default="unknown",
        description="Routing decision (LOCAL_ONLY, CLOUD_LIVE, etc.)"
    )
    provider: str = Field(default="unknown", description="Provider name")
    model: str = Field(default="unknown", description="Model name")

    # Execution result
    success: bool = Field(default=False, description="Whether execution succeeded")
    error: Optional[str] = Field(default=None, description="Error message if failed")

    # Timing
    latency_ms: float = Field(default=0.0, description="End-to-end latency in ms")
    provider_latency_ms: float = Field(default=0.0, description="Provider-specific latency")

    # Token usage
    tokens_input: int = Field(default=0, description="Input tokens consumed")
    tokens_output: int = Field(default=0, description="Output tokens generated")
    total_tokens: int = Field(default=0, description="Total tokens")

    # Cost
    cost_usd: float = Field(default=0.0, description="Estimated cost in USD")

    # Outcome
    outcome_value: Optional[float] = Field(
        default=None,
        description="Numeric outcome value for learning (if applicable)"
    )

    # Shadow executions
    shadow_count: int = Field(default=0, description="Number of shadow executions")
    shadow_results: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Summary of shadow execution results"
    )

    # Additional metadata
    metadata: Dict[str, Any] = Field(
        default_factory=dict,
        description="Additional provider-specific metadata"
    )

    @classmethod
    def from_provider_receipt(
        cls,
        provider_receipt: Any,
        envelope_id: str,
        domain: str = "execution",
        parent_ids: Optional[List[str]] = None,
    ) -> "ProviderExecutionReceipt":
        """
        Create a ProviderExecutionReceipt from a core ProviderReceipt.

        Args:
            provider_receipt: ProviderReceipt instance (from core/provider_receipt.py)
            envelope_id: Decision envelope ID
            domain: Receipt domain
            parent_ids: Parent receipt IDs for lineage

        Returns:
            ProviderExecutionReceipt ready for persistence
        """
        # Extract shadow summaries
        shadows = getattr(provider_receipt, "shadows", [])
        shadow_results = []
        for s in shadows:
            shadow_results.append({
                "provider": getattr(s, "provider", "unknown"),
                "success": getattr(s, "success", False),
                "latency_ms": getattr(s, "latency_ms", 0.0),
            })

        # Extract metadata
        metadata = {}
        if hasattr(provider_receipt, "metadata") and provider_receipt.metadata:
            metadata = dict(provider_receipt.metadata)

        return cls(
            receipt_id=f"pexec_{provider_receipt.receipt_id}",
            domain=domain,
            envelope_id=envelope_id,
            provider_receipt_id=provider_receipt.receipt_id,
            routing_decision=getattr(provider_receipt, "routing_decision", "unknown"),
            provider=getattr(provider_receipt, "provider", "unknown"),
            model=getattr(provider_receipt, "model", "unknown"),
            success=getattr(provider_receipt, "success", False),
            error=getattr(provider_receipt, "error", None),
            latency_ms=getattr(provider_receipt, "latency_ms", 0.0),
            provider_latency_ms=getattr(provider_receipt, "provider_latency_ms", 0.0),
            tokens_input=getattr(provider_receipt, "tokens_input", 0),
            tokens_output=getattr(provider_receipt, "tokens_output", 0),
            total_tokens=getattr(provider_receipt, "total_tokens", 0),
            cost_usd=getattr(provider_receipt, "cost_usd", 0.0),
            outcome_value=getattr(provider_receipt, "outcome_value", None),
            shadow_count=len(shadows),
            shadow_results=shadow_results,
            metadata=metadata,
            parent_ids=parent_ids or [],
        )


class LearningSignalReceipt(BaseReceipt):
    """
    Receipt emitted for every learning signal dispatched in the execution spine.

    Captures the routing of learning observations to learning consumers
    (replay buffers, policy tuners, etc.) for audit and debugging.
    """

    receipt_type: str = Field(default="learning_signal")

    # Core identifiers
    source_receipt_id: str = Field(
        ..., description="ID of the receipt that generated this signal (usually ProviderReceipt)"
    )
    envelope_id: Optional[str] = Field(
        default=None, description="Decision envelope ID"
    )

    # Signal content
    success: bool = Field(default=False, description="Whether the source action succeeded")
    outcome_value: Optional[float] = Field(
        default=None, description="Numeric outcome value for learning"
    )

    # Routing result
    routed_to: List[str] = Field(
        default_factory=list,
        description="List of learning consumer names that received this signal"
    )

    # Additional context
    observation: Dict[str, Any] = Field(
        default_factory=dict,
        description="Learning observation payload (summarized)"
    )


class APMEnforcementReceipt(BaseReceipt):
    """
    Receipt emitted for APM proof tier enforcement in the execution spine.

    Captures the proof tier determination, strictness enforcement, and
    any escalation decisions for constitutional audit.
    """

    receipt_type: str = Field(default="apm_enforcement")

    # Core identifiers
    envelope_id: str = Field(..., description="Decision envelope ID")

    # Proof tier
    proof_tier: str = Field(default="unknown", description="Determined proof tier")
    status: str = Field(default="unknown", description="APM status (allowed, escalated, failed)")

    # Profile enforcement
    proof_profile_id: Optional[str] = Field(
        default=None, description="Proof profile ID if strict mode"
    )
    strict_mode: bool = Field(default=False, description="Whether strict proof mode was required")

    # Escalation
    escalated: bool = Field(default=False, description="Whether action was escalated for review")
    escalation_reason: Optional[str] = Field(
        default=None, description="Reason for escalation if applicable"
    )

    # Verification details
    verification_details: Dict[str, Any] = Field(
        default_factory=dict,
        description="Additional verification metadata"
    )


# =============================================================================
# SEMANTIC MASS RECEIPTS (SFT v0.1)
# =============================================================================


class SemanticMassUpdateReceipt(BaseReceipt):
    """
    Receipt for semantic mass algorithm changes or batch recomputation.

    Emitted when:
    - Algorithm parameters change (mass_version bump)
    - Batch recomputation job runs
    - Single node mass is manually updated

    Constitutional: Changes to mass-based routing are CONSTITUTIONAL
    (affects provider routing → safety).
    """

    receipt_type: str = Field(default="semantic_mass_update")

    # Update classification
    update_type: Literal["algorithm_change", "batch_recompute", "single_node"] = Field(
        ..., description="Type of mass update"
    )

    # Algorithm parameters (for replay determinism)
    mass_version: str = Field(..., description="Mass algorithm version (e.g., 'sft.mass.v0.1')")
    m0_parameter: float = Field(default=8.0, description="Normalization scale parameter")
    content_measure: Literal["chars", "tokens", "bytes"] = Field(
        default="chars", description="How |content| is measured"
    )
    ref_count_definition: str = Field(
        default="inbound_dependents",
        description="What ref_count represents (inbound_dependents, parent_ids, etc.)"
    )

    # Decay parameters
    t_stale_days: float = Field(default=90.0, description="Days before decay begins")
    half_life_days: float = Field(default=180.0, description="Decay half-life in days")
    dignity_floor: float = Field(default=0.15, description="Dignity floor for m_eff gating")

    # Scope
    node_count: int = Field(..., description="Number of nodes affected")
    graph_hash: Optional[str] = Field(
        default=None, description="Hash of graph state before update"
    )

    # Single-node fields (when update_type == "single_node")
    node_id: Optional[str] = Field(default=None, description="Node ID for single-node update")
    mass_before: Optional[float] = Field(default=None, description="Raw mass before update")
    mass_after: Optional[float] = Field(default=None, description="Raw mass after update")
    mass_norm_before: Optional[float] = Field(default=None, description="Normalized mass before")
    mass_norm_after: Optional[float] = Field(default=None, description="Normalized mass after")

    # Batch summary stats (when update_type != "single_node")
    mean_mass_before: Optional[float] = Field(default=None, description="Mean raw mass before")
    mean_mass_after: Optional[float] = Field(default=None, description="Mean raw mass after")
    median_mass_before: Optional[float] = Field(default=None)
    median_mass_after: Optional[float] = Field(default=None)
    p95_mass_after: Optional[float] = Field(default=None, description="95th percentile mass")
    nodes_changed: Optional[int] = Field(default=None, description="Nodes with mass delta > epsilon")
    max_delta: Optional[float] = Field(default=None, description="Maximum mass change observed")

    # Delta manifest (for detailed audit)
    delta_manifest_hash: Optional[str] = Field(
        default=None,
        description="Hash of delta manifest artifact (JSONL with per-node changes)"
    )


class SemanticMassDeltaManifestEntry(BaseModel):
    """
    Single entry in the delta manifest artifact.

    Stored as JSONL, hash included in SemanticMassUpdateReceipt.delta_manifest_hash.
    """

    node_id: str
    mass_before: float
    mass_after: float
    mass_norm_before: float
    mass_norm_after: float
    ref_count_before: int
    ref_count_after: int
    components_before: Optional[Dict[str, Any]] = None
    components_after: Optional[Dict[str, Any]] = None


# Temporary compatibility shim
from receipts.models_compat import *  # noqa
