"""镜生域模型:灵胚、执镜关系、命台、魂籍(data-model.md §1)。 - 灵胚只有预备身份,启镜(activate)才产生正式人物与执镜关系; - 命台人物粒度唯一(UNIQUE(character_id)),租约到期可被接管,epoch 递增; - 魂籍版本不可变(UNIQUE(character_id, version)),签名绑定人物与版本。 """ from datetime import datetime from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from app.core.ids import new_custodianship_id, new_embryo_id, new_id, new_identity_record_id, new_seat_id from app.core.time import utc_now from app.db.base import Base class EmbryoDraft(Base): """灵胚:启镜前的可审阅草稿。放弃灵胚是删除草稿,不是人物死亡。""" __tablename__ = "embryo_drafts" id: Mapped[str] = mapped_column(String(80), primary_key=True, default=new_embryo_id) owner_scope: Mapped[str] = mapped_column(String(80), nullable=False, index=True) # drafting / previewing / confirming / activated / abandoned status: Mapped[str] = mapped_column(String(40), nullable=False, default="drafting", index=True) # original / adopted / imported / serendipity source: Mapped[str] = mapped_column(String(40), nullable=False, default="original") name: Mapped[str] = mapped_column(String(120), nullable=False) description: Mapped[str | None] = mapped_column(Text) identity_baseline_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}") # 照心试演引用:[{run_id, prompt, performed_at}],内容在事件日志,不入正式经历 rehearsals_json: Mapped[str] = mapped_column(Text, nullable=False, default="[]") 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 Custodianship(Base): """执镜关系:治理责任(1.0 一对一),与缘契(covenants)严格分离。""" __tablename__ = "custodianships" id: Mapped[str] = mapped_column(String(80), primary_key=True, default=new_custodianship_id) character_id: Mapped[str] = mapped_column( String(80), ForeignKey("characters.id"), nullable=False, unique=True, index=True ) custodian_scope: Mapped[str] = mapped_column(String(80), nullable=False, index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class RuntimeSeat(Base): """命台:人物唯一权威运行位置;holder 持有租约,过期可接管,epoch 每次获取递增。""" __tablename__ = "runtime_seats" id: Mapped[str] = mapped_column(String(80), primary_key=True, default=new_seat_id) character_id: Mapped[str] = mapped_column( String(80), ForeignKey("characters.id"), nullable=False, unique=True, index=True ) epoch: Mapped[int] = mapped_column(Integer, nullable=False, default=1) holder: Mapped[str | None] = mapped_column(String(120)) lease_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utc_now, onupdate=utc_now ) class IdentityRecord(Base): """魂籍:可验证、版本化的身份与状态包。版本不可变;复制不能凭相同 ID 冒充原人物。""" __tablename__ = "identity_records" __table_args__ = (UniqueConstraint("character_id", "version", name="uq_identity_char_version"),) id: Mapped[str] = mapped_column(String(80), primary_key=True, default=new_identity_record_id) character_id: Mapped[str] = mapped_column( String(80), ForeignKey("characters.id"), nullable=False, index=True ) version: Mapped[int] = mapped_column(Integer, nullable=False) payload_json: Mapped[str] = mapped_column(Text, nullable=False) digest: Mapped[str] = mapped_column(String(64), nullable=False) signature: Mapped[str] = mapped_column(String(128), nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) class SoulForging(Base): """铸魂提案:道体锚点重大变更必须走确认流(identity-continuity.md §3)。 pending → confirmed(应用锚点 + 签发新魂籍版本)/ rejected; 确认前人物锚点不变——日常对话、模型输出、普通工具不可静默覆盖。 """ __tablename__ = "soul_forgings" id: Mapped[str] = mapped_column(String(80), primary_key=True, default=lambda: new_id("forge")) character_id: Mapped[str] = mapped_column( String(80), ForeignKey("characters.id"), nullable=False, index=True ) proposed_anchor_json: Mapped[str] = mapped_column(Text, nullable=False) reason: Mapped[str] = mapped_column(Text, nullable=False, default="") # pending / confirmed / rejected status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending", index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) confirmed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))