"""百工域表模型:能力注册表 / 授契与行契 / 调用记录。 设计依据 docs/domain/mandate-and-capability-model.md 与 docs/architecture/data-model.md §6: - ``capabilities`` 统一能力契约(工具/MCP/Skill/感官/形器),保留执行类型; - ``mandates`` 授契(单次范围)与行契(重复执行:范围/预算/时间窗),撤销立即生效; - ``tool_calls`` 调用记录与证据链(净化后的输入输出摘要 + 观察标证); - 请命复用 ``pending_confirmations`` 表(models/safety.py,迁移 006 增列改造)。 """ from datetime import datetime from sqlalchemy import JSON, DateTime, ForeignKey, Index, String, Text from sqlalchemy.orm import Mapped, mapped_column from app.core.ids import new_id from app.core.time import utc_now from app.db.base import Base # 执行类型(data-model §6):HTTP / MCP / 乾坤 / 内置 EXECUTOR_TYPES = ("builtin", "qiankun", "mcp", "http") # 四维风险标注(mandate-and-capability-model.md §3):能力/数据/后果/情境 RISK_DIMENSIONS = ("capability", "data", "consequence", "context") class Capability(Base): """百工能力注册表:稳定标识 + 参数 Schema + 执行类型 + 四维风险标注。""" __tablename__ = "capabilities" id: Mapped[str] = mapped_column(String(80), primary_key=True, default=lambda: new_id("cap")) # 稳定标识(契约词典 §5:capability_registry);同 owner 内唯一 name: Mapped[str] = mapped_column(String(120), nullable=False) owner_scope: Mapped[str] = mapped_column(String(80), nullable=False, index=True) description: Mapped[str] = mapped_column(Text, nullable=False, default="") # 执行类型:builtin / qiankun / mcp / http executor_type: Mapped[str] = mapped_column(String(20), nullable=False) # 执行器绑定(builtin 工具名 / MCP server id / 乾坤命令模板等) executor_ref: Mapped[str] = mapped_column(String(200), nullable=False, default="") # 参数 Schema(JSON Schema 子集;供校验与 UI 表单) params_schema_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}") # 四维风险标注:{"capability": "L1", "data": "L2", "consequence": "L0", "context": "L1"} risk_dims_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}") # 读取/传出/修改的数据范围与外部目的地(能力契约展示) data_scope: Mapped[str] = mapped_column(Text, nullable=False, default="") destinations: Mapped[str] = mapped_column(Text, nullable=False, default="") # 来源与版本:builtin / official_signed / user_custom source: Mapped[str] = mapped_column(String(20), nullable=False, default="builtin") version: Mapped[str] = mapped_column(String(40), nullable=False, default="1") # 司命裁决模式:allow(授契即行)/ ask(每次请命);禁域由裁决器硬编码,不落库不可配 consent_mode: Mapped[str] = mapped_column(String(10), nullable=False, default="allow") enabled: Mapped[bool] = mapped_column(default=True, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) __table_args__ = ( Index("uq_capabilities_owner_name", "owner_scope", "name", unique=True), ) class Mandate(Base): """授契(grant:单次范围)与行契(charter:限定条件内重复执行)。""" __tablename__ = "mandates" id: Mapped[str] = mapped_column(String(80), primary_key=True, default=lambda: new_id("mdt")) owner_scope: Mapped[str] = mapped_column(String(80), nullable=False, index=True) # 授予人物(空 = 用户全局,所有人物可用) character_id: Mapped[str | None] = mapped_column( String(80), ForeignKey("characters.id"), nullable=True, index=True ) capability_id: Mapped[str] = mapped_column( String(80), ForeignKey("capabilities.id"), nullable=False, index=True ) # grant(授契:范围用完即止)/ charter(行契:条件内重复执行) kind: Mapped[str] = mapped_column(String(10), nullable=False, default="grant") # 范围(允许的参数/数据/目标约束,JSON;行契含预算与停止条件) scope_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) # 行契:时间窗与预算(次数上限;0 = 不限次但受停止条件约束) expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) budget_total: Mapped[int] = mapped_column(nullable=False, default=1) budget_used: Mapped[int] = mapped_column(nullable=False, default=0) status: Mapped[str] = mapped_column(String(20), nullable=False, default="active", index=True) granted_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class ToolCall(Base): """调用记录与证据链:净化后的输入输出摘要 + 观察标证(不落原文)。""" __tablename__ = "tool_calls" id: Mapped[str] = mapped_column(String(80), primary_key=True, default=lambda: new_id("tc")) run_id: Mapped[str | None] = mapped_column(String(80), ForeignKey("runs.id"), index=True) character_id: Mapped[str | None] = mapped_column( String(80), ForeignKey("characters.id"), index=True ) capability_id: Mapped[str] = mapped_column(String(80), nullable=False, index=True) trace_id: Mapped[str | None] = mapped_column(String(80), index=True) # 司命裁决:allow / ask_approved / denied_forbidden(HJ-1301) / denied_unmandated(HJ-1303) decision: Mapped[str] = mapped_column(String(40), nullable=False) # 命中的授契/行契(拒绝时为空) mandate_id: Mapped[str | None] = mapped_column(String(80)) status: Mapped[str] = mapped_column(String(20), nullable=False, default="ok") error_code: Mapped[str | None] = mapped_column(String(40)) # 净化后摘要(体量受限,不含原文全文) input_digest: Mapped[str] = mapped_column(Text, nullable=False, default="") output_digest: Mapped[str] = mapped_column(Text, nullable=False, default="") # 观察标证:来源、执行条件、观察语义(非事实)——供灵台作为观察接纳 evidence_json: Mapped[str] = mapped_column(JSON, nullable=False, default=dict) duration_ms: Mapped[int] = mapped_column(nullable=False, default=0) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, index=True)