"""PMFlow AI multi-agent operating runtime contracts. These contracts are the Step 1 kernel for moving Space PMFlow AI from a single answer-oriented agent run to a mission-oriented operating run. """ from __future__ import annotations from datetime import datetime, timezone from typing import Any, Literal from uuid import uuid4 from pydantic import BaseModel, ConfigDict, Field, field_validator OPERATING_MISSION_VERSION = "pmflow_ai_operating_mission.v1" OPERATING_RUN_VERSION = "pmflow_ai_operating_run.v1" AGENT_ASSIGNMENT_VERSION = "pmflow_ai_agent_assignment.v1" AGENT_WORK_PRODUCT_VERSION = "pmflow_ai_agent_work_product.v1" OPERATING_COMMAND_PAYLOAD_VERSION = "pmflow_ai_operating_command_payload.v1" AGENT_CAPABILITY_CONTRACT_VERSION = "pmflow_ai_agent_capability_contract.v1" PRODUCT_OBJECT_PROJECTION_VERSION = "pmflow_ai_product_object_projection.v1" PROJECT_WORLD_STATE_VERSION = "pmflow_ai_project_world_state.v1" PROJECT_WORLD_FACET_VERSION = "pmflow_ai_project_world_facet.v1" EVIDENCE_LEDGER_ENTRY_VERSION = "pmflow_ai_evidence_ledger_entry.v1" CONFLICT_LEDGER_ENTRY_VERSION = "pmflow_ai_conflict_ledger_entry.v1" AGENT_COUNCIL_REVIEW_VERSION = "pmflow_ai_agent_council_review.v1" AUTONOMY_GATE_VERSION = "pmflow_ai_autonomy_gate.v1" PREPARED_ACTION_PACKET_VERSION = "pmflow_ai_prepared_action_packet.v1" CONFIRMATION_REQUEST_VERSION = "pmflow_ai_confirmation_request.v1" EXECUTION_PREPARATION_VERSION = "pmflow_ai_execution_preparation.v1" OPERATING_CYCLE_TRIGGER_VERSION = "pmflow_ai_operating_cycle_trigger.v1" OUTCOME_OBSERVATION_VERSION = "pmflow_ai_outcome_observation.v1" LEARNING_LEDGER_VERSION = "pmflow_ai_learning_ledger.v1" OPERATING_CYCLE_STATE_VERSION = "pmflow_ai_operating_cycle_state.v1" OPERATING_MISSION_CRITIC_VERSION = "pmflow_ai_operating_mission_critic.v1" CUSTOMER_SAFE_CLAIM_LEDGER_VERSION = "pmflow_ai_customer_safe_claim_ledger.v1" CUSTOMER_SAFE_CLAIM_VERSION = "pmflow_ai_customer_safe_claim.v1" def new_operating_id(prefix: str) -> str: return f"{prefix}_{uuid4().hex[:12]}" def utc_now_iso() -> str: return datetime.now(timezone.utc).isoformat() def clean_text(value: Any, *, limit: int = 1000) -> str: return " ".join(str(value or "").strip().split())[:limit] class PMFlowAIOperatingScope(BaseModel): model_config = ConfigDict(extra="ignore") tenant_id: int | None = None user_id: int | None = None session_id: int | None = None object_refs: list[str] = Field(default_factory=list) current_project_id: int | None = None time_horizon: str = Field(default="current_operating_cycle", max_length=120) surface: str = Field(default="space", max_length=120) @field_validator("object_refs", mode="before") @classmethod def _normalize_object_refs(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows: text = clean_text(item, limit=160) if text and text not in out: out.append(text) return out[:12] @field_validator("time_horizon", "surface", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=120) class PMFlowAIOperatingMission(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = OPERATING_MISSION_VERSION mission_id: str = Field(default_factory=lambda: new_operating_id("omis"), max_length=80) mission_goal: str = Field(default="", max_length=1200) stage: Literal[ "idea", "research", "positioning", "mvp", "requirement", "changeset", "project_memory", "work_graph", "execution_bridge", "qa", "release", "growth", "revenue_learning", ] = "research" desired_outcome: str = Field(default="", max_length=1200) desired_business_outcome: str = Field(default="", max_length=1200) original_user_input: str = Field(default="", max_length=1600) project_scope: PMFlowAIOperatingScope = Field(default_factory=PMFlowAIOperatingScope) target_objects: list[dict[str, Any]] = Field(default_factory=list) deliverable_type: Literal[ "idea_seed", "research_note", "opportunity_brief", "product_hypothesis", "mvp_option", "requirement_draft", "changeset_draft", "task_draft", "execution_package", "qa_plan", "release_signal", "growth_experiment", "revenue_signal", "operating_decision", ] = "opportunity_brief" action_ladder_level: Literal[ "answer", "diagnose", "recommend", "prepare_object", "prepare_execution", "execute_after_confirmation", ] = "prepare_object" success_criteria: list[str] = Field(default_factory=list) autonomy_level: Literal[ "answer_only", "read_and_diagnose", "propose_plan", "prepare_changes", "execute_after_confirmation", "autonomous_cycle_within_policy", ] = "prepare_changes" decision_boundary: list[str] = Field(default_factory=list) blocked_boundaries: list[str] = Field(default_factory=list) required_agents: list[str] = Field(default_factory=list) next_cycle_policy: dict[str, Any] = Field(default_factory=dict) created_at: str = Field(default_factory=utc_now_iso) @field_validator("mission_id", "mission_goal", "desired_outcome", "desired_business_outcome", "original_user_input", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=1600) @field_validator("success_criteria", "decision_boundary", "blocked_boundaries", "required_agents", mode="before") @classmethod def _normalize_list(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows: text = clean_text(item, limit=260) if text and text not in out: out.append(text) return out[:16] @field_validator("target_objects", mode="before") @classmethod def _normalize_target_objects(cls, value: Any) -> list[dict[str, Any]]: rows = value if isinstance(value, list) else [] out: list[dict[str, Any]] = [] for item in rows[:12]: if not isinstance(item, dict): continue object_ref = clean_text(item.get("object_ref") or item.get("ref") or "", limit=160) title = clean_text(item.get("title") or item.get("display_name") or object_ref, limit=240) object_type = clean_text(item.get("object_type") or item.get("type") or (object_ref.split(":", 1)[0] if ":" in object_ref else ""), limit=80) if object_ref or title: out.append( { "object_ref": object_ref, "object_type": object_type or "business_object", "title": title, "binding_strength": clean_text(item.get("binding_strength") or "mission_scope", limit=80), } ) return out class PMFlowAIAgentCapabilityContract(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = AGENT_CAPABILITY_CONTRACT_VERSION agent_id: str = Field(..., min_length=1, max_length=80) display_name: str = Field(default="", max_length=120) responsibility: str = Field(default="", max_length=500) input_contract: list[str] = Field(default_factory=list) output_contract: list[str] = Field(default_factory=list) allowed_tools: list[str] = Field(default_factory=list) write_permissions: list[str] = Field(default_factory=list) autonomy_limit: str = Field(default="propose_plan", max_length=120) critic_checks: list[str] = Field(default_factory=list) customer_safe_projection: dict[str, Any] = Field(default_factory=dict) @field_validator("agent_id", "display_name", "responsibility", "autonomy_limit", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=500) @field_validator("input_contract", "output_contract", "allowed_tools", "write_permissions", "critic_checks", mode="before") @classmethod def _normalize_list(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows: text = clean_text(item, limit=260) if text and text not in out: out.append(text) return out[:24] class PMFlowAIAgentAssignment(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = AGENT_ASSIGNMENT_VERSION assignment_id: str = Field(default_factory=lambda: new_operating_id("aasn"), max_length=80) agent_id: str = Field(default="", max_length=80) role_goal: str = Field(default="", max_length=1000) input_refs: list[str] = Field(default_factory=list) expected_work_product: str = Field(default="", max_length=120) status: Literal["assigned", "completed", "blocked", "skipped"] = "assigned" autonomy_limit: str = Field(default="propose_plan", max_length=120) created_at: str = Field(default_factory=utc_now_iso) @field_validator("assignment_id", "agent_id", "role_goal", "expected_work_product", "autonomy_limit", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=1000) @field_validator("input_refs", mode="before") @classmethod def _normalize_refs(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] return [clean_text(item, limit=160) for item in rows[:16] if clean_text(item, limit=160)] class PMFlowAIOperatingMissionCriticResult(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = OPERATING_MISSION_CRITIC_VERSION critic_id: str = Field(default_factory=lambda: new_operating_id("omcr"), max_length=80) mission_ok: bool = True missing_capability_roles: list[str] = Field(default_factory=list) over_selected_roles: list[str] = Field(default_factory=list) needs_replan: bool = False reason: str = Field(default="", max_length=1200) evidence_alignment: dict[str, Any] = Field(default_factory=dict) created_at: str = Field(default_factory=utc_now_iso) @field_validator("critic_id", "reason", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=1200) @field_validator("missing_capability_roles", "over_selected_roles", mode="before") @classmethod def _normalize_roles(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:16]: text = clean_text(item, limit=100) if text and text not in out: out.append(text) return out class PMFlowAIAgentWorkProduct(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = AGENT_WORK_PRODUCT_VERSION work_product_id: str = Field(default_factory=lambda: new_operating_id("awp"), max_length=80) assignment_id: str = Field(default="", max_length=80) agent_id: str = Field(default="", max_length=80) product_type: str = Field(default="", max_length=120) status: Literal["draft", "ready", "blocked", "needs_followup"] = "draft" summary: str = Field(default="", max_length=1200) structured_output: dict[str, Any] = Field(default_factory=dict) claims: list[dict[str, Any]] = Field(default_factory=list) recommendations: list[dict[str, Any]] = Field(default_factory=list) prepared_actions: list[dict[str, Any]] = Field(default_factory=list) blocked_actions: list[dict[str, Any]] = Field(default_factory=list) evidence_refs: list[str] = Field(default_factory=list) verification: dict[str, Any] = Field(default_factory=dict) created_at: str = Field(default_factory=utc_now_iso) @field_validator("work_product_id", "assignment_id", "agent_id", "product_type", "summary", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=1200) @field_validator("evidence_refs", mode="before") @classmethod def _normalize_refs(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] return [clean_text(item, limit=160) for item in rows[:24] if clean_text(item, limit=160)] class PMFlowAIProductObjectProjection(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = PRODUCT_OBJECT_PROJECTION_VERSION projection_id: str = Field(default_factory=lambda: new_operating_id("pobj"), max_length=80) object_type: Literal[ "IdeaSeed", "ResearchNote", "OpportunityBrief", "ProductHypothesis", "MVPOption", "RequirementDraft", "ChangeSetDraft", "TaskDraft", "ExecutionPackage", "QAPlan", "ReleaseSignal", "GrowthExperiment", "RevenueSignal", ] = "OpportunityBrief" title: str = Field(default="", max_length=240) summary: str = Field(default="", max_length=1200) source_agents: list[str] = Field(default_factory=list) target_refs: list[str] = Field(default_factory=list) evidence_refs: list[str] = Field(default_factory=list) fields: dict[str, Any] = Field(default_factory=dict) action_ladder_level: str = Field(default="prepare_object", max_length=80) confirmation_required: bool = True status: Literal["draft", "ready_for_review", "blocked"] = "draft" blocked_boundaries: list[str] = Field(default_factory=list) next_cycle_policy: dict[str, Any] = Field(default_factory=dict) created_at: str = Field(default_factory=utc_now_iso) @field_validator("projection_id", "title", "summary", "action_ladder_level", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=1200) @field_validator("source_agents", "target_refs", "evidence_refs", "blocked_boundaries", mode="before") @classmethod def _normalize_text_list(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:24]: text = clean_text(item, limit=220) if text and text not in out: out.append(text) return out class PMFlowAIProjectWorldFacet(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = PROJECT_WORLD_FACET_VERSION facet_key: str = Field(default="", max_length=100) display_label: str = Field(default="", max_length=160) status: Literal["ready", "partial", "missing", "unavailable"] = "missing" summary: str = Field(default="", max_length=1000) facts: list[dict[str, Any]] = Field(default_factory=list) evidence_refs: list[str] = Field(default_factory=list) source_refs: list[dict[str, Any]] = Field(default_factory=list) gaps: list[str] = Field(default_factory=list) @field_validator("facet_key", "display_label", "summary", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=1000) @field_validator("evidence_refs", "gaps", mode="before") @classmethod def _normalize_refs(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] return [clean_text(item, limit=180) for item in rows[:24] if clean_text(item, limit=180)] class PMFlowAIEvidenceLedgerEntry(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = EVIDENCE_LEDGER_ENTRY_VERSION evidence_id: str = Field(default_factory=lambda: new_operating_id("evd"), max_length=80) project_ref: str = Field(default="", max_length=160) facet_key: str = Field(default="", max_length=100) source_ref: str = Field(default="", max_length=200) summary: str = Field(default="", max_length=800) confidence: Literal["high", "medium", "low", "unknown"] = "unknown" captured_at: str = Field(default_factory=utc_now_iso) @field_validator("evidence_id", "project_ref", "facet_key", "source_ref", "summary", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=800) class PMFlowAICustomerSafeClaim(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = CUSTOMER_SAFE_CLAIM_VERSION claim_id: str = Field(default_factory=lambda: new_operating_id("csc"), max_length=80) claim_text: str = Field(default="", max_length=900) source_kind: str = Field(default="", max_length=100) source_label: str = Field(default="", max_length=160) source_ref: str = Field(default="", max_length=180) confidence: Literal["high", "medium", "low", "unknown"] = "unknown" evidence_refs: list[str] = Field(default_factory=list) object_refs: list[str] = Field(default_factory=list) allowed_to_state: bool = True created_at: str = Field(default_factory=utc_now_iso) @field_validator("claim_id", "claim_text", "source_kind", "source_label", "source_ref", "confidence", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=900) @field_validator("evidence_refs", "object_refs", mode="before") @classmethod def _normalize_refs(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:16]: text = clean_text(item, limit=180) if text and text not in out: out.append(text) return out class PMFlowAICustomerSafeClaimLedger(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = CUSTOMER_SAFE_CLAIM_LEDGER_VERSION visibility: Literal["customer"] = "customer" ledger_id: str = Field(default_factory=lambda: new_operating_id("cscl"), max_length=80) mission_id: str = Field(default="", max_length=80) status: Literal["ready", "partial", "empty", "blocked"] = "empty" claims: list[PMFlowAICustomerSafeClaim] = Field(default_factory=list) claim_count: int = 0 source_counts: dict[str, int] = Field(default_factory=dict) source_refs: list[dict[str, Any]] = Field(default_factory=list) evidence_refs: list[str] = Field(default_factory=list) blocked_reason: str = Field(default="", max_length=500) created_at: str = Field(default_factory=utc_now_iso) @field_validator("ledger_id", "mission_id", "status", "blocked_reason", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=500) @field_validator("evidence_refs", mode="before") @classmethod def _normalize_refs(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:30]: text = clean_text(item, limit=180) if text and text not in out: out.append(text) return out class PMFlowAIConflictLedgerEntry(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = CONFLICT_LEDGER_ENTRY_VERSION conflict_id: str = Field(default_factory=lambda: new_operating_id("cflt"), max_length=80) conflict_type: str = Field(default="", max_length=120) involved_agents: list[str] = Field(default_factory=list) summary: str = Field(default="", max_length=800) resolution: Literal["accepted", "resolved", "blocked"] = "resolved" resolution_reason: str = Field(default="", max_length=800) severity: Literal["low", "medium", "high"] = "medium" source_refs: list[str] = Field(default_factory=list) created_at: str = Field(default_factory=utc_now_iso) @field_validator("conflict_id", "conflict_type", "summary", "resolution_reason", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=800) @field_validator("involved_agents", "source_refs", mode="before") @classmethod def _normalize_refs(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:16]: text = clean_text(item, limit=160) if text and text not in out: out.append(text) return out class PMFlowAIAgentCouncilReview(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = AGENT_COUNCIL_REVIEW_VERSION visibility: Literal["customer"] = "customer" review_id: str = Field(default_factory=lambda: new_operating_id("acrv"), max_length=80) status: Literal["passed", "passed_with_conflicts", "blocked"] = "passed" role_reviews: list[dict[str, Any]] = Field(default_factory=list) conflict_ledger: list[PMFlowAIConflictLedgerEntry] = Field(default_factory=list) resolved_conflicts: list[dict[str, Any]] = Field(default_factory=list) risk_vetoes: list[dict[str, Any]] = Field(default_factory=list) priority_order: list[dict[str, Any]] = Field(default_factory=list) resolution_summary: str = Field(default="", max_length=1000) created_at: str = Field(default_factory=utc_now_iso) @field_validator("review_id", "status", "resolution_summary", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=1000) class PMFlowAIAutonomyDecision(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = AUTONOMY_GATE_VERSION decision_id: str = Field(default_factory=lambda: new_operating_id("adec"), max_length=80) status: Literal["suggest_only", "prepared_waiting_confirmation", "blocked"] = "suggest_only" autonomy_level: str = Field(default="", max_length=120) safe_suggestions: list[dict[str, Any]] = Field(default_factory=list) prepared_action_packets: list[dict[str, Any]] = Field(default_factory=list) confirmation_requests: list[dict[str, Any]] = Field(default_factory=list) execution_preparations: list[dict[str, Any]] = Field(default_factory=list) blocked_actions: list[dict[str, Any]] = Field(default_factory=list) policy_findings: list[str] = Field(default_factory=list) prohibited_side_effects: list[str] = Field(default_factory=list) created_at: str = Field(default_factory=utc_now_iso) @field_validator("decision_id", "status", "autonomy_level", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=160) @field_validator("policy_findings", "prohibited_side_effects", mode="before") @classmethod def _normalize_text_list(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:16]: text = clean_text(item, limit=260) if text and text not in out: out.append(text) return out class PMFlowAIPreparedActionPacket(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = PREPARED_ACTION_PACKET_VERSION prepared_action_id: str = Field(default_factory=lambda: new_operating_id("pact"), max_length=80) action_family: Literal[ "task_change", "experiment_plan", "project_memory_candidate", "product_object_projection", "research_recovery", "execution_handoff_package", ] = "task_change" title: str = Field(default="", max_length=240) target_ref: str = Field(default="", max_length=160) source_agent: str = Field(default="", max_length=80) source_action_type: str = Field(default="", max_length=120) payload: dict[str, Any] = Field(default_factory=dict) evidence_refs: list[str] = Field(default_factory=list) confirmation_required: bool = True confirmation_request_id: str | None = Field(default=None, max_length=80) status: Literal["prepared_waiting_confirmation", "blocked_by_policy"] = "prepared_waiting_confirmation" prohibited_side_effects: list[str] = Field(default_factory=list) created_at: str = Field(default_factory=utc_now_iso) @field_validator("prepared_action_id", "title", "target_ref", "source_agent", "source_action_type", "confirmation_request_id", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str | None: if value is None: return None return clean_text(value, limit=240) @field_validator("evidence_refs", "prohibited_side_effects", mode="before") @classmethod def _normalize_text_list(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:16]: text = clean_text(item, limit=220) if text and text not in out: out.append(text) return out class PMFlowAIConfirmationRequest(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = CONFIRMATION_REQUEST_VERSION confirmation_request_id: str = Field(default_factory=lambda: new_operating_id("creq"), max_length=80) title: str = Field(default="", max_length=240) target_ref: str = Field(default="", max_length=160) requested_for: list[str] = Field(default_factory=list) confirmation_reason: str = Field(default="", max_length=600) required_before: list[str] = Field(default_factory=list) action_label: str = Field(default="", max_length=120) action_type: str = Field(default="", max_length=120) action_payload: dict[str, Any] = Field(default_factory=dict) can_execute_after_confirmation: bool = False status: Literal["waiting_for_user_confirmation", "blocked"] = "waiting_for_user_confirmation" created_at: str = Field(default_factory=utc_now_iso) @field_validator("confirmation_request_id", "title", "target_ref", "confirmation_reason", "action_label", "action_type", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=600) @field_validator("requested_for", "required_before", mode="before") @classmethod def _normalize_text_list(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:16]: text = clean_text(item, limit=220) if text and text not in out: out.append(text) return out class PMFlowAIConfirmationExecutionRequest(BaseModel): """Customer confirmation execution request. The server always resolves the real action contract from the persisted assistant message. Client payload is only used as an idempotency/audit hint. """ action_payload: dict[str, Any] = Field(default_factory=dict) supplement_text: str = Field(default="", max_length=2000) client_message_id: str = Field(default="", max_length=80) @field_validator("supplement_text", "client_message_id", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=2000) class PMFlowAIExecutionPreparation(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = EXECUTION_PREPARATION_VERSION execution_preparation_id: str = Field(default_factory=lambda: new_operating_id("eprep"), max_length=80) target_ref: str = Field(default="", max_length=160) readiness: Literal["prepared_waiting_confirmation", "blocked_by_missing_context"] = "prepared_waiting_confirmation" handoff_package: dict[str, Any] = Field(default_factory=dict) required_confirmations: list[str] = Field(default_factory=list) blocked_actions: list[dict[str, Any]] = Field(default_factory=list) prohibited_side_effects: list[str] = Field(default_factory=list) created_at: str = Field(default_factory=utc_now_iso) @field_validator("execution_preparation_id", "target_ref", "readiness", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=160) @field_validator("required_confirmations", "prohibited_side_effects", mode="before") @classmethod def _normalize_text_list(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:16]: text = clean_text(item, limit=220) if text and text not in out: out.append(text) return out class PMFlowAIOperatingCycleTrigger(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = OPERATING_CYCLE_TRIGGER_VERSION trigger_id: str = Field(default_factory=lambda: new_operating_id("otrg"), max_length=80) trigger_type: Literal[ "user_turn", "signal", "project_creation", "task_update", "execution_result", "daily_review", "weekly_strategy_review", ] = "user_turn" source_ref: str = Field(default="", max_length=180) project_ref: str = Field(default="", max_length=160) payload: dict[str, Any] = Field(default_factory=dict) created_at: str = Field(default_factory=utc_now_iso) @field_validator("trigger_id", "trigger_type", "source_ref", "project_ref", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=180) class PMFlowAIOutcomeObservation(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = OUTCOME_OBSERVATION_VERSION observation_id: str = Field(default_factory=lambda: new_operating_id("oobs"), max_length=80) outcome_type: Literal[ "recommendation_decision", "execution_result", "experiment_result", "user_preference", ] = "recommendation_decision" outcome_status: Literal[ "pending", "accepted", "rejected", "succeeded", "failed", "partial", "observed", ] = "pending" source_ref: str = Field(default="", max_length=180) target_ref: str = Field(default="", max_length=160) summary: str = Field(default="", max_length=800) evidence_refs: list[str] = Field(default_factory=list) learning_signals: dict[str, Any] = Field(default_factory=dict) observed_at: str = Field(default_factory=utc_now_iso) @field_validator("observation_id", "outcome_type", "outcome_status", "source_ref", "target_ref", "summary", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=800) @field_validator("evidence_refs", mode="before") @classmethod def _normalize_text_list(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:16]: text = clean_text(item, limit=220) if text and text not in out: out.append(text) return out class PMFlowAILearningLedger(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = LEARNING_LEDGER_VERSION ledger_id: str = Field(default_factory=lambda: new_operating_id("lgr"), max_length=80) scope_ref: str = Field(default="", max_length=160) learning_items: list[dict[str, Any]] = Field(default_factory=list) decision_outcomes: list[dict[str, Any]] = Field(default_factory=list) experiment_results: list[dict[str, Any]] = Field(default_factory=list) execution_outcomes: list[dict[str, Any]] = Field(default_factory=list) user_preferences: list[dict[str, Any]] = Field(default_factory=list) consumed_memory_refs: list[str] = Field(default_factory=list) updated_at: str = Field(default_factory=utc_now_iso) @field_validator("ledger_id", "scope_ref", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=160) @field_validator("consumed_memory_refs", mode="before") @classmethod def _normalize_text_list(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:24]: text = clean_text(item, limit=220) if text and text not in out: out.append(text) return out class PMFlowAIOperatingCycleState(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = OPERATING_CYCLE_STATE_VERSION cycle_id: str = Field(default_factory=lambda: new_operating_id("ocyc"), max_length=80) trigger: PMFlowAIOperatingCycleTrigger supported_triggers: list[dict[str, Any]] = Field(default_factory=list) consumed_learning_ledger: PMFlowAILearningLedger = Field(default_factory=PMFlowAILearningLedger) outcome_observations: list[PMFlowAIOutcomeObservation] = Field(default_factory=list) learning_ledger: PMFlowAILearningLedger = Field(default_factory=PMFlowAILearningLedger) next_cycle_triggers: list[dict[str, Any]] = Field(default_factory=list) created_at: str = Field(default_factory=utc_now_iso) @field_validator("cycle_id", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=80) class PMFlowAIProjectWorldStatePacket(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = PROJECT_WORLD_STATE_VERSION visibility: Literal["customer"] = "customer" world_state_id: str = Field(default_factory=lambda: new_operating_id("pws"), max_length=80) primary_project_ref: str = Field(default="", max_length=160) project_refs: list[str] = Field(default_factory=list) status: Literal["ready", "partial", "missing_scope", "unavailable"] = "missing_scope" facets: list[PMFlowAIProjectWorldFacet] = Field(default_factory=list) project_operating_evidence_packets: list[dict[str, Any]] = Field(default_factory=list) evidence_ledger: list[PMFlowAIEvidenceLedgerEntry] = Field(default_factory=list) customer_safe_claim_ledger: PMFlowAICustomerSafeClaimLedger | None = None coverage: dict[str, Any] = Field(default_factory=dict) remaining_gaps: list[str] = Field(default_factory=list) created_at: str = Field(default_factory=utc_now_iso) @field_validator("world_state_id", "primary_project_ref", "status", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=160) @field_validator("project_refs", "remaining_gaps", mode="before") @classmethod def _normalize_refs(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] out: list[str] = [] for item in rows[:30]: text = clean_text(item, limit=180) if text and text not in out: out.append(text) return out class PMFlowAIOperatingRunEvent(BaseModel): model_config = ConfigDict(extra="ignore") event_id: str = Field(default_factory=lambda: new_operating_id("oevt"), max_length=80) event_type: str = Field(default="", max_length=120) summary: str = Field(default="", max_length=1000) agent_id: str | None = Field(default=None, max_length=80) assignment_id: str | None = Field(default=None, max_length=80) created_at: str = Field(default_factory=utc_now_iso) @field_validator("event_id", "event_type", "summary", "agent_id", "assignment_id", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str | None: if value is None: return None return clean_text(value, limit=1000) class PMFlowAIOperatingPlan(BaseModel): model_config = ConfigDict(extra="ignore") strategic_read: str = Field(default="", max_length=1200) highest_leverage_move: str = Field(default="", max_length=1200) next_cycle_plan: list[dict[str, Any]] = Field(default_factory=list) safe_suggestions: list[dict[str, Any]] = Field(default_factory=list) prepared_changes: list[dict[str, Any]] = Field(default_factory=list) requires_confirmation: list[dict[str, Any]] = Field(default_factory=list) blocked_actions: list[dict[str, Any]] = Field(default_factory=list) evidence_boundary: list[str] = Field(default_factory=list) council_review: dict[str, Any] = Field(default_factory=dict) conflict_ledger: list[dict[str, Any]] = Field(default_factory=list) resolved_conflicts: list[dict[str, Any]] = Field(default_factory=list) risk_vetoes: list[dict[str, Any]] = Field(default_factory=list) priority_order: list[dict[str, Any]] = Field(default_factory=list) autonomy_gate: dict[str, Any] = Field(default_factory=dict) prepared_action_packets: list[dict[str, Any]] = Field(default_factory=list) confirmation_requests: list[dict[str, Any]] = Field(default_factory=list) execution_preparations: list[dict[str, Any]] = Field(default_factory=list) product_object_projections: list[dict[str, Any]] = Field(default_factory=list) operating_cycle: dict[str, Any] = Field(default_factory=dict) consumed_learning_ledger: dict[str, Any] = Field(default_factory=dict) learning_updates: list[dict[str, Any]] = Field(default_factory=list) next_cycle_triggers: list[dict[str, Any]] = Field(default_factory=list) @field_validator("strategic_read", "highest_leverage_move", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=1200) @field_validator("evidence_boundary", mode="before") @classmethod def _normalize_boundary(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] return [clean_text(item, limit=360) for item in rows[:12] if clean_text(item, limit=360)] class PMFlowAIOperatingRunPacket(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = OPERATING_RUN_VERSION visibility: Literal["customer"] = "customer" run_id: str = Field(default_factory=lambda: new_operating_id("orun"), max_length=80) mission: PMFlowAIOperatingMission status: Literal["active", "completed", "blocked", "waiting_for_confirmation"] = "active" assignments: list[PMFlowAIAgentAssignment] = Field(default_factory=list) project_world_state: PMFlowAIProjectWorldStatePacket | None = None customer_safe_claim_ledger: PMFlowAICustomerSafeClaimLedger | None = None work_products: list[PMFlowAIAgentWorkProduct] = Field(default_factory=list) agent_council_review: PMFlowAIAgentCouncilReview | None = None autonomy_gate: PMFlowAIAutonomyDecision | None = None operating_cycle: PMFlowAIOperatingCycleState | None = None product_object_projections: list[PMFlowAIProductObjectProjection] = Field(default_factory=list) operating_plan: PMFlowAIOperatingPlan = Field(default_factory=PMFlowAIOperatingPlan) events: list[PMFlowAIOperatingRunEvent] = Field(default_factory=list) delivery_ref: dict[str, Any] = Field(default_factory=dict) created_at: str = Field(default_factory=utc_now_iso) completed_at: str | None = None @field_validator("run_id", "status", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=120) class PMFlowAIOperatingCommandPayload(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = OPERATING_COMMAND_PAYLOAD_VERSION visibility: Literal["customer"] = "customer" mission: dict[str, Any] = Field(default_factory=dict) project_focus: dict[str, Any] = Field(default_factory=dict) project_world_state: dict[str, Any] = Field(default_factory=dict) agent_council: list[dict[str, Any]] = Field(default_factory=list) agent_council_review: dict[str, Any] = Field(default_factory=dict) conflict_ledger: list[dict[str, Any]] = Field(default_factory=list) priority_order: list[dict[str, Any]] = Field(default_factory=list) risk_vetoes: list[dict[str, Any]] = Field(default_factory=list) autonomy_gate: dict[str, Any] = Field(default_factory=dict) operating_cycle: dict[str, Any] = Field(default_factory=dict) consumed_learning_ledger: dict[str, Any] = Field(default_factory=dict) confirmation_requests: list[dict[str, Any]] = Field(default_factory=list) execution_preparations: list[dict[str, Any]] = Field(default_factory=list) product_object_projections: list[dict[str, Any]] = Field(default_factory=list) strategic_read: str = Field(default="", max_length=1200) operating_plan: dict[str, Any] = Field(default_factory=dict) prepared_actions: list[dict[str, Any]] = Field(default_factory=list) blocked_actions: list[dict[str, Any]] = Field(default_factory=list) evidence_boundary: list[str] = Field(default_factory=list) evidence_ledger: list[dict[str, Any]] = Field(default_factory=list) customer_safe_claim_ledger: dict[str, Any] = Field(default_factory=dict) learning_updates: list[dict[str, Any]] = Field(default_factory=list) next_cycle: dict[str, Any] = Field(default_factory=dict) @field_validator("strategic_read", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=1200) @field_validator("evidence_boundary", mode="before") @classmethod def _normalize_boundary(cls, value: Any) -> list[str]: rows = value if isinstance(value, list) else [] return [clean_text(item, limit=360) for item in rows[:12] if clean_text(item, limit=360)] __all__ = [ "PMFlowAIAgentAssignment", "PMFlowAIAgentCapabilityContract", "PMFlowAIAgentCouncilReview", "PMFlowAIAgentWorkProduct", "PMFlowAIAutonomyDecision", "PMFlowAICustomerSafeClaim", "PMFlowAICustomerSafeClaimLedger", "PMFlowAIConfirmationExecutionRequest", "PMFlowAIConfirmationRequest", "PMFlowAIConflictLedgerEntry", "PMFlowAIEvidenceLedgerEntry", "PMFlowAIExecutionPreparation", "PMFlowAILearningLedger", "PMFlowAIOperatingCycleState", "PMFlowAIOperatingCycleTrigger", "PMFlowAIOperatingCommandPayload", "PMFlowAIOperatingMission", "PMFlowAIOperatingPlan", "PMFlowAIOperatingRunEvent", "PMFlowAIOperatingRunPacket", "PMFlowAIOperatingScope", "PMFlowAIOutcomeObservation", "PMFlowAIPreparedActionPacket", "PMFlowAIProjectWorldFacet", "PMFlowAIProjectWorldStatePacket", "PMFlowAIProductObjectProjection", "clean_text", "new_operating_id", "utc_now_iso", ]