"""Shared diagnostics and structured errors.""" from __future__ import annotations from dataclasses import dataclass, field from typing import Any @dataclass(frozen=True) class Diagnostic: """A structured finding emitted by engine operations.""" severity: str code: str message: str details: dict[str, Any] = field(default_factory=dict) def to_dict(self) -> dict[str, Any]: return { "severity": self.severity, "code": self.code, "message": self.message, "details": dict(self.details), } @dataclass(frozen=True) class OperationFailure: """Structured operation failure suitable for API and batch envelopes.""" code: str message: str operation: str correlation_id: str details: dict[str, Any] = field(default_factory=dict) remediation: str | None = None def to_dict(self) -> dict[str, Any]: data: dict[str, Any] = { "code": self.code, "message": self.message, "operation": self.operation, "correlation_id": self.correlation_id, "details": dict(self.details), } if self.remediation: data["remediation"] = self.remediation return data @dataclass(frozen=True) class BatchItemResult: """One item result inside a batch operation envelope.""" item_id: str operation: str success: bool result_ref: dict[str, Any] = field(default_factory=dict) error: OperationFailure | None = None @classmethod def succeeded( cls, *, item_id: str, operation: str, result_ref: dict[str, Any] | None = None, ) -> "BatchItemResult": return cls( item_id=item_id, operation=operation, success=True, result_ref=dict(result_ref or {}), ) @classmethod def failed( cls, *, item_id: str, operation: str, error: OperationFailure, ) -> "BatchItemResult": return cls(item_id=item_id, operation=operation, success=False, error=error) def to_dict(self) -> dict[str, Any]: data: dict[str, Any] = { "item_id": self.item_id, "operation": self.operation, "success": self.success, } if self.result_ref: data["result_ref"] = dict(self.result_ref) if self.error: data["error"] = self.error.to_dict() return data @dataclass(frozen=True) class BatchOperationResult: """Compact result envelope for batch operations with partial failures.""" operation: str correlation_id: str items: tuple[BatchItemResult, ...] = () audit_event_id: str | None = None def __post_init__(self) -> None: object.__setattr__(self, "items", tuple(self.items)) @property def total(self) -> int: return len(self.items) @property def succeeded(self) -> int: return sum(1 for item in self.items if item.success) @property def failed(self) -> int: return self.total - self.succeeded @property def partial(self) -> bool: return self.succeeded > 0 and self.failed > 0 @property def outcome(self) -> str: if self.partial: return "partial" if self.failed: return "failed" return "success" def to_dict(self) -> dict[str, Any]: data: dict[str, Any] = { "operation": self.operation, "correlation_id": self.correlation_id, "outcome": self.outcome, "total": self.total, "succeeded": self.succeeded, "failed": self.failed, "partial": self.partial, "items": [item.to_dict() for item in self.items], } if self.audit_event_id: data["audit_event_id"] = self.audit_event_id return data class KontextualError(Exception): """Base class for explicit engine failures.""" code = "kontextual.error" def __init__(self, message: str, *, details: dict[str, Any] | None = None) -> None: super().__init__(message) self.details = details or {} def diagnostic(self, *, severity: str = "error") -> Diagnostic: return Diagnostic( severity=severity, code=self.code, message=str(self), details=dict(self.details), ) def to_operation_failure( self, *, operation: str, correlation_id: str, remediation: str | None = None, ) -> OperationFailure: return OperationFailure( code=str(self.details.get("code") or self.code), message=str(self), operation=operation, correlation_id=correlation_id, details=dict(self.details), remediation=remediation, ) class NotFoundError(KontextualError): code = "kontextual.not_found" class DuplicateResourceError(KontextualError): code = "kontextual.duplicate" class ValidationError(KontextualError): code = "kontextual.validation" class AuthorizationError(KontextualError): code = "kontextual.authorization" class AdapterUnavailableError(KontextualError): code = "kontextual.adapter_unavailable"