508 lines
26 KiB
Python
508 lines
26 KiB
Python
"""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",
|
|
]
|