"""领域错误类型定义。 定义 ``DomainError`` 抽象基类及其 24 个具体子类,表示领域规则违反或业务 状态异常。涵盖冲突、规则违反、渠道降级、Bot 循环预算、插件生命周期、 配对审批、DM 安全、限流、能力证明、配置热更新、Schema 初始化、账户生命 周期回调、内容审核、Agent 协作等场景。 """ from __future__ import annotations from typing import Any from yuxi.channels.contract.errors.base import Error class DomainError(Error): """领域错误抽象基类。 表示领域规则违反或业务状态异常。子类覆盖各类业务场景。 """ error_code = "DOMAIN_ERROR" class ConflictError(DomainError): """资源冲突错误。 资源状态冲突时抛出,如重复创建、并发修改(HTTP 409)。 """ error_code = "CONFLICT" def __init__( self, resource: str, *, trace_id: str | None = None, ) -> None: super().__init__(f"Conflict on resource: {resource}", trace_id=trace_id) self.resource = resource def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["resource"] = self.resource return data class RuleViolationError(DomainError): """领域规则违反错误。 业务规则被违反时抛出,如会话合并规则不满足(HTTP 422)。 """ error_code = "RULE_VIOLATION" def __init__( self, rule: str, *, trace_id: str | None = None, ) -> None: super().__init__(f"Rule violated: {rule}", trace_id=trace_id) self.rule = rule def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["rule"] = self.rule return data class ChannelDegradedError(DomainError): """渠道降级错误。 渠道插件失败触发优雅降级时抛出(FR-36,HTTP 503)。 """ error_code = "CHANNEL_DEGRADED" def __init__( self, channel: str, *, trace_id: str | None = None, ) -> None: super().__init__(f"Channel degraded: {channel}", trace_id=trace_id) self.channel = channel def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["channel"] = self.channel return data class BotLoopBudgetExceededError(DomainError): """Bot 循环预算超限错误。 Bot 回复频率超出预算限制时抛出,防止 Bot 之间形成无限循环(FR-33,HTTP 429)。 """ error_code = "BOT_LOOP_BUDGET_EXCEEDED" def __init__( self, budget: dict[str, Any], *, trace_id: str | None = None, ) -> None: super().__init__("Bot loop budget exceeded", trace_id=trace_id) self.budget = budget def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["budget"] = self.budget return data class PluginAlreadyRegisteredError(DomainError): """插件已注册错误。 同一插件 ID 重复注册时抛出(FR-32,HTTP 409)。 """ error_code = "PLUGIN_ALREADY_REGISTERED" def __init__( self, plugin_id: str, *, trace_id: str | None = None, ) -> None: super().__init__( f"Plugin already registered: {plugin_id}", trace_id=trace_id, ) self.plugin_id = plugin_id def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["plugin_id"] = self.plugin_id return data class PluginNotFoundError(DomainError): """插件不存在错误。 操作的插件不存在时抛出(FR-32,HTTP 404)。 """ error_code = "PLUGIN_NOT_FOUND" def __init__( self, plugin_id: str, *, trace_id: str | None = None, ) -> None: super().__init__(f"Plugin not found: {plugin_id}", trace_id=trace_id) self.plugin_id = plugin_id def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["plugin_id"] = self.plugin_id return data class PairingExpiredError(DomainError): """配对审批过期错误。 DM 配对审批记录已过期时抛出(FR-33,HTTP 410)。 """ error_code = "PAIRING_EXPIRED" def __init__( self, pairing_id: str, *, trace_id: str | None = None, ) -> None: super().__init__(f"Pairing expired: {pairing_id}", trace_id=trace_id) self.pairing_id = pairing_id def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["pairing_id"] = self.pairing_id return data class DmDeniedError(DomainError): """DM 被拒绝错误。 DM 安全策略拒绝消息时抛出(FR-33,HTTP 403)。 """ error_code = "DM_DENIED" def __init__( self, reason: str, *, trace_id: str | None = None, ) -> None: super().__init__(f"DM denied: {reason}", trace_id=trace_id) self.reason = reason def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["reason"] = self.reason return data class RateLimitError(DomainError): """限流错误。 请求频率超出限制时抛出(FR-19,HTTP 429),携带建议重试等待时间。 """ error_code = "RATE_LIMIT" def __init__( self, resource: str, retry_after_ms: int, *, trace_id: str | None = None, ) -> None: super().__init__( f"Rate limit exceeded on {resource}, retry after {retry_after_ms}ms", trace_id=trace_id, ) self.resource = resource self.retry_after_ms = retry_after_ms def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["resource"] = self.resource data["retry_after_ms"] = self.retry_after_ms data["retry_after"] = max(1, self.retry_after_ms // 1000) return data class CapabilityNotProvenError(DomainError): """能力证明失败错误。 声明的能力在运行时证明失败时抛出(FR-08/FR-21,HTTP 422)。 """ error_code = "CAPABILITY_NOT_PROVEN" def __init__( self, capability: str, *, trace_id: str | None = None, ) -> None: super().__init__( f"Capability not proven: {capability}", trace_id=trace_id, ) self.capability = capability def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["capability"] = self.capability return data class ConfigRestartRequiredError(DomainError): """配置需重启错误。 ``hybrid`` 模式下不可热更新的配置项变更时抛出(FR-37,HTTP 409)。 """ error_code = "CONFIG_RESTART_REQUIRED" def __init__( self, key: str, *, trace_id: str | None = None, ) -> None: super().__init__( f"Config restart required: {key}", trace_id=trace_id, ) self.key = key def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["key"] = self.key return data class ConfigNotHotReloadableError(DomainError): """配置不可热更新错误。 ``hot`` 模式下不可热更新的配置项变更时抛出(FR-37,HTTP 422)。 """ error_code = "CONFIG_NOT_HOT_RELOADABLE" def __init__( self, key: str, *, trace_id: str | None = None, ) -> None: super().__init__( f"Config not hot reloadable: {key}", trace_id=trace_id, ) self.key = key def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["key"] = self.key return data class ConfigVersionConflictError(DomainError): """配置版本冲突错误。 并发修改配置导致版本冲突时抛出(FR-37,HTTP 409)。 """ error_code = "CONFIG_VERSION_CONFLICT" def __init__( self, expected: int, actual: int, *, trace_id: str | None = None, ) -> None: super().__init__( f"Version conflict: expected {expected}, actual {actual}", trace_id=trace_id, ) self.expected = expected self.actual = actual def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["expected"] = self.expected data["actual"] = self.actual return data class ConfigValidationError(DomainError): """配置校验失败错误。 配置 schema 校验失败时抛出(FR-37,HTTP 400),携带错误明细列表。 """ error_code = "CONFIG_VALIDATION" def __init__( self, errors: list[str], *, trace_id: str | None = None, ) -> None: super().__init__( f"Config validation failed: {', '.join(errors)}", trace_id=trace_id, ) self.errors = errors def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["errors"] = list(self.errors) return data class SchemaInitializationError(DomainError): """Schema 初始化失败错误。 渠道专属表 ``ensure_channel_schema()`` 初始化失败时抛出(INV-I5,HTTP 500), 必须导致宿主启动失败。 """ error_code = "SCHEMA_INIT_FAILED" def __init__( self, reason: str, *, trace_id: str | None = None, ) -> None: super().__init__( f"Schema initialization failed: {reason}", trace_id=trace_id, ) self.reason = reason def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["reason"] = self.reason return data class ConfigRollbackError(DomainError): """配置回滚失败错误。 配置热更新失败后回滚也失败时抛出(FR-37,HTTP 500),携带失败原因。 """ error_code = "CONFIG_ROLLBACK_FAILED" def __init__( self, key: str, reason: str, *, trace_id: str | None = None, ) -> None: super().__init__( f"Config rollback failed for key: {key}", trace_id=trace_id, ) self.key = key self.reason = reason def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["key"] = self.key data["reason"] = self.reason return data class PluginFailedError(DomainError): """插件失败错误。 插件初始化、启动或运行时失败时抛出(FR-32),触发 FR-36 优雅降级。 """ error_code = "PLUGIN_FAILED" def __init__( self, plugin_id: str, reason: str, *, trace_id: str | None = None, ) -> None: super().__init__( f"Plugin {plugin_id} failed: {reason}", trace_id=trace_id, ) self.plugin_id = plugin_id self.reason = reason def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["plugin_id"] = self.plugin_id data["reason"] = self.reason return data class PipelineConfigError(DomainError): """管道配置错误。 管道装配时校验失败抛出(如阶段 ``compensate`` 字段指向不存在的阶段 ID)。 属于编程错误,必须在管道构造期 fail-fast 暴露,避免运行期补偿阶段查找 静默失败(HTTP 500)。 """ error_code = "PIPELINE_CONFIG_ERROR" def __init__( self, pipeline: str, stage_id: str, compensate: str, *, trace_id: str | None = None, ) -> None: super().__init__( f"Pipeline '{pipeline}' stage '{stage_id}' references unknown compensate stage '{compensate}'", trace_id=trace_id, ) self.pipeline = pipeline self.stage_id = stage_id self.compensate = compensate def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["pipeline"] = self.pipeline data["stage_id"] = self.stage_id data["compensate"] = self.compensate return data class LifecycleHookError(DomainError): """生命周期回调失败异常。 由 ``LifecycleAdapter`` 的四个回调方法(``afterAccountConfigWritten`` / ``beforeAccountDelete`` / ``onAccountEnabled`` / ``onAccountDisabled``) 在回调失败时抛出。编排链路捕获后按"不阻塞主流程"策略处理:记录告警 审计,响应附 ``warning`` 字段,不回滚已落库的状态/配置。 ``ChannelDegradedError`` / ``DependencyError`` / 超时等异常在 ``AccountLifecycleService`` 中统一转换为本异常的等价表示(``LifecycleCallbackResult``), 保留原始 traceback(``exc_info`` 日志记录)。 字段: hook: 回调方法名(如 ``afterAccountConfigWritten``)。 reason: 失败原因摘要。 """ error_code = "LIFECYCLE_HOOK_ERROR" def __init__(self, hook: str, reason: str, *, trace_id: str = "") -> None: super().__init__( f"lifecycle hook '{hook}' failed: {reason}", trace_id=trace_id, ) self.hook = hook self.reason = reason def to_dict(self) -> dict[str, Any]: data = super().to_dict() data["hook"] = self.hook data["reason"] = self.reason return data class ContentReviewError(DomainError): """内容审核错误基类。 表示内容审核域的领域规则违反或业务状态异常。子类覆盖内容违规等场景。 """ error_code = "CONTENT_REVIEW_ERROR" class ContentViolationError(ContentReviewError): """内容违规错误(管道自动审核命中 block 时抛出)。 预留错误类型:本期不实现管道自动审核,本错误类不投入使用。 未来入站 / 出站管道自动审核命中 ``block`` 时抛出(HTTP 422), ``raiseOnControlFailure`` 的 error_code 映射表届时同步扩展收录 ``CONTENT_VIOLATION``。本期 3 个端点(CR-01/02/03)不抛此错误: CR-01 预审核仅返回 ``verdict=block``,CR-02/03 为纯查询。 """ error_code = "CONTENT_VIOLATION" class AgentCollaborationError(DomainError): """Agent 协作错误基类。 表示多 Agent 协作场景的领域规则违反或业务状态异常。子类覆盖 Agent 不可用、移交失败等具体场景。 """ error_code = "AGENT_COLLABORATION_ERROR" class AgentNotAvailableError(AgentCollaborationError): """目标 Agent 不可用错误。 协作目标 Agent 不存在或未启用时抛出(HTTP 503)。 """ error_code = "AGENT_NOT_AVAILABLE" class AgentHandoffFailedError(AgentCollaborationError): """Agent 移交失败错误。 Agent 间移交操作失败时抛出(HTTP 500)。 """ error_code = "AGENT_HANDOFF_FAILED"