"""인물 정체성 YAML 스키마 (ENOS) — content/characters.yaml. kb_query 항목은 pre_kb 상태(status: pending)로 통과시킨다. """ from __future__ import annotations from typing import Optional from pydantic import BaseModel, ConfigDict, Field class KnowledgeItem(BaseModel): model_config = ConfigDict(extra="forbid") value: Optional[str] = None source: str = "static" # static | kb_query query: str = "" status: str = "" # kb_query면 "pending" (pre_kb) class Role(BaseModel): model_config = ConfigDict(extra="forbid") type: str = "" description: str = "" player_controlled: bool = False perspective: str = "" class Personality(BaseModel): model_config = ConfigDict(extra="forbid") traits: list[str] = Field(default_factory=list) strengths: list[str] = Field(default_factory=list) weaknesses: list[str] = Field(default_factory=list) class Speech(BaseModel): model_config = ConfigDict(extra="forbid") style: list[str] = Field(default_factory=list) tone: list[str] = Field(default_factory=list) pacing: list[str] = Field(default_factory=list) class CharacterSheet(BaseModel): # 원본 YAML에 부가 필드가 있어도 콘텐츠를 수정하지 않고 받아들인다 (읽기 전용 원칙) model_config = ConfigDict(extra="allow") id: str display_name: str aliases: list[str] = Field(default_factory=list) role: Role = Field(default_factory=Role) description: str = "" personality: Personality = Field(default_factory=Personality) speech: Speech = Field(default_factory=Speech) permanent_knowledge: list[KnowledgeItem] = Field(default_factory=list) immutable_constraints: list[KnowledgeItem] = Field(default_factory=list) class CharactersFile(BaseModel): model_config = ConfigDict(extra="allow") characters: list[CharacterSheet]