"""缘契域表模型:契约当前版 + 不可变事件历史。 设计依据 docs/domain/core-domains.md §3 与 data-model.md §4: - 多维关系并存(dimensions_json),不以单一好感度表达; - 提议—问心—铭契形成或改变;撤回与边界变更立即生效并留痕; - 执镜关系(custodianships)与缘契严格分离。 """ from datetime import datetime from sqlalchemy import DateTime, ForeignKey, 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 class Covenant(Base): """缘契当前生效版本(互动者=owner_scope 用户,1.0 单用户档)。""" __tablename__ = "covenants" id: Mapped[str] = mapped_column(String(80), primary_key=True, default=lambda: new_id("cov")) character_id: Mapped[str] = mapped_column( String(80), ForeignKey("characters.id"), nullable=False, index=True ) owner_scope: Mapped[str] = mapped_column(String(80), nullable=False, index=True) # 多维关系维度:{"trust": "L4", "mentorship": "...", "companionship": "..."}—— # 结构自由但禁止单一好感度数值(service 层拒绝 affinity/favorability 键) dimensions_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}") # 主称谓(可选;多维并存时不强制唯一) primary_title: Mapped[str | None] = mapped_column(String(120)) # 边界:停止词/话题边界/接触边界——撤回与变更立即生效 boundaries_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}") # 成人内容授契(默认关;按会话临时开启,不落长期状态) adult_consent: Mapped[bool] = mapped_column(default=False, nullable=False) # pending(提议中)/ sealed(已铭契)/ withdrawn(已撤回) status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending", index=True) version: Mapped[int] = mapped_column(nullable=False, default=0) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, onupdate=utc_now) class CovenantEvent(Base): """缘契事件(不可变 append-only):propose / wenxin / seal / withdraw / boundary。""" __tablename__ = "covenant_events" id: Mapped[str] = mapped_column(String(80), primary_key=True, default=lambda: new_id("cove")) covenant_id: Mapped[str] = mapped_column(String(80), ForeignKey("covenants.id"), index=True) character_id: Mapped[str] = mapped_column(String(80), nullable=False, index=True) # propose(提议)/ wenxin(问心·人物自陈候选)/ seal(铭契确认)/ # withdraw(撤回)/ boundary(边界变更) type: Mapped[str] = mapped_column(String(20), nullable=False, index=True) # 事件载荷:提议维度 / 问心候选 / 铭契确认的最终维度 / 边界快照 payload_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}") actor: Mapped[str] = mapped_column(String(20), nullable=False, default="user") # user / character created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, index=True)