本次提交包含多维度代码优化与功能增强: 1. 移除报告模块冗余导入与枚举,清理报表相关代码 2. 新增扫码登录支持方法与飞书适配器适配 3. 完善异常日志与健康检查信息 4. 扩展目录、配对管理、能力查询等接口 5. 优化出站管道与事务提交后钩子逻辑 6. 修复飞书消息解析与响应空值问题 7. 重构配置更新与服务账号创建逻辑 8. 统一传输错误分类契约与错误基类扩展
424 lines
18 KiB
Python
424 lines
18 KiB
Python
"""审计 DTO。
|
||
|
||
定义审计日志的不可变值对象,包括审计日志 ID、审计操作类型、审计条目、审计
|
||
查询、审计统计聚合与审计导出任务。所有 DTO 均为 ``dataclass(frozen=True)``,
|
||
仅依赖标准库与契约层内部类型,用于审计日志记录、查询、统计与导出。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
from datetime import datetime
|
||
from enum import StrEnum
|
||
from typing import Any, Literal
|
||
|
||
from yuxi.channels.contract.dtos.channel import ChannelType
|
||
from yuxi.channels.contract.dtos.common import Operator
|
||
from yuxi.channels.contract.errors import ValidationError
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class AuditLogId:
|
||
"""审计日志 ID。
|
||
|
||
标识一条审计日志的全局唯一 ID,用于审计日志关联与查询。
|
||
|
||
字段:
|
||
value: 审计日志 ID 字符串。
|
||
"""
|
||
|
||
value: str
|
||
|
||
|
||
class AuditOperationType(StrEnum):
|
||
"""审计操作类型。
|
||
|
||
标识审计日志记录的操作类型,覆盖插件生命周期、能力声明、白名单、配对、
|
||
管理员消息、DM 决策、配置变更、Outbox 状态、诊断导出、账户生命周期与
|
||
扫码登录等场景。继承 ``str, Enum`` 以支持 JSON 序列化与字符串比较。
|
||
|
||
取值:
|
||
PLUGIN_DISCOVERED: 插件发现。
|
||
PLUGIN_LOADED: 插件加载。
|
||
PLUGIN_STARTED: 插件启动。
|
||
PLUGIN_STOPPED: 插件停止。
|
||
PLUGIN_UNLOADED: 插件卸载。
|
||
PLUGIN_FAILED: 插件失败。
|
||
PLUGIN_PAUSED: 插件暂停。
|
||
PLUGIN_RESUMED: 插件恢复。
|
||
PLUGIN_RELOADED: 插件重载(FR-32,unload + load 组合操作)。
|
||
PLUGIN_RECOVERED: 插件从失败降级中恢复(FR-36,对称于 PLUGIN_FAILED)。
|
||
CAPABILITY_DECLARED: 能力声明。
|
||
CAPABILITY_VERIFIED: 能力验证。
|
||
CAPABILITY_CHANGED: 能力变更。
|
||
WHITELIST_ADDED: 白名单添加。
|
||
WHITELIST_REMOVED: 白名单移除。
|
||
WHITELIST_CLEARED: 白名单清空。
|
||
PAIRING_CREATED: 配对创建。
|
||
PAIRING_APPROVED: 配对批准。
|
||
PAIRING_REJECTED: 配对拒绝。
|
||
PAIRING_REVOKED: 配对撤销。
|
||
PAIRING_EXPIRED: 配对过期。
|
||
ADMIN_MESSAGE_SENT: 管理员消息发送。
|
||
MESSAGE_RECALLED: 消息撤回(MSG-05)。
|
||
DM_DECISION_ALLOW: DM 决策允许。
|
||
DM_DECISION_DENY: DM 决策拒绝。
|
||
DM_DECISION_PENDING: DM 决策待定。
|
||
CONFIG_CHANGED: 配置变更。
|
||
OUTBOX_STATE_CHANGED: Outbox 状态变更。
|
||
DIAGNOSTICS_EXPORTED: 诊断导出。
|
||
CHANNEL_PROBED: 渠道主动探测(FR-35)。
|
||
SESSION_MERGED: 会话合并。
|
||
SESSION_OWNER_TRANSFERRED: 会话所有者转移(FR-26)。
|
||
ADMIN_QUERY: 管理查询(目录列表 / 审计查询 / 健康详情等只读操作)。
|
||
DOCTOR_CHECK_LIST: 诊断检查项列表查询(FR-17,只读)。
|
||
DOCTOR_RUN: 全量诊断执行(FR-17,只读)。
|
||
DOCTOR_CHECK_ITEM: 单项诊断执行(FR-17,只读)。
|
||
DOCTOR_REPAIR: 诊断自动修复执行(FR-17 / AC-40,写操作)。
|
||
DOCTOR_REPAIR_PREVIEWED: 诊断修复预览(FR-17 / AC-40,dry-run 只读)。
|
||
WIZARD_APPLY: 安装向导步骤应用(FR-16)。
|
||
WIZARD_FINALIZE: 安装向导完成(FR-16)。
|
||
WIZARD_OAUTH_CALLBACK: 安装向导 OAuth 回调(FR-16)。
|
||
ACCOUNT_CREATED: 账户创建(AL-01)。
|
||
ACCOUNT_UPDATED: 账户配置更新(AL-01)。
|
||
ACCOUNT_DELETED: 账户删除(AL-04)。
|
||
ACCOUNT_ENABLED: 账户启用,回调成功或无回调(AL-02)。
|
||
ACCOUNT_DISABLED: 账户禁用,回调成功或无回调(AL-02)。
|
||
ACCOUNT_ENABLED_WARN: 账户启用,回调失败(AL-02)。
|
||
ACCOUNT_DISABLED_WARN: 账户禁用,回调失败(AL-02)。
|
||
AFTER_CONFIG_WRITTEN_OK: 配置写入后回调成功(AL-03)。
|
||
AFTER_CONFIG_WRITTEN_WARN: 配置写入后回调失败(AL-03)。
|
||
ACCOUNT_DELETE_CLEANUP_OK: 删除前清理回调成功(AL-04)。
|
||
ACCOUNT_DELETE_CLEANUP_WARN: 删除前清理回调失败(AL-04)。
|
||
START_QR_LOGIN: 发起扫码登录(QR-06)。
|
||
QR_LOGIN_SUCCESS: 扫码登录成功(QR-06)。
|
||
QR_LOGIN_FAILED: 扫码登录失败(QR-06)。
|
||
QR_LOGIN_TIMEOUT: 扫码登录超时(QR-06)。
|
||
QR_LOGIN_CANCELLED: 扫码取消(QR-06)。
|
||
QR_REFRESH: 扫码刷新(QR-06)。
|
||
LOGOUT: 登出(QR-06)。
|
||
CONTENT_REVIEW_PREVIEWED: 内容预审核(CR-01,本期使用)。
|
||
CONTENT_REVIEW_BLOCKED: 内容审核命中 block(预留,本期不使用;
|
||
未来管道自动审核命中 block 时记录)。
|
||
REPORT_CREATED: 报告创建。
|
||
REPORT_DOWNLOADED: 报告下载。
|
||
AGENT_LIST: Agent 列表查询(多 Agent 协作,只读)。
|
||
AGENT_GET: Agent 详情查询(多 Agent 协作,只读)。
|
||
AGENT_HANDOFF: Agent 移交执行(多 Agent 协作,写操作)。
|
||
AGENT_CONTEXT: Agent 上下文查询(多 Agent 协作,只读)。
|
||
SESSION_CLOSED: 会话关闭。
|
||
OUTBOX_DEAD_LETTER_BATCH_RETRIED: Outbox 死信批量重投。
|
||
OUTBOX_DEAD_LETTER_BATCH_DELETED: Outbox 死信批量删除。
|
||
OUTBOX_RETRY_POLICY_UPDATED: Outbox 重试策略更新。
|
||
PAIRING_REQUEST_CREATED: 配对请求创建。
|
||
ACCOUNT_CREDENTIALS_ROTATED: 账户凭据轮换。
|
||
ACCOUNT_CONNECTION_TESTED: 账户连接测试。
|
||
AUDIT_EXPORTED: 审计日志导出(同步导出)。
|
||
"""
|
||
|
||
PLUGIN_DISCOVERED = "plugin_discovered"
|
||
PLUGIN_LOADED = "plugin_loaded"
|
||
PLUGIN_STARTED = "plugin_started"
|
||
PLUGIN_STOPPED = "plugin_stopped"
|
||
PLUGIN_UNLOADED = "plugin_unloaded"
|
||
PLUGIN_FAILED = "plugin_failed"
|
||
PLUGIN_PAUSED = "plugin_paused"
|
||
PLUGIN_RESUMED = "plugin_resumed"
|
||
PLUGIN_RELOADED = "plugin_reloaded"
|
||
PLUGIN_RECOVERED = "plugin_recovered"
|
||
CAPABILITY_DECLARED = "capability_declared"
|
||
CAPABILITY_VERIFIED = "capability_verified"
|
||
CAPABILITY_CHANGED = "capability_changed"
|
||
WHITELIST_ADDED = "whitelist_added"
|
||
WHITELIST_REMOVED = "whitelist_removed"
|
||
WHITELIST_CLEARED = "whitelist_cleared"
|
||
PAIRING_CREATED = "pairing_created"
|
||
PAIRING_APPROVED = "pairing_approved"
|
||
PAIRING_REJECTED = "pairing_rejected"
|
||
PAIRING_REVOKED = "pairing_revoked"
|
||
PAIRING_EXPIRED = "pairing_expired"
|
||
ADMIN_MESSAGE_SENT = "admin_message_sent"
|
||
MESSAGE_RECALLED = "message_recalled"
|
||
DM_DECISION_ALLOW = "dm_decision_allow"
|
||
DM_DECISION_DENY = "dm_decision_deny"
|
||
DM_DECISION_PENDING = "dm_decision_pending"
|
||
CONFIG_CHANGED = "config_changed"
|
||
OUTBOX_STATE_CHANGED = "outbox_state_changed"
|
||
DIAGNOSTICS_EXPORTED = "diagnostics_exported"
|
||
CHANNEL_PROBED = "channel_probed"
|
||
SESSION_MERGED = "session_merged"
|
||
SESSION_OWNER_TRANSFERRED = "session_owner_transferred"
|
||
ADMIN_QUERY = "admin_query"
|
||
DOCTOR_CHECK_LIST = "doctor_check_list"
|
||
DOCTOR_RUN = "doctor_run"
|
||
DOCTOR_CHECK_ITEM = "doctor_check_item"
|
||
DOCTOR_REPAIR = "doctor_repair"
|
||
DOCTOR_REPAIR_PREVIEWED = "doctor_repair_previewed"
|
||
WIZARD_APPLY = "wizard_apply"
|
||
WIZARD_FINALIZE = "wizard_finalize"
|
||
WIZARD_OAUTH_CALLBACK = "wizard_oauth_callback"
|
||
# 账户生命周期审计类型(AL-01~AL-04)
|
||
ACCOUNT_CREATED = "account_created"
|
||
ACCOUNT_UPDATED = "account_updated"
|
||
ACCOUNT_DELETED = "account_deleted"
|
||
ACCOUNT_ENABLED = "account_enabled"
|
||
ACCOUNT_DISABLED = "account_disabled"
|
||
ACCOUNT_ENABLED_WARN = "account_enabled_warn"
|
||
ACCOUNT_DISABLED_WARN = "account_disabled_warn"
|
||
AFTER_CONFIG_WRITTEN_OK = "after_config_written_ok"
|
||
AFTER_CONFIG_WRITTEN_WARN = "after_config_written_warn"
|
||
ACCOUNT_DELETE_CLEANUP_OK = "account_delete_cleanup_ok"
|
||
ACCOUNT_DELETE_CLEANUP_WARN = "account_delete_cleanup_warn"
|
||
# 扫码登录审计类型(QR-06)
|
||
START_QR_LOGIN = "start_qr_login"
|
||
QR_LOGIN_SUCCESS = "qr_login_success"
|
||
QR_LOGIN_FAILED = "qr_login_failed"
|
||
QR_LOGIN_TIMEOUT = "qr_login_timeout"
|
||
QR_LOGIN_CANCELLED = "qr_login_cancelled"
|
||
QR_REFRESH = "qr_refresh"
|
||
LOGOUT = "logout"
|
||
# 内容审核审计类型(CR-01 / 管道自动审核)
|
||
CONTENT_REVIEW_PREVIEWED = "content_review_previewed"
|
||
CONTENT_REVIEW_BLOCKED = "content_review_blocked" # 预留:管道自动审核命中 block 时记录
|
||
# 多 Agent 协作审计类型
|
||
AGENT_LIST = "agent_list"
|
||
AGENT_GET = "agent_get"
|
||
AGENT_HANDOFF = "agent_handoff"
|
||
AGENT_CONTEXT = "agent_context"
|
||
# 会话关闭审计类型
|
||
SESSION_CLOSED = "session_closed"
|
||
# Outbox 死信批量操作审计类型
|
||
OUTBOX_DEAD_LETTER_BATCH_RETRIED = "outbox_dead_letter_batch_retried"
|
||
OUTBOX_DEAD_LETTER_BATCH_DELETED = "outbox_dead_letter_batch_deleted"
|
||
OUTBOX_RETRY_POLICY_UPDATED = "outbox_retry_policy_updated"
|
||
# 配对请求创建审计类型
|
||
PAIRING_REQUEST_CREATED = "pairing_request_created"
|
||
# 账户凭据轮换与连接测试审计类型
|
||
ACCOUNT_CREDENTIALS_ROTATED = "account_credentials_rotated"
|
||
ACCOUNT_CONNECTION_TESTED = "account_connection_tested"
|
||
# 强制下线审计类型(LGN-FORCE-LOGOUT)
|
||
ACCOUNT_OFFLINE = "account_offline"
|
||
# 目录缓存清理审计类型(DIR-CACHE-CLEAR)
|
||
DIRECTORY_CACHE_CLEARED = "directory_cache_cleared"
|
||
# 审计日志导出(同步导出,区别于 admin_query 查询)
|
||
AUDIT_EXPORTED = "audit_exported"
|
||
|
||
|
||
class AuditTxStrategy(StrEnum):
|
||
"""审计事务策略。
|
||
|
||
标识控制面操作产生副作用的可回滚性,决定 audit 阶段的事务边界。
|
||
继承 ``str, Enum`` 以支持 JSON 序列化与字符串比较。
|
||
|
||
取值:
|
||
SHARED: 共享事务(fail-closed)。适用于纯 DB 写入或只读操作,
|
||
审计与业务写入原子提交,审计失败回滚业务(FR-34 fail-closed)。
|
||
INDEPENDENT: 独立事务(best-effort)。适用于已产生不可回滚外部
|
||
副作用的操作(IM 发送、Redis 写入、事件发布),审计使用独立
|
||
事务,失败时仅记录告警,不中止业务——避免"外部副作用已发生
|
||
但审计回滚导致无痕迹"(§10.1 事务边界)。
|
||
"""
|
||
|
||
SHARED = "shared"
|
||
INDEPENDENT = "independent"
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class AuditEntry:
|
||
"""审计条目。
|
||
|
||
描述一条审计日志的完整内容,包括操作人、操作类型、目标、参数摘要、结果、
|
||
时间戳与链路追踪信息,用于审计日志持久化与查询。
|
||
|
||
字段:
|
||
operator: 操作人(用户 ID 或 "system")。
|
||
operation: 操作类型。
|
||
target: 操作目标。
|
||
result: 操作结果(success | failed)。
|
||
timestamp: 操作时间戳。
|
||
params_summary: 脱敏后的参数摘要(可选)。
|
||
trace_id: 链路追踪 ID(可选)。
|
||
source_ip: 来源 IP(可选,FR-34)。
|
||
request_id: 请求 ID(可选,链路追踪用,FR-34)。
|
||
message_id: 关联消息 ID(可选,FR-19)。
|
||
content_summary: 消息内容摘要(可选,FR-19)。
|
||
target_channel: 目标渠道类型(可选,FR-34 审计日志按渠道作用域)。
|
||
target_account: 目标账户 ID(可选,FR-34 按账户作用域查询)。
|
||
"""
|
||
|
||
operator: str
|
||
operation: AuditOperationType
|
||
target: str
|
||
result: Literal["success", "failed"]
|
||
timestamp: datetime
|
||
params_summary: dict[str, Any] | None = None
|
||
trace_id: str | None = None
|
||
source_ip: str | None = None
|
||
request_id: str | None = None
|
||
message_id: str | None = None
|
||
content_summary: str | None = None
|
||
target_channel: str | None = None
|
||
target_account: str | None = None
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class AuditQuery:
|
||
"""审计查询(AUD-001)。
|
||
|
||
描述审计日志查询条件,支持按操作类型、操作人、目标渠道、目标账户、
|
||
时间范围与分页过滤。管理员审计日志 API 的作用域为"按渠道类型+账户"
|
||
(PRD FR-34),target_channel 与 target_account 共同确定查询范围。
|
||
|
||
字段:
|
||
operation_type: 操作类型(可选)。
|
||
operator: 操作人(可选)。
|
||
target_channel: 目标渠道(可选)。
|
||
target_account: 目标账户(可选,FR-34 按账户作用域查询)。
|
||
start_time: 起始时间(可选)。
|
||
end_time: 结束时间(可选)。
|
||
limit: 分页大小(默认 100)。
|
||
offset: 分页偏移(默认 0)。
|
||
trace_id: 链路追踪 ID(可选,按链路追踪 ID 过滤)。
|
||
"""
|
||
|
||
operation_type: AuditOperationType | None = None
|
||
operator: str | None = None
|
||
target_channel: ChannelType | None = None
|
||
target_account: str | None = None
|
||
start_time: datetime | None = None
|
||
end_time: datetime | None = None
|
||
limit: int = 100
|
||
offset: int = 0
|
||
trace_id: str | None = None
|
||
|
||
def __post_init__(self) -> None:
|
||
"""校验时间范围与分页参数。
|
||
|
||
``start_time`` 与 ``end_time`` 同时提供时,``start_time`` 必须早于
|
||
``end_time``;``limit`` 必须为正整数,``offset`` 必须为非负整数。
|
||
在构造时即抛出 ``ValidationError``,adapter 不再做该校验(INV-8)。
|
||
"""
|
||
if self.start_time is not None and self.end_time is not None:
|
||
if self.start_time >= self.end_time:
|
||
raise ValidationError(
|
||
"time_range",
|
||
"start_time must be earlier than end_time",
|
||
)
|
||
if self.limit <= 0:
|
||
raise ValidationError("limit", "must be a positive integer")
|
||
if self.offset < 0:
|
||
raise ValidationError("offset", "must be a non-negative integer")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class AuditLogStats:
|
||
"""审计日志统计聚合结果。
|
||
|
||
描述审计日志按操作类型、操作结果分组的计数与时间范围,由
|
||
``getAuditLogStats`` 返回,供管理后台展示审计日志分布与合规报表。
|
||
|
||
字段:
|
||
total: 满足过滤条件的审计日志总数。
|
||
by_operation_type: 按操作类型分组的计数(key 为 operation_type 字符串)。
|
||
by_result: 按操作结果分组的计数(key 为 "success" / "failed")。
|
||
time_range_start: 匹配记录的最早时间戳(无数据时为 None)。
|
||
time_range_end: 匹配记录的最晚时间戳(无数据时为 None)。
|
||
"""
|
||
|
||
total: int
|
||
by_operation_type: dict[str, int]
|
||
by_result: dict[str, int]
|
||
time_range_start: datetime | None
|
||
time_range_end: datetime | None
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class RetentionPolicy:
|
||
"""审计保留策略(AUD-RETENTION)。
|
||
|
||
描述审计日志的保留策略配置,包括默认保留天数、按操作类型的保留天数、
|
||
自动归档开关与归档提前天数,由 ConfigManager 管理
|
||
``audit_retention_policy`` 配置键(GLOBAL 作用域)。
|
||
|
||
字段:
|
||
default_retention_days: 默认保留天数。
|
||
by_operation_type: 按操作类型分组的保留天数(key 为
|
||
AuditOperationType 字符串值)。
|
||
auto_archive_enabled: 是否启用自动归档。
|
||
auto_archive_before_days: 归档提前天数(到期前 N 天归档)。
|
||
updated_at: 策略最后更新时间(可选,未更新时为 None)。
|
||
"""
|
||
|
||
default_retention_days: int
|
||
by_operation_type: dict[str, int]
|
||
auto_archive_enabled: bool
|
||
auto_archive_before_days: int
|
||
updated_at: datetime | None = None
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class RetentionPolicyUpdateCmd:
|
||
"""审计保留策略更新命令(AUD-RETENTION-PUT)。
|
||
|
||
由 ``AuditQueryPort.updateRetentionPolicy`` 引用,更新审计日志保留策略,
|
||
需记录操作人以满足审计要求(PUT 需超级管理员)。
|
||
|
||
字段:
|
||
operator: 操作人(审计用)。
|
||
default_retention_days: 默认保留天数(默认 90)。
|
||
by_operation_type: 按操作类型分组的保留天数(可选)。
|
||
auto_archive_enabled: 是否启用自动归档(默认 True)。
|
||
auto_archive_before_days: 归档提前天数(默认 80)。
|
||
"""
|
||
|
||
operator: Operator
|
||
default_retention_days: int = 90
|
||
by_operation_type: dict[str, Any] | None = None
|
||
auto_archive_enabled: bool = True
|
||
auto_archive_before_days: int = 80
|
||
|
||
def __post_init__(self) -> None:
|
||
"""校验业务规则(INV-8 / 端口 @pre)。
|
||
|
||
- ``default_retention_days`` 与 ``auto_archive_before_days`` 必须为正整数。
|
||
- ``auto_archive_before_days`` 必须小于 ``default_retention_days``。
|
||
- ``by_operation_type``(如提供)的 key 必须为合法的
|
||
``AuditOperationType`` 字符串值,value 必须为正整数。
|
||
|
||
在构造时即抛出 ``ValidationError``,避免非法值传播到 dispatch 阶段。
|
||
"""
|
||
if self.default_retention_days <= 0:
|
||
raise ValidationError("default_retention_days", "must be positive")
|
||
if self.auto_archive_before_days <= 0:
|
||
raise ValidationError("auto_archive_before_days", "must be positive")
|
||
if self.auto_archive_before_days >= self.default_retention_days:
|
||
raise ValidationError(
|
||
"auto_archive_before_days",
|
||
"must be less than default_retention_days",
|
||
)
|
||
if self.by_operation_type is not None:
|
||
valid_keys = {e.value for e in AuditOperationType}
|
||
for k, v in self.by_operation_type.items():
|
||
if k not in valid_keys:
|
||
raise ValidationError(
|
||
"by_operation_type",
|
||
f"invalid operation_type key: {k}",
|
||
)
|
||
if not isinstance(v, int) or isinstance(v, bool) or v <= 0:
|
||
raise ValidationError(
|
||
"by_operation_type",
|
||
f"value for '{k}' must be positive int, got {v!r}",
|
||
)
|
||
|
||
|
||
__all__ = [
|
||
"AuditEntry",
|
||
"AuditLogId",
|
||
"AuditLogStats",
|
||
"AuditOperationType",
|
||
"AuditQuery",
|
||
"AuditTxStrategy",
|
||
"RetentionPolicy",
|
||
"RetentionPolicyUpdateCmd",
|
||
]
|