from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field, model_validator class ConnectorMappingRule(BaseModel): model_config = ConfigDict(extra="forbid") source: str = Field(min_length=1, max_length=255) target: str = Field(min_length=1, max_length=255) required: bool = False default: Any = None class ConnectorMappingDefinition(BaseModel): model_config = ConfigDict(extra="forbid") version: str = Field(min_length=1, max_length=100) rules: list[ConnectorMappingRule] = Field(default_factory=list, max_length=500) class ConnectorValidationRule(BaseModel): model_config = ConfigDict(extra="forbid") kind: Literal["required", "one_of", "unique"] field: str = Field(min_length=1, max_length=255) values: list[Any] = Field(default_factory=list, max_length=500) severity: Literal["warning", "error"] = "error" code: str = Field(min_length=1, max_length=120) message: str = Field(min_length=1, max_length=500) @model_validator(mode="after") def validate_values(self) -> "ConnectorValidationRule": if self.kind == "one_of" and not self.values: raise ValueError("one_of validation requires allowed values") if self.kind != "one_of" and self.values: raise ValueError("Only one_of validation accepts values") return self class ConnectorDryRunMetadata(BaseModel): model_config = ConfigDict(extra="forbid") supported: bool = True simulation_supported: bool = True sample_rows: list[dict[str, Any]] = Field(default_factory=list, max_length=500) max_items: int = Field(default=500, ge=1, le=10_000) redacted_fields: list[str] = Field(default_factory=list, max_length=100) class ConnectorAuditMetadata(BaseModel): model_config = ConfigDict(extra="forbid") event_prefix: str = Field(min_length=1, max_length=120) expected_events: list[str] = Field(default_factory=list, max_length=100) evidence_fields: list[str] = Field(default_factory=list, max_length=100) class GovernedConnectorSpecification(BaseModel): model_config = ConfigDict(extra="forbid") provider: str = Field(min_length=1, max_length=120) protocol: str = Field(min_length=1, max_length=80) capabilities: list[str] = Field(min_length=1, max_length=100) input_schema: dict[str, Any] output_schema: dict[str, Any] mapping: ConnectorMappingDefinition validation_rules: list[ConnectorValidationRule] = Field( default_factory=list, max_length=500, ) dry_run: ConnectorDryRunMetadata audit: ConnectorAuditMetadata privacy_classification: Literal[ "public", "internal", "confidential", "restricted", ] = "internal" retention_class: str = Field(min_length=1, max_length=120) operational_limits: dict[str, Any] = Field(default_factory=dict) retry_policy: dict[str, Any] = Field(default_factory=dict) class ConnectorDefinitionUpsertRequest(BaseModel): model_config = ConfigDict(extra="forbid") definition_key: str = Field(pattern=r"^[a-z0-9][a-z0-9._-]{0,159}$") name: str = Field(min_length=1, max_length=300) description: str | None = Field(default=None, max_length=4000) origin: Literal["package", "local"] = "local" package_ref: str | None = Field(default=None, max_length=300) specification: GovernedConnectorSpecification @model_validator(mode="after") def package_provenance(self) -> "ConnectorDefinitionUpsertRequest": if self.origin == "package" and not self.package_ref: raise ValueError("Package definitions require package_ref") if self.origin == "local" and self.package_ref: raise ValueError("Local definitions cannot claim package_ref") return self class ConnectorDefinitionItem(BaseModel): id: str tenant_id: str definition_key: str name: str description: str | None = None status: str current_revision: int source_package: str | None = None local_definition: bool revision_id: str definition_hash: str origin: str package_ref: str | None = None specification: GovernedConnectorSpecification created_at: datetime updated_at: datetime class ConnectorDefinitionListResponse(BaseModel): items: list[ConnectorDefinitionItem] class ConnectorConfigurationCreateRequest(BaseModel): model_config = ConfigDict(extra="forbid") definition_id: str = Field(min_length=1, max_length=36) name: str = Field(min_length=1, max_length=300) endpoint_url: str | None = Field(default=None, max_length=1500) credential_ref: str | None = Field(default=None, max_length=500) local_overrides: dict[str, Any] = Field(default_factory=dict) ambiguity_policy: Literal["manual_review", "quarantine", "reject"] = ( "manual_review" ) status: Literal["draft", "active", "disabled"] = "draft" class ConnectorConfigurationUpdateRequest(BaseModel): model_config = ConfigDict(extra="forbid") expected_revision: int = Field(ge=1) name: str | None = Field(default=None, min_length=1, max_length=300) endpoint_url: str | None = Field(default=None, max_length=1500) credential_ref: str | None = Field(default=None, max_length=500) local_overrides: dict[str, Any] | None = None ambiguity_policy: Literal["manual_review", "quarantine", "reject"] | None = None status: Literal["draft", "active", "disabled"] | None = None adopt_latest_definition: bool = False class ConnectorConfigurationItem(BaseModel): id: str tenant_id: str definition_id: str definition_key: str definition_name: str name: str status: str endpoint_url: str | None = None credential_ref: str | None = None base_definition_revision: int latest_definition_revision: int update_available: bool local_overrides: dict[str, Any] protected_paths: list[str] effective_configuration: dict[str, Any] effective_hash: str resource_revision: int ambiguity_policy: str updated_at: datetime class ConnectorConfigurationListResponse(BaseModel): items: list[ConnectorConfigurationItem] class ConnectorRunRequest(BaseModel): model_config = ConfigDict(extra="forbid") idempotency_key: str = Field(min_length=1, max_length=255) input_rows: list[dict[str, Any]] | None = Field(default=None, max_length=500) external_revision: str | None = Field(default=None, max_length=255) class ConnectorRunItem(BaseModel): id: str tenant_id: str configuration_id: str mode: Literal["dry_run", "simulation"] idempotency_key: str status: str review_state: str definition_revision: int configuration_revision: int configuration_hash: str input_hash: str summary: dict[str, Any] effects: list[dict[str, Any]] diagnostics: list[dict[str, Any]] provenance: dict[str, Any] reviewed_by: str | None = None reviewed_at: datetime | None = None review_reason: str | None = None created_at: datetime class ConnectorRunListResponse(BaseModel): items: list[ConnectorRunItem] class ConnectorReviewRequest(BaseModel): model_config = ConfigDict(extra="forbid") decision: Literal["approved", "rejected"] reason: str = Field(min_length=5, max_length=1000)