"""司命台管理 API schema(api-spec §19)。 九枢健康度聚合 / 朱批待办 / 起居注审计 + 五组治理对象 CRUD。 枚举取值见契约字典 §5.5 / §5.7 / §5.9 / §5.10 / §5.11–§5.14 / §11 / §13。 """ from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field # —— §19.1 九枢健康度聚合 —— class PivotHealthSummary(BaseModel): pivot: str = Field(description="九枢标识(契约字典 §5.9)。") name: str = Field(description="枢中文名。") module: str = Field(description="工程模块名。") health: str = Field(description="健康状态:void / breathing / steady / cracked(§5.10)。") summary: str = Field(description="人类可读的一句话态势;cracked 须给出原因。") updated_at: str | None = Field(default=None, description="最近更新时间。") class PivotHealthReport(BaseModel): overall: str = Field(description="九枢聚合健康度;存在任一 cracked 则为 cracked。") alerts: int = Field(description="cracked 枢计数。") pivots: list[PivotHealthSummary] = Field( description="九枢全集,缺数据枢 health=void,不得省略。" ) # —— §19.2 朱批待办 —— class PendingApproval(BaseModel): confirmation_id: str = Field( description="确认 ID,用于 /safety/confirmations/{id}/approve|deny。" ) character_id: str | None = Field(default=None, description="所属人物 ID。") tool: str = Field(description="待裁定工具名。") risk_level: str = Field(description="风险等级(§5.5)。") permission_policy: str = Field(description="权限策略(§5.6)。") action_summary: str = Field(description="动作摘要。") input_summary: str | None = Field(default=None, description="输入摘要,不得含明文密钥。") possible_impact: str | None = Field(default=None, description="可能影响。") reason: str | None = Field(default=None, description="裁定原因。") created_at: str | None = Field(default=None, description="创建时间。") # —— §19.3 起居注审计 —— class AuditEntry(BaseModel): audit_id: str = Field(description="审计事件 ID。") pivot: str = Field(description="九枢标识。") actor: str = Field(description="发起者:user / system / tool(§13.2)。") action: str = Field(description="动作。") subject: str | None = Field(default=None, description="动作对象。") decision: str = Field(description="裁定结果:auto / approved / denied / blocked(§13.3)。") risk_level: str | None = Field(default=None, description="风险等级。") summary: str = Field(description="人类可读一句话摘要。") trace_id: str | None = Field(default=None, description="链路追踪 ID。") created_at: str | None = Field(default=None, description="创建时间。") # —— §19.4 太一枢 · 目标 —— class GoalNode(BaseModel): model_config = ConfigDict(from_attributes=True) id: str character_id: str | None = None title: str description: str level: str = Field(description="目标层级(§5.11)。") type: str = Field( default="session", description="目标类型(§5.15):mission/relationship/skill/knowledge/maintenance/exploration", ) status: str = Field(description="目标状态(§5.12)。") priority: int progress: float = Field(default=0.0, ge=0.0, le=1.0, description="完成度 0~1。") constraints: list[str] parent_id: str | None = None success_criteria: list[str] updated_at: str | None = None class CreateGoalRequest(BaseModel): title: str = Field(max_length=300) description: str = Field(default="", max_length=2000) level: str = Field(description="目标层级(§5.11)。") type: str = Field(default="session", description="目标类型(§5.15)。") priority: int = Field(default=50, ge=0, le=100) character_id: str | None = Field(default=None, description="归属于哪个人物。") constraints: list[str] = Field(default_factory=list) parent_id: str | None = None success_criteria: list[str] = Field(default_factory=list) class UpdateGoalRequest(BaseModel): title: str | None = Field(default=None, max_length=300) description: str | None = Field(default=None, max_length=2000) status: str | None = Field(default=None, description="目标状态(§5.12)。") type: str | None = Field(default=None, description="目标类型(§5.15)。") priority: int | None = Field(default=None, ge=0, le=100) progress: float | None = Field(default=None, ge=0.0, le=1.0) constraints: list[str] | None = None # —— §19.4 烛照枢 · 感知通道 —— class PerceptionChannelResponse(BaseModel): model_config = ConfigDict(from_attributes=True) channel: str name: str description: str enabled: bool consent_status: str = Field(description="授权状态(§5.7)。") privacy_level: str = Field(description="隐私等级(§5.4)。") risk_level: str = Field(description="风险等级(§5.5)。") last_active_at: str | None = None class CreatePerceptionChannelRequest(BaseModel): channel: str = Field(max_length=80, description="通道标识;默认六类,可自定义。") name: str = Field(max_length=120) description: str = Field(default="", max_length=2000) risk_level: str = Field(default="L2") class UpdatePerceptionChannelRequest(BaseModel): enabled: bool | None = None consent_status: str | None = Field(default=None, description="授权状态(§5.7)。") privacy_level: str | None = Field(default=None, description="隐私等级(§5.4)。") # —— §19.4 河图枢 · 世界实体 —— class WorldEntityResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str type: str = Field(description="实体类型(§11.1)。") name: str attributes: dict[str, str] state_type: str = Field(description="状态类型(§11.2)。") confidence: float sources: list[str] updated_at: str | None = None class CreateWorldEntityRequest(BaseModel): type: str = Field(description="实体类型(§11.1)。") name: str = Field(max_length=200) attributes: dict[str, str] = Field(default_factory=dict) state_type: str = Field(default="inference", description="状态类型(§11.2)。") confidence: float = Field(default=0.5, ge=0.0, le=1.0) sources: list[str] = Field(default_factory=list) class UpdateWorldEntityRequest(BaseModel): name: str | None = Field(default=None, max_length=200) state_type: str | None = Field(default=None, description="状态类型(§11.2)。") confidence: float | None = Field(default=None, ge=0.0, le=1.0) # —— §19.4 洛书枢 · 计划 —— class PlanSummaryResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: str goal_id: str title: str status: str = Field(description="计划状态(§5.13)。") assumptions: list[str] risks: list[str] required_tools: list[str] success_criteria: list[str] step_count: int completed_steps: int updated_at: str | None = None class CreatePlanRequest(BaseModel): title: str = Field(max_length=300) goal_id: str | None = None assumptions: list[str] = Field(default_factory=list) risks: list[str] = Field(default_factory=list) class UpdatePlanRequest(BaseModel): status: str | None = Field(default=None, description="计划状态(§5.13)。") assumptions: list[str] | None = None risks: list[str] | None = None # —— §19.4 天工阁枢 · 角色在岗 —— class AgentRosterResponse(BaseModel): model_config = ConfigDict(from_attributes=True) role: str name: str responsibility: str enabled: bool presence: str = Field(description="在岗呈现态(派生自最近活跃时间):idle / active。") last_active_at: str | None = None class CreateAgentRequest(BaseModel): role: str = Field(max_length=80, description="角色标识;默认九角色,可自定义。") name: str = Field(max_length=120) responsibility: str = Field(default="", max_length=2000) class UpdateAgentRequest(BaseModel): enabled: bool | None = None # —— §19.5 链路追踪 —— class TraceItem(BaseModel): kind: Literal["event", "audit", "idempotency"] = Field( description="记录来源:运行事件 / 安全审计 / 请求幂等。" ) id: str = Field(description="记录主键。") created_at: datetime = Field(description="发生时间(前端按此排序展示)。") source_label: str = Field(description="人类可读来源(如「运行事件 / message.completed」)。") summary: str = Field(description="一句话摘要。") detail: dict[str, Any] = Field( default_factory=dict, description="原始字段(payload / decision / path 等),供详情展开。", ) class TraceDetail(BaseModel): trace_id: str = Field(description="被查询的链路追踪 ID。") character_ids: list[str] = Field( default_factory=list, description="该链路涉及的人物去重列表(由 events / audits 聚合)。" ) run_ids: list[str] = Field( default_factory=list, description="该链路涉及的 run 去重列表(由 events 聚合)。" ) session_ids: list[str] = Field( default_factory=list, description="该链路涉及的对话(chat session)去重列表(由 runs 聚合)。", ) items: list[TraceItem] = Field( default_factory=list, description="按 created_at 升序的时间线条目。" ) class TraceSummary(BaseModel): trace_id: str = Field(description="链路追踪 ID。") last_seen: datetime = Field(description="该链路最近一次活动时间。") character_ids: list[str] = Field(default_factory=list, description="涉及的人物去重列表。") session_ids: list[str] = Field(default_factory=list, description="涉及的对话去重列表。") event_count: int = Field(default=0, description="运行事件数。") audit_count: int = Field(default=0, description="安全审计数。") idempotency_count: int = Field(default=0, description="请求幂等数。") class TraceListResponse(BaseModel): items: list[TraceSummary] = Field(description="按 last_seen 倒序的链路概览。") total: int = Field(description="distinct trace 总数(未截断)。") # —— §8 司命台 · 起居注时间河(辰正 time.* 事件)—— class TimeEventResponse(BaseModel): """辰正 time.* 事件(起居注时间河条目)。""" id: str = Field(description="事件 ID。") type: str = Field(description="事件类型(time.* 前缀,如 time.reunion / time.sentiment)。") payload: dict[str, Any] = Field(default_factory=dict, description="事件 payload(已解析)。") run_id: str | None = Field(default=None, description="所属 run ID(可空)。") character_id: str | None = Field(default=None, description="所属人物 ID(可空)。") sensitivity: str = Field(default="low", description="敏感度(§5.4)。") created_at: str = Field(description="发生时间(ISO 字符串)。") class TimeEventListResponse(BaseModel): items: list[TimeEventResponse] = Field(description="按 created_at 倒序的事件列表。") total: int = Field(description="source='time_sense' 的事件总数(未截断)。") # —— §19.6 用户管理 —— class AdminUserResponse(BaseModel): """管理员视角的用户信息。""" model_config = ConfigDict(from_attributes=True) id: str username: str email: str is_active: bool is_admin: bool created_at: datetime updated_at: datetime | None = None class AdminCreateUserRequest(BaseModel): username: str = Field(min_length=1, description="用户名。") email: str = Field(min_length=1, description="邮箱。") password: str = Field(min_length=6, description="密码(>=6 字符)。") is_admin: bool = Field(default=False, description="是否管理员。") class AdminUpdateUserRequest(BaseModel): is_active: bool | None = Field(default=None, description="是否启用。") is_admin: bool | None = Field(default=None, description="是否管理员。")