from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field, model_validator class StrictModel(BaseModel): model_config = ConfigDict(extra="forbid") class FilePlanNodeWriteRequest(StrictModel): node_id: str = Field(min_length=1, max_length=255) code: str = Field(min_length=1, max_length=120) label: str = Field(min_length=1, max_length=500) parent_node_id: str | None = Field(default=None, max_length=255) description: str | None = Field(default=None, max_length=10_000) active: bool = True valid_from: datetime | None = None valid_to: datetime | None = None recorded_at: datetime expected_revision: int | None = Field(default=None, ge=1) idempotency_key: str = Field(min_length=1, max_length=255) institutional_context: dict[str, Any] = Field(default_factory=dict) @model_validator(mode="after") def validate_interval(self): if self.valid_from and self.valid_to and self.valid_to <= self.valid_from: raise ValueError("valid_to must be after valid_from") return self class RecordClassWriteRequest(StrictModel): class_id: str = Field(min_length=1, max_length=255) file_plan_node_id: str = Field(min_length=1, max_length=255) key: str = Field(min_length=1, max_length=120) label: str = Field(min_length=1, max_length=500) description: str | None = Field(default=None, max_length=10_000) metadata_requirements: list[str] = Field(default_factory=list, max_length=100) allowed_source_types: list[str] = Field(default_factory=list, max_length=100) retention_period_days: int | None = Field(default=None, ge=0, le=365_000) closure_trigger: str | None = Field(default=None, max_length=255) access_mode: Literal["tenant", "restricted"] = "tenant" active: bool = True valid_from: datetime | None = None valid_to: datetime | None = None recorded_at: datetime expected_revision: int | None = Field(default=None, ge=1) idempotency_key: str = Field(min_length=1, max_length=255) institutional_context: dict[str, Any] = Field(default_factory=dict) @model_validator(mode="after") def validate_interval(self): if self.valid_from and self.valid_to and self.valid_to <= self.valid_from: raise ValueError("valid_to must be after valid_from") return self class RecordCreateRequest(StrictModel): record_id: str | None = Field(default=None, max_length=255) record_number: str = Field(min_length=1, max_length=255) class_id: str = Field(min_length=1, max_length=255) file_plan_node_id: str = Field(min_length=1, max_length=255) title: str = Field(min_length=1, max_length=500) description: str | None = Field(default=None, max_length=20_000) state: Literal["planned", "open"] = "open" source_authority_mode: Literal[ "native_authoritative", "external_authoritative", "external_mirror", "governed_sync", "governance_overlay", "linked_reference", ] = "native_authoritative" access_mode: Literal["tenant", "restricted"] = "tenant" purpose: str = Field(min_length=1, max_length=255) classification: str | None = Field(default=None, max_length=120) responsible_unit_id: str | None = Field(default=None, max_length=255) responsible_function_id: str | None = Field(default=None, max_length=255) external_reference: dict[str, Any] = Field(default_factory=dict) institutional_context: dict[str, Any] = Field(default_factory=dict) valid_from: datetime | None = None valid_to: datetime | None = None recorded_at: datetime change_reason: str = Field(min_length=1, max_length=2_000) idempotency_key: str = Field(min_length=1, max_length=255) @model_validator(mode="after") def validate_interval(self): if self.valid_from and self.valid_to and self.valid_to <= self.valid_from: raise ValueError("valid_to must be after valid_from") return self class RecordUpdateRequest(StrictModel): expected_revision: int = Field(ge=1) title: str | None = Field(default=None, min_length=1, max_length=500) description: str | None = Field(default=None, max_length=20_000) class_id: str | None = Field(default=None, min_length=1, max_length=255) file_plan_node_id: str | None = Field(default=None, min_length=1, max_length=255) state: Literal["planned", "open"] | None = None access_mode: Literal["tenant", "restricted"] | None = None purpose: str = Field(min_length=1, max_length=255) classification: str | None = Field(default=None, max_length=120) responsible_unit_id: str | None = Field(default=None, max_length=255) responsible_function_id: str | None = Field(default=None, max_length=255) institutional_context: dict[str, Any] | None = None valid_from: datetime | None = None valid_to: datetime | None = None recorded_at: datetime change_reason: str = Field(min_length=1, max_length=2_000) idempotency_key: str = Field(min_length=1, max_length=255) @model_validator(mode="after") def validate_interval(self): if self.valid_from and self.valid_to and self.valid_to <= self.valid_from: raise ValueError("valid_to must be after valid_from") return self class RecordVolumeCreateRequest(StrictModel): volume_id: str | None = Field(default=None, max_length=255) label: str = Field(min_length=1, max_length=500) valid_from: datetime | None = None valid_to: datetime | None = None recorded_at: datetime purpose: str = Field(min_length=1, max_length=255) idempotency_key: str = Field(min_length=1, max_length=255) class RecordSourceLocatorRequest(StrictModel): source_module: str = Field(min_length=1, max_length=100) resource_type: str = Field(min_length=1, max_length=120) resource_id: str = Field(min_length=1, max_length=500) source_revision: str = Field(min_length=1, max_length=255) metadata: dict[str, Any] = Field(default_factory=dict) class RecordItemCreateRequest(StrictModel): source: RecordSourceLocatorRequest purpose: str = Field(min_length=1, max_length=255) filing_reason: str = Field(min_length=1, max_length=2_000) idempotency_key: str = Field(min_length=1, max_length=255) volume_id: str | None = Field(default=None, max_length=255) relationship: str = Field(default="contains", min_length=1, max_length=120) institutional_context: dict[str, Any] = Field(default_factory=dict) metadata: dict[str, Any] = Field(default_factory=dict) class RecordLifecycleActionRequest(StrictModel): expected_revision: int = Field(ge=1) purpose: str = Field(min_length=1, max_length=255) reason: str = Field(min_length=1, max_length=2_000) recorded_at: datetime idempotency_key: str = Field(min_length=1, max_length=255) class RecordCloseRequest(RecordLifecycleActionRequest): retention_trigger_at: datetime | None = None restart_retention: bool = False class RecordAppraisalRequest(RecordLifecycleActionRequest): outcome: Literal["retain", "transfer", "destroy", "reclassify"] policy_refs: list[str] = Field(default_factory=list, max_length=100) override_retention_not_due: bool = False class RecordHoldCreateRequest(StrictModel): hold_id: str | None = Field(default=None, max_length=255) expected_record_revision: int = Field(ge=1) reason: str = Field(min_length=1, max_length=10_000) authority: str = Field(min_length=1, max_length=500) purpose: str = Field(min_length=1, max_length=255) scope: dict[str, Any] = Field(default_factory=dict) effective_from: datetime | None = None effective_to: datetime | None = None policy_refs: list[str] = Field(default_factory=list, max_length=100) institutional_context: dict[str, Any] = Field(default_factory=dict) recorded_at: datetime idempotency_key: str = Field(min_length=1, max_length=255) @model_validator(mode="after") def validate_interval(self): if ( self.effective_from and self.effective_to and self.effective_to <= self.effective_from ): raise ValueError("effective_to must be after effective_from") return self class RecordHoldReleaseRequest(StrictModel): expected_hold_revision: int = Field(ge=1) reason: str = Field(min_length=1, max_length=2_000) purpose: str = Field(min_length=1, max_length=255) recorded_at: datetime idempotency_key: str = Field(min_length=1, max_length=255) class RecordDispositionCreateRequest(StrictModel): disposition_id: str | None = Field(default=None, max_length=255) expected_record_revision: int = Field(ge=1) action: Literal["retain", "transfer", "destroy", "reclassify"] reason: str = Field(min_length=1, max_length=10_000) purpose: str = Field(min_length=1, max_length=255) policy_refs: list[str] = Field(default_factory=list, max_length=100) institutional_context: dict[str, Any] = Field(default_factory=dict) recorded_at: datetime idempotency_key: str = Field(min_length=1, max_length=255) class RecordDispositionFinalizeRequest(StrictModel): expected_disposition_revision: int = Field(ge=1) purpose: str = Field(min_length=1, max_length=255) recorded_at: datetime idempotency_key: str = Field(min_length=1, max_length=255) class RecordDispositionWithdrawRequest(RecordDispositionFinalizeRequest): reason: str = Field(min_length=1, max_length=2_000) class RecordTransferPackageCreateRequest(StrictModel): package_id: str | None = Field(default=None, max_length=255) disposition_id: str = Field(min_length=1, max_length=255) expected_record_revision: int = Field(ge=1) provider_id: str = Field(min_length=1, max_length=100) profile: str = Field(min_length=1, max_length=255) purpose: str = Field(min_length=1, max_length=255) recorded_at: datetime idempotency_key: str = Field(min_length=1, max_length=255) class RecordTransferDispatchRequest(StrictModel): expected_package_revision: int = Field(ge=1) purpose: str = Field(min_length=1, max_length=255) recorded_at: datetime idempotency_key: str = Field(min_length=1, max_length=255) class RecordListResponse(StrictModel): records: list[dict[str, Any]] total: int offset: int limit: int class RecordCatalogResponse(StrictModel): file_plan: list[dict[str, Any]] classes: list[dict[str, Any]] class RecordDetailResponse(StrictModel): record: dict[str, Any] volumes: list[dict[str, Any]] items: list[dict[str, Any]] chronology: list[dict[str, Any]] holds: list[dict[str, Any]] dispositions: list[dict[str, Any]] transfer_packages: list[dict[str, Any]] access_explanation: dict[str, Any] class RecordSourceProviderResponse(StrictModel): providers: list[dict[str, Any]] class RecordArchiveProviderResponse(StrictModel): providers: list[dict[str, Any]] class RecordRecoveryStatusResponse(StrictModel): record_id: str record_revision: int evidence_sha256: str healthy: bool source_checks: list[dict[str, Any]] package_checks: list[dict[str, Any]] recovery_operations: list[dict[str, Any]] failure_count: int limitations: list[str] __all__ = [ "FilePlanNodeWriteRequest", "RecordAppraisalRequest", "RecordArchiveProviderResponse", "RecordCatalogResponse", "RecordClassWriteRequest", "RecordCloseRequest", "RecordCreateRequest", "RecordDetailResponse", "RecordDispositionCreateRequest", "RecordDispositionFinalizeRequest", "RecordDispositionWithdrawRequest", "RecordHoldCreateRequest", "RecordHoldReleaseRequest", "RecordItemCreateRequest", "RecordLifecycleActionRequest", "RecordListResponse", "RecordRecoveryStatusResponse", "RecordSourceProviderResponse", "RecordTransferDispatchRequest", "RecordTransferPackageCreateRequest", "RecordUpdateRequest", "RecordVolumeCreateRequest", ]