2026-07-02 03:22:12 +08:00
|
|
|
|
"""栅栏 DTO。
|
|
|
|
|
|
|
|
|
|
|
|
定义并发回复栅栏相关的不可变值对象,包括栅栏裁决、栅栏代际与栅栏
|
|
|
|
|
|
上下文。所有 DTO 均为 ``dataclass(frozen=True)``,仅依赖标准库,
|
|
|
|
|
|
用于并发回复栅栏的代际控制与过期裁决(FR-23)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
from yuxi.channels.contract.errors import ValidationError
|
|
|
|
|
|
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
@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
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
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")
|
|
|
|
|
|
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
@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
|
2026-07-03 19:18:13 +08:00
|
|
|
|
|
|
|
|
|
|
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")
|