"""Workspace-scoped KnowledgeFS migration and cutover persistence. The ledger is Dify control-plane state only. It deliberately stores opaque KnowledgeFS identifiers and authorization watermarks without foreign keys or queries to legacy product tables. Cutover feature switches live on one row so the repository can change them with a single CAS statement. Quarantine remediation is separately CAS-versioned and retains immutable operator evidence. """ from __future__ import annotations from datetime import datetime from enum import StrEnum from typing import TypedDict import sqlalchemy as sa from sqlalchemy import DateTime, Index, String, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from .base import DefaultFieldsDCMixin, TypeBase from .types import EnumText, LongText, StringUUID class KnowledgeFSCutoverRevisionWatermark(TypedDict): membership_epoch: int space_acl_epoch: int external_access_epoch: int content_policy_revision: int class KnowledgeFSCutoverSmokeChecks(TypedDict): authorization: bool list_spaces: bool create_space: bool query: bool upload: bool stream: bool deletion: bool class KnowledgeFSCutoverSmokeEvidenceReferences(TypedDict): authorization: str list_spaces: str create_space: str query: str upload: str stream: str deletion: str class KnowledgeFSCutoverSmokeResults(TypedDict): schema_version: str tenant_id: str environment: str operator: str operator_account_id: str observed_at: str checks: KnowledgeFSCutoverSmokeChecks evidence_references: KnowledgeFSCutoverSmokeEvidenceReferences def knowledge_fs_cutover_smoke_results_passed(value: KnowledgeFSCutoverSmokeResults | None) -> bool: """Reject incomplete or pre-production JSON evidence instead of treating metadata as booleans.""" if not isinstance(value, dict): return False checks = value.get("checks") references = value.get("evidence_references") expected = { "authorization", "list_spaces", "create_space", "query", "upload", "stream", "deletion", } return bool( value.get("schema_version") == "knowledge-fs-p8-cutover-smoke/v1" and value.get("environment") == "production" and isinstance(checks, dict) and set(checks) == expected and all(checks.get(name) is True for name in expected) and isinstance(references, dict) and set(references) == expected and all(isinstance(reference := references.get(name), str) and bool(reference) for name in expected) ) class KnowledgeFSWorkspaceCutoverPhase(StrEnum): INVENTORY = "inventory" BACKFILL = "backfill" SHADOW = "shadow" FROZEN = "frozen" CUTOVER = "cutover" OBSERVING = "observing" READY_FOR_CLEANUP = "ready_for_cleanup" class KnowledgeFSMigrationIssueStatus(StrEnum): OPEN = "open" APPROVED_FAIL_CLOSED = "approved_fail_closed" RESOLVED = "resolved" class KnowledgeFSMigrationIssueKind(StrEnum): REGISTRATION_CONFLICT = "registration_conflict" UNRESOLVED_SUBJECT = "unresolved_subject" UNKNOWN_EXTERNAL_ACCESS = "unknown_external_access" REVISION_DRIFT = "revision_drift" TASK_QUARANTINE = "task_quarantine" LEGACY_SNAPSHOT_DEPENDENCY = "legacy_snapshot_dependency" LEGACY_FOREIGN_KEY_DEPENDENCY = "legacy_foreign_key_dependency" class KnowledgeFSMigrationQuarantineKind(StrEnum): CONTROL_SPACE = "control_space" SUBJECT = "subject" TASK = "task" LEGACY_API_KEY = "legacy_api_key" ORPHAN_RESOURCE = "orphan_resource" class KnowledgeFSMigrationQuarantineDisposition(StrEnum): PENDING = "pending" MIGRATABLE = "migratable" WAIT_FOR_COMPLETION = "wait_for_completion" CANCEL = "cancel" ISOLATE = "isolate" ROTATE_CREDENTIAL = "rotate_credential" RESOLVED = "resolved" class KnowledgeFSShadowAuthorizationDecision(StrEnum): MATCH = "match" TIGHTENED = "tightened" EXPANDED = "expanded" UNKNOWN = "unknown" class KnowledgeFSWorkspaceCutoverLedger(DefaultFieldsDCMixin, TypeBase): """Single-Workspace migration state, watermarks, switches, and time fences.""" __tablename__ = "knowledge_fs_workspace_cutover_ledgers" __table_args__ = ( sa.PrimaryKeyConstraint("id", name="kfs_workspace_cutover_ledger_pkey"), UniqueConstraint("tenant_id", name="kfs_workspace_cutover_ledger_tenant_uq"), UniqueConstraint("tenant_id", "id", name="kfs_workspace_cutover_ledger_tenant_id_uq"), Index("kfs_workspace_cutover_ledger_phase_updated_idx", "phase", "updated_at"), sa.CheckConstraint("cas_version >= 0", name=sa.schema.conv("kfs_workspace_cutover_cas_version_ck")), sa.CheckConstraint( "source_task_watermark >= 0 AND applied_task_watermark >= 0 " "AND (final_task_watermark IS NULL OR final_task_watermark >= 0)", name=sa.schema.conv("kfs_workspace_cutover_task_watermark_ck"), ), sa.CheckConstraint( "cutover_at IS NULL OR freeze_at IS NOT NULL", name=sa.schema.conv("kfs_workspace_cutover_freeze_before_cutover_ck"), ), sa.CheckConstraint( "rollback_cutoff_at IS NULL OR cutover_at IS NOT NULL", name=sa.schema.conv("kfs_workspace_cutover_rollback_cutoff_ck"), ), sa.CheckConstraint( "shadow_observation_count >= 0", name=sa.schema.conv("kfs_workspace_cutover_shadow_count_ck"), ), sa.CheckConstraint( "shadow_window_ended_at IS NULL OR shadow_window_started_at IS NOT NULL", name=sa.schema.conv("kfs_workspace_cutover_shadow_window_ck"), ), sa.CheckConstraint( "(shadow_completed_at IS NULL " "AND shadow_evidence_digest IS NULL AND shadow_producer IS NULL " "AND shadow_completed_by_operator IS NULL AND shadow_completed_by_account_id IS NULL " "AND shadow_window_started_at IS NULL AND shadow_window_ended_at IS NULL " "AND shadow_traffic_zero = false AND shadow_traffic_zero_evidence IS NULL " "AND shadow_latest_observed_revision IS NULL) OR " "(shadow_completed_at IS NOT NULL AND shadow_started_at IS NOT NULL " "AND shadow_evidence_digest IS NOT NULL AND shadow_producer IS NOT NULL " "AND shadow_completed_by_operator IS NOT NULL AND shadow_completed_by_account_id IS NOT NULL " "AND ((shadow_traffic_zero = true AND shadow_observation_count = 0 " "AND shadow_traffic_zero_evidence IS NOT NULL AND shadow_window_started_at IS NULL " "AND shadow_window_ended_at IS NULL AND shadow_latest_observed_revision IS NULL) OR " "(shadow_traffic_zero = false AND shadow_observation_count > 0 " "AND shadow_traffic_zero_evidence IS NULL AND shadow_window_started_at IS NOT NULL " "AND shadow_window_ended_at IS NOT NULL AND shadow_latest_observed_revision IS NOT NULL)))", name=sa.schema.conv("kfs_workspace_cutover_shadow_completion_fields_ck"), ), sa.CheckConstraint( "(remote_freeze_id IS NULL AND remote_freeze_revision IS NULL " "AND remote_freeze_digest IS NULL AND remote_freeze_task_watermark IS NULL " "AND remote_freeze_control_space_id IS NULL AND remote_freeze_frozen_at IS NULL " "AND remote_freeze_updated_at IS NULL AND remote_freeze_acknowledged_at IS NULL " "AND remote_freeze_applied IS NULL AND remote_freeze_replayed IS NULL) OR " "(remote_freeze_id IS NOT NULL AND remote_freeze_revision BETWEEN 1 AND 9007199254740991 " "AND remote_freeze_digest IS NOT NULL AND remote_freeze_task_watermark >= 0 " "AND remote_freeze_control_space_id IS NOT NULL AND remote_freeze_frozen_at IS NOT NULL " "AND remote_freeze_updated_at IS NOT NULL AND remote_freeze_acknowledged_at IS NOT NULL " "AND ((remote_freeze_applied = true AND remote_freeze_replayed = false) " "OR (remote_freeze_applied = false AND remote_freeze_replayed = true)))", name=sa.schema.conv("kfs_workspace_cutover_remote_freeze_fields_ck"), ), sa.CheckConstraint( "remote_freeze_updated_at IS NULL OR remote_freeze_updated_at >= remote_freeze_frozen_at", name=sa.schema.conv("kfs_workspace_cutover_remote_freeze_time_ck"), ), sa.CheckConstraint( "(remote_activation_id IS NULL AND remote_activation_revision IS NULL " "AND remote_activation_digest IS NULL AND remote_activation_control_space_id IS NULL " "AND remote_activation_activated_at IS NULL AND remote_activation_updated_at IS NULL " "AND remote_activation_acknowledged_at IS NULL AND remote_activation_applied IS NULL " "AND remote_activation_replayed IS NULL) OR " "(remote_activation_id IS NOT NULL AND remote_activation_revision BETWEEN 1 AND 9007199254740991 " "AND remote_activation_digest IS NOT NULL AND remote_activation_control_space_id IS NOT NULL " "AND remote_activation_activated_at IS NOT NULL AND remote_activation_updated_at IS NOT NULL " "AND remote_activation_acknowledged_at IS NOT NULL " "AND ((remote_activation_applied = true AND remote_activation_replayed = false) " "OR (remote_activation_applied = false AND remote_activation_replayed = true)))", name=sa.schema.conv("kfs_workspace_cutover_remote_activation_fields_ck"), ), sa.CheckConstraint( "remote_activation_updated_at IS NULL OR remote_activation_updated_at >= remote_activation_activated_at", name=sa.schema.conv("kfs_workspace_cutover_remote_activation_time_ck"), ), ) tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) source_revision_watermark: Mapped[KnowledgeFSCutoverRevisionWatermark] = mapped_column(sa.JSON, nullable=False) applied_revision_watermark: Mapped[KnowledgeFSCutoverRevisionWatermark] = mapped_column(sa.JSON, nullable=False) phase: Mapped[KnowledgeFSWorkspaceCutoverPhase] = mapped_column( EnumText(KnowledgeFSWorkspaceCutoverPhase, length=32), nullable=False, server_default=sa.text("'inventory'"), default=KnowledgeFSWorkspaceCutoverPhase.INVENTORY, ) final_revision_watermark: Mapped[KnowledgeFSCutoverRevisionWatermark | None] = mapped_column( sa.JSON, nullable=True, default=None ) source_task_watermark: Mapped[int] = mapped_column( sa.BigInteger, nullable=False, server_default=sa.text("0"), default=0 ) final_task_watermark: Mapped[int | None] = mapped_column(sa.BigInteger, nullable=True, default=None) applied_task_watermark: Mapped[int] = mapped_column( sa.BigInteger, nullable=False, server_default=sa.text("0"), default=0 ) shadow_started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) shadow_completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) shadow_evidence_digest: Mapped[str | None] = mapped_column(String(71), nullable=True, default=None) shadow_observation_count: Mapped[int] = mapped_column( sa.BigInteger, nullable=False, server_default=sa.text("0"), default=0 ) shadow_window_started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) shadow_window_ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) shadow_traffic_zero: Mapped[bool] = mapped_column( sa.Boolean, nullable=False, server_default=sa.text("false"), default=False ) shadow_traffic_zero_evidence: Mapped[dict[str, object] | None] = mapped_column( sa.JSON(none_as_null=True), nullable=True, default=None ) shadow_latest_observed_revision: Mapped[KnowledgeFSCutoverRevisionWatermark | None] = mapped_column( sa.JSON(none_as_null=True), nullable=True, default=None ) shadow_producer: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) shadow_completed_by_operator: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) shadow_completed_by_account_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True, default=None) remote_freeze_id: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) remote_freeze_revision: Mapped[int | None] = mapped_column(sa.BigInteger, nullable=True, default=None) remote_freeze_digest: Mapped[str | None] = mapped_column(String(71), nullable=True, default=None) remote_freeze_task_watermark: Mapped[int | None] = mapped_column(sa.BigInteger, nullable=True, default=None) remote_freeze_control_space_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True, default=None) remote_freeze_frozen_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) remote_freeze_updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) remote_freeze_acknowledged_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) remote_freeze_applied: Mapped[bool | None] = mapped_column(sa.Boolean, nullable=True, default=None) remote_freeze_replayed: Mapped[bool | None] = mapped_column(sa.Boolean, nullable=True, default=None) remote_activation_id: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) remote_activation_revision: Mapped[int | None] = mapped_column(sa.BigInteger, nullable=True, default=None) remote_activation_digest: Mapped[str | None] = mapped_column(String(71), nullable=True, default=None) remote_activation_control_space_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True, default=None) remote_activation_activated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) remote_activation_updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) remote_activation_acknowledged_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) remote_activation_applied: Mapped[bool | None] = mapped_column(sa.Boolean, nullable=True, default=None) remote_activation_replayed: Mapped[bool | None] = mapped_column(sa.Boolean, nullable=True, default=None) freeze_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) cutover_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) rolled_back_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) rollback_cutoff_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) observation_started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) observation_window_ends_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) observation_completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) maximum_task_expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) irreversible_cleanup_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) product_routes_enabled: Mapped[bool] = mapped_column( sa.Boolean, nullable=False, server_default=sa.text("false"), default=False ) capability_v2_enabled: Mapped[bool] = mapped_column( sa.Boolean, nullable=False, server_default=sa.text("false"), default=False ) integrated_mode_enabled: Mapped[bool] = mapped_column( sa.Boolean, nullable=False, server_default=sa.text("false"), default=False ) legacy_acl_read_only: Mapped[bool] = mapped_column( sa.Boolean, nullable=False, server_default=sa.text("false"), default=False ) smoke_results: Mapped[KnowledgeFSCutoverSmokeResults | None] = mapped_column(sa.JSON, nullable=True, default=None) legacy_dependency_report: Mapped[list[dict[str, object]] | None] = mapped_column( sa.JSON, nullable=True, default=None ) legacy_dependency_checked_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) legacy_dependency_ready: Mapped[bool] = mapped_column( sa.Boolean, nullable=False, server_default=sa.text("false"), default=False ) cas_version: Mapped[int] = mapped_column(sa.BigInteger, nullable=False, server_default=sa.text("0"), default=0) class KnowledgeFSMigrationIssue(DefaultFieldsDCMixin, TypeBase): """Auditable blocker; unknown access can only be approved as fail-closed.""" __tablename__ = "knowledge_fs_migration_issues" __table_args__ = ( sa.PrimaryKeyConstraint("id", name="kfs_migration_issue_pkey"), UniqueConstraint("tenant_id", "ledger_id", "issue_key", name="kfs_migration_issue_key_uq"), sa.ForeignKeyConstraint( ["tenant_id", "ledger_id"], ["knowledge_fs_workspace_cutover_ledgers.tenant_id", "knowledge_fs_workspace_cutover_ledgers.id"], name="kfs_migration_issue_ledger_fk", ondelete="CASCADE", ), Index("kfs_migration_issue_gate_idx", "tenant_id", "ledger_id", "status", "kind"), ) tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) ledger_id: Mapped[str] = mapped_column(StringUUID, nullable=False) issue_key: Mapped[str] = mapped_column(String(255), nullable=False) kind: Mapped[KnowledgeFSMigrationIssueKind] = mapped_column( EnumText(KnowledgeFSMigrationIssueKind, length=48), nullable=False ) status: Mapped[KnowledgeFSMigrationIssueStatus] = mapped_column( EnumText(KnowledgeFSMigrationIssueStatus, length=32), nullable=False, server_default=sa.text("'open'"), default=KnowledgeFSMigrationIssueStatus.OPEN, ) resource_type: Mapped[str | None] = mapped_column(String(64), nullable=True, default=None) resource_id: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) details: Mapped[dict[str, object]] = mapped_column(sa.JSON, nullable=False, default_factory=dict) approved_by_account_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True, default=None) approved_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) resolved_by_account_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True, default=None) resolved_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) class KnowledgeFSMigrationQuarantine(DefaultFieldsDCMixin, TypeBase): """Fail-closed inventory item whose operator resolution is immutable and CAS-versioned.""" __tablename__ = "knowledge_fs_migration_quarantine" __table_args__ = ( sa.PrimaryKeyConstraint("id", name="kfs_migration_quarantine_pkey"), UniqueConstraint( "tenant_id", "ledger_id", "source_kind", "source_id", name="kfs_migration_quarantine_source_uq", ), sa.ForeignKeyConstraint( ["tenant_id", "ledger_id"], ["knowledge_fs_workspace_cutover_ledgers.tenant_id", "knowledge_fs_workspace_cutover_ledgers.id"], name="kfs_migration_quarantine_ledger_fk", ondelete="CASCADE", ), Index("kfs_migration_quarantine_disposition_idx", "tenant_id", "ledger_id", "disposition"), sa.CheckConstraint("row_version >= 0", name=sa.schema.conv("kfs_migration_quarantine_version_ck")), sa.CheckConstraint( "(disposition = 'resolved' " "AND resolved_by_operator IS NOT NULL AND resolved_by_account_id IS NOT NULL " "AND evidence IS NOT NULL AND resolved_at IS NOT NULL) OR " "(disposition <> 'resolved' " "AND resolved_by_operator IS NULL AND resolved_by_account_id IS NULL " "AND evidence IS NULL AND resolved_at IS NULL)", name=sa.schema.conv("kfs_migration_quarantine_resolution_fields_ck"), ), ) tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) ledger_id: Mapped[str] = mapped_column(StringUUID, nullable=False) source_kind: Mapped[KnowledgeFSMigrationQuarantineKind] = mapped_column( EnumText(KnowledgeFSMigrationQuarantineKind, length=32), nullable=False ) source_id: Mapped[str] = mapped_column(String(255), nullable=False) reason_code: Mapped[str] = mapped_column(String(64), nullable=False) disposition: Mapped[KnowledgeFSMigrationQuarantineDisposition] = mapped_column( EnumText(KnowledgeFSMigrationQuarantineDisposition, length=32), nullable=False ) details: Mapped[dict[str, object]] = mapped_column(sa.JSON, nullable=False, default_factory=dict) resolved_by_operator: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) resolved_by_account_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True, default=None) evidence: Mapped[dict[str, object] | None] = mapped_column(sa.JSON(none_as_null=True), nullable=True, default=None) resolved_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) row_version: Mapped[int] = mapped_column(sa.BigInteger, nullable=False, server_default=sa.text("0"), default=0) class KnowledgeFSShadowAuthorizationDiff(DefaultFieldsDCMixin, TypeBase): """Current disposition for a diff key; append-only observations retain its history.""" __tablename__ = "knowledge_fs_shadow_authorization_diffs" __table_args__ = ( sa.PrimaryKeyConstraint("id", name="kfs_shadow_authorization_diff_pkey"), UniqueConstraint("tenant_id", "ledger_id", "diff_key", name="kfs_shadow_authorization_diff_key_uq"), sa.ForeignKeyConstraint( ["tenant_id", "ledger_id"], ["knowledge_fs_workspace_cutover_ledgers.tenant_id", "knowledge_fs_workspace_cutover_ledgers.id"], name="kfs_shadow_authorization_diff_ledger_fk", ondelete="CASCADE", ), Index("kfs_shadow_authorization_diff_gate_idx", "tenant_id", "ledger_id", "status", "decision"), sa.CheckConstraint("row_version >= 0", name=sa.schema.conv("kfs_shadow_authorization_diff_version_ck")), ) tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) ledger_id: Mapped[str] = mapped_column(StringUUID, nullable=False) diff_key: Mapped[str] = mapped_column(String(255), nullable=False) principal: Mapped[str] = mapped_column(String(255), nullable=False) dify_allowed: Mapped[bool] = mapped_column(sa.Boolean, nullable=False) decision: Mapped[KnowledgeFSShadowAuthorizationDecision] = mapped_column( EnumText(KnowledgeFSShadowAuthorizationDecision, length=32), nullable=False ) reason: Mapped[str] = mapped_column(LongText, nullable=False) observed_revision: Mapped[KnowledgeFSCutoverRevisionWatermark] = mapped_column(sa.JSON, nullable=False) status: Mapped[KnowledgeFSMigrationIssueStatus] = mapped_column( EnumText(KnowledgeFSMigrationIssueStatus, length=32), nullable=False ) current_evidence_digest: Mapped[str] = mapped_column(String(71), nullable=False) last_observed_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) control_space_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True, default=None) legacy_allowed: Mapped[bool | None] = mapped_column(sa.Boolean, nullable=True, default=None) approved_by_account_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True, default=None) approved_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) resolved_by_account_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True, default=None) resolved_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) row_version: Mapped[int] = mapped_column(sa.BigInteger, nullable=False, server_default=sa.text("0"), default=0) class KnowledgeFSShadowAuthorizationObservation(DefaultFieldsDCMixin, TypeBase): """Append-only producer evidence used to derive a shadow diff disposition.""" __tablename__ = "knowledge_fs_shadow_authorization_observations" __table_args__ = ( sa.PrimaryKeyConstraint("id", name="kfs_shadow_authorization_observation_pkey"), UniqueConstraint( "tenant_id", "ledger_id", "diff_key", "evidence_digest", name="kfs_shadow_authorization_observation_evidence_uq", ), sa.ForeignKeyConstraint( ["tenant_id", "ledger_id"], ["knowledge_fs_workspace_cutover_ledgers.tenant_id", "knowledge_fs_workspace_cutover_ledgers.id"], name="kfs_shadow_authorization_observation_ledger_fk", ondelete="CASCADE", ), Index( "kfs_shadow_authorization_observation_window_idx", "tenant_id", "ledger_id", "observed_at", ), ) tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) ledger_id: Mapped[str] = mapped_column(StringUUID, nullable=False) diff_key: Mapped[str] = mapped_column(String(255), nullable=False) producer: Mapped[str] = mapped_column(String(255), nullable=False) principal: Mapped[str] = mapped_column(String(255), nullable=False) dify_allowed: Mapped[bool] = mapped_column(sa.Boolean, nullable=False) decision: Mapped[KnowledgeFSShadowAuthorizationDecision] = mapped_column( EnumText(KnowledgeFSShadowAuthorizationDecision, length=32), nullable=False ) reason: Mapped[str] = mapped_column(LongText, nullable=False) observed_revision: Mapped[KnowledgeFSCutoverRevisionWatermark] = mapped_column(sa.JSON, nullable=False) observed_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) evidence_digest: Mapped[str] = mapped_column(String(71), nullable=False) control_space_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True, default=None) legacy_allowed: Mapped[bool | None] = mapped_column(sa.Boolean, nullable=True, default=None) __all__ = [ "KnowledgeFSCutoverRevisionWatermark", "KnowledgeFSCutoverSmokeChecks", "KnowledgeFSCutoverSmokeEvidenceReferences", "KnowledgeFSCutoverSmokeResults", "KnowledgeFSMigrationIssue", "KnowledgeFSMigrationIssueKind", "KnowledgeFSMigrationIssueStatus", "KnowledgeFSMigrationQuarantine", "KnowledgeFSMigrationQuarantineDisposition", "KnowledgeFSMigrationQuarantineKind", "KnowledgeFSShadowAuthorizationDecision", "KnowledgeFSShadowAuthorizationDiff", "KnowledgeFSShadowAuthorizationObservation", "KnowledgeFSWorkspaceCutoverLedger", "KnowledgeFSWorkspaceCutoverPhase", "knowledge_fs_cutover_smoke_results_passed", ]