""" Pydantic schemas for the issue-core REST API. The TaskIngestionRequest schema matches activity-core's IssueSink TaskSpec payload. See: - SCOPE.md "TaskSpec payload" section - activity-core docs/adr/adr-001-event-bridge-architecture.md """ from typing import List, Literal, Optional from pydantic import BaseModel, ConfigDict, Field SourceType = Literal["rule", "instruction"] Priority = Literal["high", "medium", "low"] BackendName = Literal["gitea", "sqlite", "github"] class TaskIngestionRequest(BaseModel): """TaskSpec payload from activity-core's IssueSink (POST /issues/).""" model_config = ConfigDict(extra="forbid") title: str = Field(..., min_length=1, max_length=500) description: str = "" target_repo: str = Field(..., min_length=1) priority: Priority = "medium" labels: List[str] = Field(default_factory=list) due_in_days: Optional[int] = Field(default=None, ge=0) source_type: SourceType source_id: str = Field(..., min_length=1) triggering_event_id: str = Field( ..., min_length=1, description=( "Activity event UUID, or a stable scheduler/source key when no " "event row exists. Not a work-record UUID." ), ) activity_definition_id: str = Field(..., min_length=1) work_record_uuid: Optional[str] = Field( default=None, description=( "Optional work-record UUIDv7. When set, ingestion upserts a " "mapping row (work_record_uuid ↔ backend issue id)." ), ) work_record_id: Optional[str] = Field( default=None, description="Optional canonical work-record id (human-facing).", ) work_record_kind: Optional[str] = Field( default=None, description="Optional work-record kind (task, intake, …).", ) class TaskIngestionResponse(BaseModel): """Response returned to the emitter after a successful ingestion.""" issue_id: str issue_url: Optional[str] = None backend: BackendName class ErrorResponse(BaseModel): """Uniform error envelope.""" error: str detail: Optional[str] = None