"""优雅降级 DTO。 定义优雅降级(FR-36)的枚举与不可变值对象,包括降级等级与恢复状态。 所有枚举继承 ``str, Enum`` 以支持 JSON 序列化,DTO 均为 ``dataclass(frozen=True)``,仅依赖标准库,用于插件失败后的降级状态 管理与恢复追踪。 """ from __future__ import annotations from dataclasses import dataclass from datetime import datetime from enum import StrEnum from yuxi.channels.contract.errors import ValidationError class DegradationLevel(StrEnum): """降级等级。 标识系统或渠道的降级状态,用于健康检查与降级决策。继承 ``str, Enum`` 以支持 JSON 序列化与字符串比较。 取值: HEALTHY: 所有插件运行正常。 DEGRADED: 部分插件失败但核心功能可用。 UNHEALTHY: 核心插件失败或数据库 / Redis 不可用。 """ HEALTHY = "healthy" DEGRADED = "degraded" UNHEALTHY = "unhealthy" @dataclass(frozen=True) class RecoveryStatus: """恢复状态。 描述失败插件的恢复追踪状态,包括插件 ID、重试次数、上次重试时间、 下次重试时间、是否已恢复与失败原因,用于 FR-36 优雅降级的恢复 流程管理与诊断。 字段: plugin_id: 插件 ID。 retry_count: 重试次数(默认 0,上限 3)。 last_retry_at: 上次重试时间(可选)。 next_retry_at: 下次重试时间(可选)。 recovered: 是否已恢复(默认 False)。 failure_reason: 失败原因(可选,便于诊断)。 """ plugin_id: str retry_count: int = 0 last_retry_at: datetime | None = None next_retry_at: datetime | None = None recovered: bool = False failure_reason: str | None = None def __post_init__(self) -> None: """校验 plugin_id 非空与 retry_count 范围。 ``plugin_id`` 必须非空,``retry_count`` 必须在 0-3 之间(FR-36 上限 3), 在构造时即抛出 ``ValidationError``,adapter 不再做该校验(INV-8)。 """ if not self.plugin_id: raise ValidationError("plugin_id", "must not be empty") if self.retry_count < 0 or self.retry_count > 3: raise ValidationError( "retry_count", "retry_count must be in [0, 3]", )