"""栅栏 DTO。 定义并发回复栅栏相关的不可变值对象,包括栅栏裁决、栅栏代际与栅栏 上下文。所有 DTO 均为 ``dataclass(frozen=True)``,仅依赖标准库, 用于并发回复栅栏的代际控制与过期裁决(FR-23)。 """ from __future__ import annotations from dataclasses import dataclass from datetime import datetime from yuxi.channels.contract.errors import ValidationError @dataclass(frozen=True) class FenceVerdict: """栅栏裁决。 描述栅栏对一次回复的过期裁决,包括是否过期、当前代际与可选的 过期代际,用于决定是否抑制过期回复。 字段: is_stale: 是否过期。 current_generation: 当前代际。 stale_generation: 过期代际(可选)。 """ is_stale: bool current_generation: int stale_generation: int | None = None @dataclass(frozen=True) class FenceGeneration: """栅栏代际。 描述会话的栅栏代际状态,包括当前代际与可见代际,用于代际比较与 过期判定。 字段: conversation_id: 会话 ID。 current_generation: 当前代际。 visible_generation: 可见代际。 """ conversation_id: str current_generation: int visible_generation: int def __post_init__(self) -> None: """校验 conversation_id 非空与代际非负。 ``conversation_id`` 必须非空,``current_generation`` 与 ``visible_generation`` 必须非负,在构造时即抛出 ``ValidationError``, 避免空会话 ID 或负代际导致栅栏代际比较失效(INV-8 / FR-23)。 """ if not self.conversation_id: raise ValidationError("conversation_id", "must not be empty") if self.current_generation < 0: raise ValidationError("current_generation", "must not be negative") if self.visible_generation < 0: raise ValidationError("visible_generation", "must not be negative") @dataclass(frozen=True) class FenceContext: """栅栏上下文。 描述栅栏的执行上下文,包括会话 ID、代际、起始时间与 TTL,用于 栅栏生命周期管理与过期清理。 字段: conversation_id: 会话 ID。 generation: 代际。 started_at: 起始时间。 ttl_seconds: TTL(秒,默认 120,与配置项 ``fence_ttl_seconds`` 一致)。 """ conversation_id: str generation: int started_at: datetime ttl_seconds: int = 120 def __post_init__(self) -> None: """校验 conversation_id 非空、generation 非负与 ttl_seconds 为正整数。 ``conversation_id`` 必须非空,``generation`` 必须非负,``ttl_seconds`` 必须为正整数,在构造时即抛出 ``ValidationError``,避免空会话 ID、负 代际或非正 TTL 导致栅栏生命周期管理失效(INV-8 / FR-23)。 """ if not self.conversation_id: raise ValidationError("conversation_id", "must not be empty") if self.generation < 0: raise ValidationError("generation", "must not be negative") if self.ttl_seconds <= 0: raise ValidationError("ttl_seconds", "must be a positive integer")