"""Pydantic schemas for the FlowPilot "What we know" session facts. See FLOWPILOT-MIGRATION.md Section 4.2 for the data model rationale. """ from __future__ import annotations from datetime import datetime from typing import Literal from uuid import UUID from pydantic import BaseModel, Field # AI-emittable source types are a subset (`user_note` is engineer-only). AIEmittableSourceType = Literal["question", "diagnostic_check", "ai_synthesis"] SourceType = Literal["question", "diagnostic_check", "user_note", "ai_synthesis"] class SessionFactResponse(BaseModel): """A single fact card in the What-we-know panel.""" id: UUID session_id: UUID text: str source_type: SourceType source_ref: UUID | None source_summary: str | None created_by: UUID created_at: datetime updated_at: datetime # `editable` is computed server-side so the client doesn't have to # re-encode the editability rule. It mirrors the PATCH endpoint's # 403 policy: only user_note and ai_synthesis facts are editable. editable: bool model_config = {"from_attributes": False} class SessionFactListResponse(BaseModel): facts: list[SessionFactResponse] class SessionFactCreateRequest(BaseModel): """Engineer-created manual fact (the "+ Add a note" affordance). The endpoint hard-codes source_type="user_note" — manual creation cannot spoof a question/check origin. Source-type-bound creation goes through `/promote` instead. """ text: str = Field(..., min_length=1, max_length=2000) summary: str | None = Field(None, max_length=200) class SessionFactUpdateRequest(BaseModel): """Edit an existing fact's text or summary. The endpoint returns 403 when the fact's source_type is `question` or `diagnostic_check` — those facts must be edited at the source item. """ text: str | None = Field(None, min_length=1, max_length=2000) summary: str | None = Field(None, max_length=200) class SessionFactPromoteRequest(BaseModel): """Promote a question answer / check result into a fact. Two modes: - **Direct**: caller provides `proposed_text` (and optionally `proposed_summary`). The fact is persisted as-is. Used by the AI [PROMOTE] marker path and by the engineer's "edit then save" affordance. - **Synthesize**: caller provides `raw_input` (the engineer's typed answer or the check output) and the server drafts `text`/`summary` via the FactSynthesisService. The draft is persisted immediately for now — the supervisor-staging review is a future enhancement (out of scope per Section 12). Exactly one of `proposed_text` or `raw_input` must be set. """ source_type: AIEmittableSourceType source_ref: UUID | None = None proposed_text: str | None = Field(None, min_length=1, max_length=2000) proposed_summary: str | None = Field(None, max_length=200) raw_input: str | None = Field(None, min_length=1, max_length=10_000)