✨ feat: WP-02 角色域——完整生命周期、铸魂、内驱力与随缘

- 迁移 6:character(生命周期六态检查约束、起源、现实人物治理标记、道体基线、内驱力)+ character_version(baseline/first_statement/drive_revision/reforging/persona_candidate 五类不可变版本与审核态)
- 创建:手动与随缘(确定性词库组合,同种子同候选、一次一个——不是抽卡);现实人物治理(已故拒绝、未声明授权拒绝)
- 启镜(draft→active + 首个 baseline 版本)与初照自陈(候选→确认归真并入道体)
- 生命周期端点:静修/归藏/复照(归档可恢复、复照不抹历史)+ 死亡两步确认(7 天冷静期可撤销、未满确认 422);非法状态迁移 422
- 铸魂(显式 confirm 强确认,直接更新道体)与人格候选(pending→审核批准才并入道体,不静默覆盖)
- 内驱力编辑:写当前值并落 drive_revision 版本(可追溯,ADR-018)
- 测试 52 项全绿(+8):状态机全路径、随缘确定性、现实人物三例、铸魂确认、候选审核与重复审核 409、跨域 fail-closed
This commit is contained in:
chenxin committed 2026-09-10 18:11:15 +08:00
1 parent 603a683c14
commit 40221170ae
7 files changed
+859

No files matched your search

+74
View File
@@ -300,3 +300,77 @@ class RunEvent(Base):
# Run 内 sequence 单调且唯一(重放去重的服务端保证)
Index("uq_run_event_run_sequence", "run_id", "sequence", unique=True),
)
class CharacterVersion(Base):
"""角色版本链:baseline/初照自陈/内驱力修订/铸魂/人格候选(不可变版本)。"""
__tablename__ = "character_version"
id: Mapped[str] = mapped_column(
String, primary_key=True, default=lambda: new_id("character_version_")
)
character_id: Mapped[str] = mapped_column(
ForeignKey("character.id", ondelete="cascade"), nullable=False, index=True
)
kind: Mapped[str] = mapped_column(String, nullable=False)
status: Mapped[str] = mapped_column(String, nullable=False, default="approved")
payload: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
__table_args__ = (
CheckConstraint(
"kind IN ('baseline', 'first_statement', 'drive_revision', 'reforging', "
"'persona_candidate')",
name="kind_values",
),
CheckConstraint("status IN ('pending', 'approved', 'rejected')", name="status_values"),
)
class Character(Base):
"""角色:稳定身份、生命周期状态、道体基线与内驱力(WP-02)。"""
__tablename__ = "character"
id: Mapped[str] = mapped_column(
String, primary_key=True, default=lambda: new_id("character_")
)
personal_domain_id: Mapped[str] = mapped_column(
ForeignKey("personal_domain.id", ondelete="cascade"), nullable=False, index=True
)
name: Mapped[str] = mapped_column(String, nullable=False)
# 生命周期:灵胚 → 启镜 → 静修/归藏(可复照);死亡走两步确认,deceased 不可逆
status: Mapped[str] = mapped_column(String, nullable=False, default="draft")
origin: Mapped[str] = mapped_column(String, nullable=False, default="original")
based_on_real_person: Mapped[bool] = mapped_column(nullable=False, default=False)
real_person_attested: Mapped[bool] = mapped_column(nullable=False, default=False)
# 道体基线(identity_baseline):最小稳定身份锚点,普通对话/记忆不可静默覆盖
identity_baseline: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
# 内驱力(character_drives):声明式动机维度,用户可审阅编辑(ADR-018)
drives: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, nullable=False, default=list)
current_version_id: Mapped[str | None] = mapped_column(
ForeignKey("character_version.id", ondelete="set null", use_alter=True), nullable=True
)
death_requested_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
)
__table_args__ = (
CheckConstraint(
"status IN ('draft', 'active', 'resting', 'archived', 'death_pending', 'deceased')",
name="status_values",
),
CheckConstraint(
"origin IN ('original', 'serendipity', 'template', 'import', 'fork')",
name="origin_values",
),
)