dify/knowledge-fs/packages/database/src/migration-artifacts.generated.ts
2026-09-04 16:13:13 +08:00

111 lines
629 KiB
TypeScript

// Generated by packages/database/scripts/migration-artifacts.ts. Do not edit manually.
import type { MigrationArtifact } from "./migration-file";
// biome-ignore format: one deterministic row per immutable SQL artifact keeps generation cheap.
export const migrationArtifacts = [
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0001_initial_schema\n-- Dialect: postgres\n\nCREATE TABLE IF NOT EXISTS \"knowledge_spaces\" (\"id\" UUID PRIMARY KEY NOT NULL, \"tenant_id\" TEXT NOT NULL, \"slug\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ NOT NULL);\nCREATE TABLE IF NOT EXISTS \"knowledge_space_manifests\" (\"id\" UUID PRIMARY KEY NOT NULL, \"tenant_id\" TEXT NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"manifest_version\" INTEGER NOT NULL, \"storage_provider\" TEXT NOT NULL, \"object_key_prefix\" TEXT NOT NULL, \"metadata_dialect\" TEXT NOT NULL, \"parser_policy_version\" TEXT NOT NULL, \"node_schema_version\" INTEGER NOT NULL, \"projection_set_version\" TEXT NOT NULL, \"min_client_version\" TEXT NOT NULL, \"retention_policy\" JSONB NOT NULL, \"quota_policy\" JSONB NOT NULL, \"consistency_policy\" JSONB NOT NULL, \"encryption_policy\" JSONB NOT NULL, \"metadata\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ NOT NULL, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"sources\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"type\" TEXT NOT NULL, \"status\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"uri\" TEXT NOT NULL, \"metadata\" JSONB NOT NULL, \"permission_scope\" JSONB NOT NULL, \"version\" INTEGER NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ NOT NULL, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"resource_mounts\" (\"id\" UUID PRIMARY KEY NOT NULL, \"tenant_id\" TEXT NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"mount_path\" TEXT NOT NULL, \"resource_type\" TEXT NOT NULL, \"provider\" TEXT NOT NULL, \"mode\" TEXT NOT NULL, \"capabilities\" JSONB NOT NULL, \"source_pointer\" TEXT NOT NULL, \"permission_scope\" JSONB NOT NULL, \"permission_snapshot_version\" INTEGER NOT NULL, \"freshness_policy\" JSONB NOT NULL, \"cache_policy\" JSONB NOT NULL, \"metadata\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"last_synced_at\" TIMESTAMPTZ, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"document_assets\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"source_id\" UUID, \"filename\" TEXT NOT NULL, \"mime_type\" TEXT NOT NULL, \"object_key\" TEXT NOT NULL, \"sha256\" TEXT NOT NULL, \"size_bytes\" INTEGER NOT NULL, \"version\" INTEGER NOT NULL, \"parser_status\" TEXT NOT NULL, \"metadata\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"parse_artifacts\" (\"id\" UUID PRIMARY KEY NOT NULL, \"document_asset_id\" UUID NOT NULL, \"version\" INTEGER NOT NULL, \"parser\" TEXT NOT NULL, \"content_type\" TEXT NOT NULL, \"artifact_hash\" TEXT NOT NULL, \"elements\" JSONB NOT NULL, \"metadata\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ, FOREIGN KEY (\"document_asset_id\") REFERENCES \"document_assets\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"document_multimodal_manifests\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"document_asset_id\" UUID NOT NULL, \"parse_artifact_id\" UUID NOT NULL, \"version\" INTEGER NOT NULL, \"artifact_hash\" TEXT NOT NULL, \"manifest_version\" TEXT NOT NULL, \"items\" JSONB NOT NULL, \"metadata\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"document_asset_id\") REFERENCES \"document_assets\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"parse_artifact_id\") REFERENCES \"parse_artifacts\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"artifact_segments\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"document_asset_id\" UUID NOT NULL, \"parse_artifact_id\" UUID NOT NULL, \"segment_index\" INTEGER NOT NULL, \"segment_type\" TEXT NOT NULL, \"artifact_hash\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"object_key\" TEXT, \"inline_text\" TEXT, \"content_encoding\" TEXT NOT NULL, \"size_bytes\" INTEGER, \"start_offset\" INTEGER, \"end_offset\" INTEGER, \"source_location\" JSONB NOT NULL, \"metadata\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"document_asset_id\") REFERENCES \"document_assets\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"parse_artifact_id\") REFERENCES \"parse_artifacts\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"knowledge_space_staged_commits\" (\"id\" UUID PRIMARY KEY NOT NULL, \"tenant_id\" TEXT NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"operation_type\" TEXT NOT NULL, \"idempotency_key\" TEXT NOT NULL, \"status\" TEXT NOT NULL, \"raw_object_key\" TEXT, \"published_object_key\" TEXT, \"document_asset_id\" UUID, \"parse_artifact_id\" UUID, \"projection_fingerprint\" TEXT, \"checksum\" TEXT, \"size_bytes\" INTEGER, \"error_code\" TEXT, \"error_message\" TEXT, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ NOT NULL, \"expires_at\" TIMESTAMPTZ, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"document_asset_id\") REFERENCES \"document_assets\" (\"id\") ON DELETE SET NULL, FOREIGN KEY (\"parse_artifact_id\") REFERENCES \"parse_artifacts\" (\"id\") ON DELETE SET NULL);\nCREATE TABLE IF NOT EXISTS \"knowledge_fs_sessions\" (\"id\" UUID PRIMARY KEY NOT NULL, \"tenant_id\" TEXT NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"client_kind\" TEXT NOT NULL, \"client_version\" TEXT NOT NULL, \"subject\" JSONB NOT NULL, \"permission_snapshot\" JSONB NOT NULL, \"consistency_class\" TEXT NOT NULL, \"heartbeat_at\" TIMESTAMPTZ NOT NULL, \"expires_at\" TIMESTAMPTZ NOT NULL, \"metadata\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ NOT NULL, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"knowledge_fs_leases\" (\"id\" UUID PRIMARY KEY NOT NULL, \"tenant_id\" TEXT NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"session_id\" UUID NOT NULL, \"lease_type\" TEXT NOT NULL, \"target_type\" TEXT NOT NULL, \"target_id\" TEXT NOT NULL, \"target_version\" INTEGER, \"virtual_path\" TEXT NOT NULL, \"status\" TEXT NOT NULL, \"heartbeat_at\" TIMESTAMPTZ NOT NULL, \"expires_at\" TIMESTAMPTZ NOT NULL, \"metadata\" JSONB NOT NULL, \"acquired_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ NOT NULL, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"session_id\") REFERENCES \"knowledge_fs_sessions\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"knowledge_nodes\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"document_asset_id\" UUID NOT NULL, \"parse_artifact_id\" UUID NOT NULL, \"kind\" TEXT NOT NULL, \"text\" TEXT NOT NULL, \"start_offset\" INTEGER NOT NULL, \"end_offset\" INTEGER NOT NULL, \"source_location\" JSONB NOT NULL, \"permission_scope\" JSONB NOT NULL, \"artifact_hash\" TEXT NOT NULL, \"metadata\" JSONB NOT NULL, \"updated_at\" TIMESTAMPTZ, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"document_asset_id\") REFERENCES \"document_assets\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"parse_artifact_id\") REFERENCES \"parse_artifacts\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"index_projections\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"node_id\" UUID NOT NULL, \"type\" TEXT NOT NULL, \"status\" TEXT NOT NULL, \"model\" TEXT, \"projection_version\" INTEGER NOT NULL, \"dense_vector\" vector(1536), \"visual_vector\" vector, \"fts_document\" tsvector, \"metadata\" JSONB NOT NULL, \"updated_at\" TIMESTAMPTZ, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"node_id\") REFERENCES \"knowledge_nodes\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"embedding_models\" (\"id\" UUID PRIMARY KEY NOT NULL, \"provider\" TEXT NOT NULL, \"model_id\" TEXT NOT NULL, \"version\" TEXT NOT NULL, \"dimension\" INTEGER NOT NULL, \"metric\" TEXT NOT NULL, \"tokenizer\" TEXT NOT NULL, \"max_tokens\" INTEGER NOT NULL, \"status\" TEXT NOT NULL, \"metadata\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ NOT NULL);\nCREATE TABLE IF NOT EXISTS \"knowledge_paths\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"virtual_path\" TEXT NOT NULL, \"resource_type\" TEXT NOT NULL, \"target_id\" TEXT NOT NULL, \"version\" INTEGER, \"view_type\" TEXT NOT NULL, \"view_name\" TEXT NOT NULL, \"metadata\" JSONB NOT NULL, \"updated_at\" TIMESTAMPTZ, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"evidence_bundles\" (\"id\" UUID PRIMARY KEY NOT NULL, \"trace_id\" UUID, \"query\" TEXT NOT NULL, \"state\" TEXT NOT NULL, \"items\" JSONB NOT NULL, \"missing_evidence\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ);\nCREATE TABLE IF NOT EXISTS \"golden_questions\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"question\" TEXT NOT NULL, \"expected_evidence_ids\" JSONB NOT NULL, \"tags\" JSONB NOT NULL, \"metadata\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ NOT NULL, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"answer_traces\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"evidence_bundle_id\" UUID, \"query\" TEXT NOT NULL, \"mode\" TEXT NOT NULL, \"completed\" BOOLEAN NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"evidence_bundle_id\") REFERENCES \"evidence_bundles\" (\"id\") ON DELETE SET NULL);\nCREATE TABLE IF NOT EXISTS \"answer_trace_steps\" (\"id\" UUID PRIMARY KEY NOT NULL, \"trace_id\" UUID NOT NULL, \"name\" TEXT NOT NULL, \"status\" TEXT NOT NULL, \"metadata\" JSONB NOT NULL, \"started_at\" TIMESTAMPTZ NOT NULL, \"ended_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ, FOREIGN KEY (\"trace_id\") REFERENCES \"answer_traces\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"graph_entities\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"canonical_key\" TEXT NOT NULL, \"type\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"aliases\" JSONB NOT NULL, \"confidence\" DOUBLE PRECISION NOT NULL, \"source_node_ids\" JSONB NOT NULL, \"permission_scope\" JSONB NOT NULL, \"metadata\" JSONB NOT NULL, \"extraction_version\" INTEGER NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ NOT NULL, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"graph_relations\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"subject_entity_id\" UUID NOT NULL, \"object_entity_id\" UUID NOT NULL, \"type\" TEXT NOT NULL, \"confidence\" DOUBLE PRECISION NOT NULL, \"source_node_ids\" JSONB NOT NULL, \"permission_scope\" JSONB NOT NULL, \"metadata\" JSONB NOT NULL, \"extraction_version\" INTEGER NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ NOT NULL, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"subject_entity_id\") REFERENCES \"graph_entities\" (\"id\") ON DELETE CASCADE, FOREIGN KEY (\"object_entity_id\") REFERENCES \"graph_entities\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"failed_queries\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"answer_trace_id\" UUID, \"query\" TEXT NOT NULL, \"mode\" TEXT NOT NULL, \"trigger\" TEXT NOT NULL, \"status\" TEXT NOT NULL, \"metadata\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ NOT NULL, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS \"document_outlines\" (\"id\" UUID PRIMARY KEY NOT NULL, \"knowledge_space_id\" UUID NOT NULL, \"document_asset_id\" UUID NOT NULL, \"parse_artifact_id\" UUID NOT NULL, \"artifact_hash\" TEXT NOT NULL, \"outline_version\" TEXT NOT NULL, \"version\" INTEGER NOT NULL, \"nodes\" JSONB NOT NULL, \"metadata\" JSONB NOT NULL, \"created_at\" TIMESTAMPTZ NOT NULL, \"updated_at\" TIMESTAMPTZ, FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE);\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_spaces_tenant_slug_uq\" ON \"knowledge_spaces\" (\"tenant_id\", \"slug\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_manifests_tenant_space_uq\" ON \"knowledge_space_manifests\" (\"tenant_id\", \"knowledge_space_id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_space_manifests_tenant_space_idx\" ON \"knowledge_space_manifests\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"sources_space_status_idx\" ON \"sources\" (\"knowledge_space_id\", \"status\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"resource_mounts_space_path_uq\" ON \"resource_mounts\" (\"knowledge_space_id\", \"mount_path\");\nCREATE INDEX IF NOT EXISTS \"resource_mounts_space_type_path_idx\" ON \"resource_mounts\" (\"knowledge_space_id\", \"resource_type\", \"mount_path\", \"id\");\nCREATE INDEX IF NOT EXISTS \"resource_mounts_permission_scope_idx\" ON \"resource_mounts\" USING GIN (\"permission_scope\");\nCREATE INDEX IF NOT EXISTS \"document_assets_space_source_version_idx\" ON \"document_assets\" (\"knowledge_space_id\", \"source_id\", \"version\", \"id\");\nCREATE INDEX IF NOT EXISTS \"document_assets_space_status_created_idx\" ON \"document_assets\" (\"knowledge_space_id\", \"parser_status\", \"created_at\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"parse_artifacts_asset_version_uq\" ON \"parse_artifacts\" (\"document_asset_id\", \"version\");\nCREATE INDEX IF NOT EXISTS \"parse_artifacts_hash_idx\" ON \"parse_artifacts\" (\"artifact_hash\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_multimodal_manifests_asset_version_uq\" ON \"document_multimodal_manifests\" (\"document_asset_id\", \"version\");\nCREATE INDEX IF NOT EXISTS \"document_multimodal_manifests_space_asset_idx\" ON \"document_multimodal_manifests\" (\"knowledge_space_id\", \"document_asset_id\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"artifact_segments_artifact_index_uq\" ON \"artifact_segments\" (\"parse_artifact_id\", \"segment_index\");\nCREATE INDEX IF NOT EXISTS \"artifact_segments_space_artifact_index_idx\" ON \"artifact_segments\" (\"knowledge_space_id\", \"parse_artifact_id\", \"segment_index\", \"id\");\nCREATE INDEX IF NOT EXISTS \"artifact_segments_space_checksum_idx\" ON \"artifact_segments\" (\"knowledge_space_id\", \"checksum\", \"id\");\nCREATE INDEX IF NOT EXISTS \"artifact_segments_document_source_idx\" ON \"artifact_segments\" (\"document_asset_id\", \"start_offset\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_staged_commits_idempotency_uq\" ON \"knowledge_space_staged_commits\" (\"tenant_id\", \"knowledge_space_id\", \"idempotency_key\");\nCREATE INDEX IF NOT EXISTS \"knowledge_space_staged_commits_status_updated_idx\" ON \"knowledge_space_staged_commits\" (\"tenant_id\", \"knowledge_space_id\", \"status\", \"updated_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_space_staged_commits_expiry_idx\" ON \"knowledge_space_staged_commits\" (\"tenant_id\", \"knowledge_space_id\", \"expires_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_space_staged_commits_document_idx\" ON \"knowledge_space_staged_commits\" (\"document_asset_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_fs_sessions_space_expiry_idx\" ON \"knowledge_fs_sessions\" (\"tenant_id\", \"knowledge_space_id\", \"expires_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_fs_sessions_expiry_idx\" ON \"knowledge_fs_sessions\" (\"tenant_id\", \"expires_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_fs_leases_active_path_idx\" ON \"knowledge_fs_leases\" (\"tenant_id\", \"knowledge_space_id\", \"status\", \"virtual_path\", \"expires_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_fs_leases_expiry_idx\" ON \"knowledge_fs_leases\" (\"tenant_id\", \"expires_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_fs_leases_session_idx\" ON \"knowledge_fs_leases\" (\"tenant_id\", \"session_id\", \"status\", \"id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_nodes_space_asset_kind_idx\" ON \"knowledge_nodes\" (\"knowledge_space_id\", \"document_asset_id\", \"kind\");\nCREATE INDEX IF NOT EXISTS \"knowledge_nodes_artifact_offset_idx\" ON \"knowledge_nodes\" (\"parse_artifact_id\", \"start_offset\", \"id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_nodes_permission_scope_idx\" ON \"knowledge_nodes\" USING GIN (\"permission_scope\");\nCREATE INDEX IF NOT EXISTS \"index_projections_space_type_status_idx\" ON \"index_projections\" (\"knowledge_space_id\", \"type\", \"status\", \"node_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"index_projections_node_type_version_idx\" ON \"index_projections\" (\"node_id\", \"type\", \"projection_version\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"index_projections_node_type_version_model_uq\" ON \"index_projections\" (\"node_id\", \"type\", \"projection_version\", (COALESCE(\"model\", '')));\nCREATE INDEX IF NOT EXISTS \"index_projections_dense_vector_hnsw_idx\" ON \"index_projections\" USING hnsw (\"dense_vector\" vector_cosine_ops);\nCREATE INDEX IF NOT EXISTS \"index_projections_fts_document_idx\" ON \"index_projections\" USING GIN (\"fts_document\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"embedding_models_model_version_uq\" ON \"embedding_models\" (\"model_id\", \"version\");\nCREATE INDEX IF NOT EXISTS \"embedding_models_status_provider_idx\" ON \"embedding_models\" (\"status\", \"provider\", \"model_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"embedding_models_status_model_idx\" ON \"embedding_models\" (\"status\", \"model_id\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_paths_space_path_uq\" ON \"knowledge_paths\" (\"knowledge_space_id\", \"virtual_path\");\nCREATE INDEX IF NOT EXISTS \"knowledge_paths_target_idx\" ON \"knowledge_paths\" (\"resource_type\", \"target_id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_paths_space_view_path_idx\" ON \"knowledge_paths\" (\"knowledge_space_id\", \"view_type\", \"view_name\", \"virtual_path\", \"id\");\nCREATE INDEX IF NOT EXISTS \"evidence_bundles_trace_idx\" ON \"evidence_bundles\" (\"trace_id\");\nCREATE INDEX IF NOT EXISTS \"evidence_bundles_state_created_idx\" ON \"evidence_bundles\" (\"state\", \"created_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"golden_questions_space_id_idx\" ON \"golden_questions\" (\"knowledge_space_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"golden_questions_space_created_idx\" ON \"golden_questions\" (\"knowledge_space_id\", \"created_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"answer_traces_space_created_idx\" ON \"answer_traces\" (\"knowledge_space_id\", \"created_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"answer_traces_bundle_idx\" ON \"answer_traces\" (\"evidence_bundle_id\");\nCREATE INDEX IF NOT EXISTS \"answer_trace_steps_trace_started_idx\" ON \"answer_trace_steps\" (\"trace_id\", \"started_at\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"graph_entities_space_key_uq\" ON \"graph_entities\" (\"knowledge_space_id\", \"canonical_key\");\nCREATE INDEX IF NOT EXISTS \"graph_entities_space_type_name_idx\" ON \"graph_entities\" (\"knowledge_space_id\", \"type\", \"name\", \"id\");\nCREATE INDEX IF NOT EXISTS \"graph_entities_permission_scope_idx\" ON \"graph_entities\" USING GIN (\"permission_scope\");\nCREATE INDEX IF NOT EXISTS \"graph_relations_subject_traversal_idx\" ON \"graph_relations\" (\"knowledge_space_id\", \"subject_entity_id\", \"type\", \"object_entity_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"graph_relations_object_traversal_idx\" ON \"graph_relations\" (\"knowledge_space_id\", \"object_entity_id\", \"type\", \"subject_entity_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"graph_relations_permission_scope_idx\" ON \"graph_relations\" USING GIN (\"permission_scope\");\n", path: "packages/database/migrations/0001_initial_schema.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0001_initial_schema\n-- Dialect: tidb\n\nCREATE TABLE IF NOT EXISTS `knowledge_spaces` (`id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `slug` VARCHAR(160) NOT NULL, `name` TEXT NOT NULL, `description` TEXT, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL);\nCREATE TABLE IF NOT EXISTS `knowledge_space_manifests` (`id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `manifest_version` INT NOT NULL, `storage_provider` TEXT NOT NULL, `object_key_prefix` TEXT NOT NULL, `metadata_dialect` TEXT NOT NULL, `parser_policy_version` TEXT NOT NULL, `node_schema_version` INT NOT NULL, `projection_set_version` TEXT NOT NULL, `min_client_version` TEXT NOT NULL, `retention_policy` JSON NOT NULL, `quota_policy` JSON NOT NULL, `consistency_policy` JSON NOT NULL, `encryption_policy` JSON NOT NULL, `metadata` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `sources` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `type` TEXT NOT NULL, `status` VARCHAR(16) NOT NULL, `name` TEXT NOT NULL, `uri` TEXT NOT NULL, `metadata` JSON NOT NULL, `permission_scope` JSON NOT NULL, `version` INT NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `resource_mounts` (`id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `mount_path` VARCHAR(384) NOT NULL, `resource_type` VARCHAR(64) NOT NULL, `provider` TEXT NOT NULL, `mode` TEXT NOT NULL, `capabilities` JSON NOT NULL, `source_pointer` TEXT NOT NULL, `permission_scope` JSON NOT NULL, `permission_snapshot_version` INT NOT NULL, `freshness_policy` JSON NOT NULL, `cache_policy` JSON NOT NULL, `metadata` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `last_synced_at` DATETIME(3), FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `document_assets` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `source_id` CHAR(36), `filename` TEXT NOT NULL, `mime_type` TEXT NOT NULL, `object_key` TEXT NOT NULL, `sha256` TEXT NOT NULL, `size_bytes` INT NOT NULL, `version` INT NOT NULL, `parser_status` VARCHAR(16) NOT NULL, `metadata` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3), FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `parse_artifacts` (`id` CHAR(36) PRIMARY KEY NOT NULL, `document_asset_id` CHAR(36) NOT NULL, `version` INT NOT NULL, `parser` TEXT NOT NULL, `content_type` TEXT NOT NULL, `artifact_hash` VARCHAR(64) NOT NULL, `elements` JSON NOT NULL, `metadata` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3), FOREIGN KEY (`document_asset_id`) REFERENCES `document_assets` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `document_multimodal_manifests` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `document_asset_id` CHAR(36) NOT NULL, `parse_artifact_id` CHAR(36) NOT NULL, `version` INT NOT NULL, `artifact_hash` TEXT NOT NULL, `manifest_version` TEXT NOT NULL, `items` JSON NOT NULL, `metadata` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3), FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE, FOREIGN KEY (`document_asset_id`) REFERENCES `document_assets` (`id`) ON DELETE CASCADE, FOREIGN KEY (`parse_artifact_id`) REFERENCES `parse_artifacts` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `artifact_segments` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `document_asset_id` CHAR(36) NOT NULL, `parse_artifact_id` CHAR(36) NOT NULL, `segment_index` INT NOT NULL, `segment_type` TEXT NOT NULL, `artifact_hash` TEXT NOT NULL, `checksum` VARCHAR(64) NOT NULL, `object_key` TEXT, `inline_text` TEXT, `content_encoding` TEXT NOT NULL, `size_bytes` INT, `start_offset` INT, `end_offset` INT, `source_location` JSON NOT NULL, `metadata` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3), FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE, FOREIGN KEY (`document_asset_id`) REFERENCES `document_assets` (`id`) ON DELETE CASCADE, FOREIGN KEY (`parse_artifact_id`) REFERENCES `parse_artifacts` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `knowledge_space_staged_commits` (`id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `operation_type` TEXT NOT NULL, `idempotency_key` VARCHAR(255) NOT NULL, `status` VARCHAR(32) NOT NULL, `raw_object_key` TEXT, `published_object_key` TEXT, `document_asset_id` CHAR(36), `parse_artifact_id` CHAR(36), `projection_fingerprint` TEXT, `checksum` TEXT, `size_bytes` INT, `error_code` TEXT, `error_message` TEXT, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, `expires_at` DATETIME(3), FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE, FOREIGN KEY (`document_asset_id`) REFERENCES `document_assets` (`id`) ON DELETE SET NULL, FOREIGN KEY (`parse_artifact_id`) REFERENCES `parse_artifacts` (`id`) ON DELETE SET NULL);\nCREATE TABLE IF NOT EXISTS `knowledge_fs_sessions` (`id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `client_kind` TEXT NOT NULL, `client_version` TEXT NOT NULL, `subject` JSON NOT NULL, `permission_snapshot` JSON NOT NULL, `consistency_class` TEXT NOT NULL, `heartbeat_at` DATETIME(3) NOT NULL, `expires_at` DATETIME(3) NOT NULL, `metadata` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `knowledge_fs_leases` (`id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `session_id` CHAR(36) NOT NULL, `lease_type` TEXT NOT NULL, `target_type` TEXT NOT NULL, `target_id` TEXT NOT NULL, `target_version` INT, `virtual_path` VARCHAR(384) NOT NULL, `status` VARCHAR(16) NOT NULL, `heartbeat_at` DATETIME(3) NOT NULL, `expires_at` DATETIME(3) NOT NULL, `metadata` JSON NOT NULL, `acquired_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE, FOREIGN KEY (`session_id`) REFERENCES `knowledge_fs_sessions` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `knowledge_nodes` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `document_asset_id` CHAR(36) NOT NULL, `parse_artifact_id` CHAR(36) NOT NULL, `kind` VARCHAR(16) NOT NULL, `text` TEXT NOT NULL, `start_offset` INT NOT NULL, `end_offset` INT NOT NULL, `source_location` JSON NOT NULL, `permission_scope` JSON NOT NULL, `artifact_hash` TEXT NOT NULL, `metadata` JSON NOT NULL, `updated_at` DATETIME(3), FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE, FOREIGN KEY (`document_asset_id`) REFERENCES `document_assets` (`id`) ON DELETE CASCADE, FOREIGN KEY (`parse_artifact_id`) REFERENCES `parse_artifacts` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `index_projections` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `node_id` CHAR(36) NOT NULL, `type` VARCHAR(32) NOT NULL, `status` VARCHAR(16) NOT NULL, `model` VARCHAR(255), `model_key` VARCHAR(255) GENERATED ALWAYS AS (COALESCE(`model`, '')) VIRTUAL, `projection_version` INT NOT NULL, `dense_vector` VECTOR, `visual_vector` VECTOR, `fts_document` TEXT, `metadata` JSON NOT NULL, `updated_at` DATETIME(3), FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE, FOREIGN KEY (`node_id`) REFERENCES `knowledge_nodes` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `embedding_models` (`id` CHAR(36) PRIMARY KEY NOT NULL, `provider` VARCHAR(64) NOT NULL, `model_id` VARCHAR(255) NOT NULL, `version` VARCHAR(128) NOT NULL, `dimension` INT NOT NULL, `metric` TEXT NOT NULL, `tokenizer` TEXT NOT NULL, `max_tokens` INT NOT NULL, `status` VARCHAR(16) NOT NULL, `metadata` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL);\nCREATE TABLE IF NOT EXISTS `knowledge_paths` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `virtual_path` VARCHAR(384) NOT NULL, `resource_type` VARCHAR(64) NOT NULL, `target_id` VARCHAR(512) NOT NULL, `version` INT, `view_type` VARCHAR(16) NOT NULL, `view_name` VARCHAR(64) NOT NULL, `metadata` JSON NOT NULL, `updated_at` DATETIME(3), FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `evidence_bundles` (`id` CHAR(36) PRIMARY KEY NOT NULL, `trace_id` CHAR(36), `query` TEXT NOT NULL, `state` VARCHAR(16) NOT NULL, `items` JSON NOT NULL, `missing_evidence` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3));\nCREATE TABLE IF NOT EXISTS `golden_questions` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `question` TEXT NOT NULL, `expected_evidence_ids` JSON NOT NULL, `tags` JSON NOT NULL, `metadata` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `answer_traces` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `evidence_bundle_id` CHAR(36), `query` TEXT NOT NULL, `mode` TEXT NOT NULL, `completed` BOOLEAN NOT NULL, `created_at` DATETIME(3) NOT NULL, FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE, FOREIGN KEY (`evidence_bundle_id`) REFERENCES `evidence_bundles` (`id`) ON DELETE SET NULL);\nCREATE TABLE IF NOT EXISTS `answer_trace_steps` (`id` CHAR(36) PRIMARY KEY NOT NULL, `trace_id` CHAR(36) NOT NULL, `name` VARCHAR(64) NOT NULL, `status` VARCHAR(16) NOT NULL, `metadata` JSON NOT NULL, `started_at` DATETIME(3) NOT NULL, `ended_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3), FOREIGN KEY (`trace_id`) REFERENCES `answer_traces` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `graph_entities` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `canonical_key` VARCHAR(512) NOT NULL, `type` VARCHAR(64) NOT NULL, `name` VARCHAR(255) NOT NULL, `aliases` JSON NOT NULL, `confidence` DOUBLE NOT NULL, `source_node_ids` JSON NOT NULL, `permission_scope` JSON NOT NULL, `metadata` JSON NOT NULL, `extraction_version` INT NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `graph_relations` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `subject_entity_id` CHAR(36) NOT NULL, `object_entity_id` CHAR(36) NOT NULL, `type` VARCHAR(64) NOT NULL, `confidence` DOUBLE NOT NULL, `source_node_ids` JSON NOT NULL, `permission_scope` JSON NOT NULL, `metadata` JSON NOT NULL, `extraction_version` INT NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE, FOREIGN KEY (`subject_entity_id`) REFERENCES `graph_entities` (`id`) ON DELETE CASCADE, FOREIGN KEY (`object_entity_id`) REFERENCES `graph_entities` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `failed_queries` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `answer_trace_id` CHAR(36), `query` TEXT NOT NULL, `mode` TEXT NOT NULL, `trigger` TEXT NOT NULL, `status` TEXT NOT NULL, `metadata` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE);\nCREATE TABLE IF NOT EXISTS `document_outlines` (`id` CHAR(36) PRIMARY KEY NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `document_asset_id` CHAR(36) NOT NULL, `parse_artifact_id` CHAR(36) NOT NULL, `artifact_hash` TEXT NOT NULL, `outline_version` TEXT NOT NULL, `version` INT NOT NULL, `nodes` JSON NOT NULL, `metadata` JSON NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3), FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE);\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_spaces_tenant_slug_uq` ON `knowledge_spaces` (`tenant_id`, `slug`);\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_manifests_tenant_space_uq` ON `knowledge_space_manifests` (`tenant_id`, `knowledge_space_id`);\nCREATE INDEX IF NOT EXISTS `knowledge_space_manifests_tenant_space_idx` ON `knowledge_space_manifests` (`tenant_id`, `knowledge_space_id`, `id`);\nCREATE INDEX IF NOT EXISTS `sources_space_status_idx` ON `sources` (`knowledge_space_id`, `status`);\nCREATE UNIQUE INDEX IF NOT EXISTS `resource_mounts_space_path_uq` ON `resource_mounts` (`knowledge_space_id`, `mount_path`);\nCREATE INDEX IF NOT EXISTS `resource_mounts_space_type_path_idx` ON `resource_mounts` (`knowledge_space_id`, `resource_type`, `mount_path`, `id`);\nCREATE INDEX IF NOT EXISTS `document_assets_space_source_version_idx` ON `document_assets` (`knowledge_space_id`, `source_id`, `version`, `id`);\nCREATE INDEX IF NOT EXISTS `document_assets_space_status_created_idx` ON `document_assets` (`knowledge_space_id`, `parser_status`, `created_at`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `parse_artifacts_asset_version_uq` ON `parse_artifacts` (`document_asset_id`, `version`);\nCREATE INDEX IF NOT EXISTS `parse_artifacts_hash_idx` ON `parse_artifacts` (`artifact_hash`);\nCREATE UNIQUE INDEX IF NOT EXISTS `document_multimodal_manifests_asset_version_uq` ON `document_multimodal_manifests` (`document_asset_id`, `version`);\nCREATE INDEX IF NOT EXISTS `document_multimodal_manifests_space_asset_idx` ON `document_multimodal_manifests` (`knowledge_space_id`, `document_asset_id`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `artifact_segments_artifact_index_uq` ON `artifact_segments` (`parse_artifact_id`, `segment_index`);\nCREATE INDEX IF NOT EXISTS `artifact_segments_space_artifact_index_idx` ON `artifact_segments` (`knowledge_space_id`, `parse_artifact_id`, `segment_index`, `id`);\nCREATE INDEX IF NOT EXISTS `artifact_segments_space_checksum_idx` ON `artifact_segments` (`knowledge_space_id`, `checksum`, `id`);\nCREATE INDEX IF NOT EXISTS `artifact_segments_document_source_idx` ON `artifact_segments` (`document_asset_id`, `start_offset`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_staged_commits_idempotency_uq` ON `knowledge_space_staged_commits` (`tenant_id`, `knowledge_space_id`, `idempotency_key`);\nCREATE INDEX IF NOT EXISTS `knowledge_space_staged_commits_status_updated_idx` ON `knowledge_space_staged_commits` (`tenant_id`, `knowledge_space_id`, `status`, `updated_at`, `id`);\nCREATE INDEX IF NOT EXISTS `knowledge_space_staged_commits_expiry_idx` ON `knowledge_space_staged_commits` (`tenant_id`, `knowledge_space_id`, `expires_at`, `id`);\nCREATE INDEX IF NOT EXISTS `knowledge_space_staged_commits_document_idx` ON `knowledge_space_staged_commits` (`document_asset_id`, `id`);\nCREATE INDEX IF NOT EXISTS `knowledge_fs_sessions_space_expiry_idx` ON `knowledge_fs_sessions` (`tenant_id`, `knowledge_space_id`, `expires_at`, `id`);\nCREATE INDEX IF NOT EXISTS `knowledge_fs_sessions_expiry_idx` ON `knowledge_fs_sessions` (`tenant_id`, `expires_at`, `id`);\nCREATE INDEX IF NOT EXISTS `knowledge_fs_leases_active_path_idx` ON `knowledge_fs_leases` (`tenant_id`, `knowledge_space_id`, `status`, `virtual_path`, `expires_at`, `id`);\nCREATE INDEX IF NOT EXISTS `knowledge_fs_leases_expiry_idx` ON `knowledge_fs_leases` (`tenant_id`, `expires_at`, `id`);\nCREATE INDEX IF NOT EXISTS `knowledge_fs_leases_session_idx` ON `knowledge_fs_leases` (`tenant_id`, `session_id`, `status`, `id`);\nCREATE INDEX IF NOT EXISTS `knowledge_nodes_space_asset_kind_idx` ON `knowledge_nodes` (`knowledge_space_id`, `document_asset_id`, `kind`);\nCREATE INDEX IF NOT EXISTS `knowledge_nodes_artifact_offset_idx` ON `knowledge_nodes` (`parse_artifact_id`, `start_offset`, `id`);\nCREATE INDEX IF NOT EXISTS `index_projections_space_type_status_idx` ON `index_projections` (`knowledge_space_id`, `type`, `status`, `node_id`, `id`);\nCREATE INDEX IF NOT EXISTS `index_projections_node_type_version_idx` ON `index_projections` (`node_id`, `type`, `projection_version`);\nCREATE UNIQUE INDEX IF NOT EXISTS `index_projections_node_type_version_model_uq` ON `index_projections` (`node_id`, `type`, `projection_version`, `model_key`);\nCREATE UNIQUE INDEX IF NOT EXISTS `embedding_models_model_version_uq` ON `embedding_models` (`model_id`, `version`);\nCREATE INDEX IF NOT EXISTS `embedding_models_status_provider_idx` ON `embedding_models` (`status`, `provider`, `model_id`, `id`);\nCREATE INDEX IF NOT EXISTS `embedding_models_status_model_idx` ON `embedding_models` (`status`, `model_id`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_paths_space_path_uq` ON `knowledge_paths` (`knowledge_space_id`, `virtual_path`);\nCREATE INDEX IF NOT EXISTS `knowledge_paths_target_idx` ON `knowledge_paths` (`resource_type`, `target_id`);\nCREATE INDEX IF NOT EXISTS `knowledge_paths_space_view_path_idx` ON `knowledge_paths` (`knowledge_space_id`, `view_type`, `view_name`, `virtual_path`, `id`);\nCREATE INDEX IF NOT EXISTS `evidence_bundles_trace_idx` ON `evidence_bundles` (`trace_id`);\nCREATE INDEX IF NOT EXISTS `evidence_bundles_state_created_idx` ON `evidence_bundles` (`state`, `created_at`, `id`);\nCREATE INDEX IF NOT EXISTS `golden_questions_space_id_idx` ON `golden_questions` (`knowledge_space_id`, `id`);\nCREATE INDEX IF NOT EXISTS `golden_questions_space_created_idx` ON `golden_questions` (`knowledge_space_id`, `created_at`, `id`);\nCREATE INDEX IF NOT EXISTS `answer_traces_space_created_idx` ON `answer_traces` (`knowledge_space_id`, `created_at`, `id`);\nCREATE INDEX IF NOT EXISTS `answer_traces_bundle_idx` ON `answer_traces` (`evidence_bundle_id`);\nCREATE INDEX IF NOT EXISTS `answer_trace_steps_trace_started_idx` ON `answer_trace_steps` (`trace_id`, `started_at`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `graph_entities_space_key_uq` ON `graph_entities` (`knowledge_space_id`, `canonical_key`);\nCREATE INDEX IF NOT EXISTS `graph_entities_space_type_name_idx` ON `graph_entities` (`knowledge_space_id`, `type`, `name`, `id`);\nCREATE INDEX IF NOT EXISTS `graph_relations_subject_traversal_idx` ON `graph_relations` (`knowledge_space_id`, `subject_entity_id`, `type`, `object_entity_id`, `id`);\nCREATE INDEX IF NOT EXISTS `graph_relations_object_traversal_idx` ON `graph_relations` (`knowledge_space_id`, `object_entity_id`, `type`, `subject_entity_id`, `id`);\n", path: "packages/database/migrations/0001_initial_schema.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0002_vector_index_upgrade\n-- Dialect: postgres\n\n-- Embedding dimensions are determined by the configured plugin model. Use typmod-free vector\n-- columns so one deployment can store projections from multiple model-specific vector spaces.\n-- Exact distance queries remain supported when they filter to the matching model. pgvector ANN\n-- indexes require a fixed-dimensional cast plus the same model predicate and therefore cannot be\n-- declared as one generic schema index.\nALTER TABLE \"index_projections\" ADD COLUMN IF NOT EXISTS \"dense_vector\" vector;\nALTER TABLE \"index_projections\" ADD COLUMN IF NOT EXISTS \"visual_vector\" vector;\nALTER TABLE \"index_projections\" ADD COLUMN IF NOT EXISTS \"fts_document\" tsvector;\nALTER TABLE \"index_projections\" ADD COLUMN IF NOT EXISTS \"updated_at\" TIMESTAMPTZ;\n\nDROP INDEX IF EXISTS \"index_projections_dense_vector_hnsw_idx\";\nDROP INDEX IF EXISTS \"index_projections_visual_vector_hnsw_idx\";\nALTER TABLE \"index_projections\"\n ALTER COLUMN \"dense_vector\" TYPE vector\n USING \"dense_vector\"::vector,\n ALTER COLUMN \"visual_vector\" TYPE vector\n USING \"visual_vector\"::vector;\n\n-- Retry/redelivery in older releases could insert the same logical projection more than once.\n-- Retain the lexicographically greatest UUID deterministically before enforcing idempotency.\nDELETE FROM \"index_projections\" AS duplicate\nUSING \"index_projections\" AS keeper\nWHERE duplicate.\"node_id\" = keeper.\"node_id\"\n AND duplicate.\"type\" = keeper.\"type\"\n AND duplicate.\"projection_version\" = keeper.\"projection_version\"\n AND COALESCE(duplicate.\"model\", '') = COALESCE(keeper.\"model\", '')\n AND duplicate.\"id\" < keeper.\"id\";\nCREATE UNIQUE INDEX IF NOT EXISTS \"index_projections_node_type_version_model_uq\"\n ON \"index_projections\" (\n \"node_id\",\n \"type\",\n \"projection_version\",\n (COALESCE(\"model\", ''))\n );\n\nCREATE INDEX IF NOT EXISTS \"index_projections_fts_document_idx\"\n ON \"index_projections\" USING GIN (\"fts_document\");\n", path: "packages/database/migrations/0002_vector_index_upgrade.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0002_vector_index_upgrade\n-- Dialect: tidb\n\n-- Embedding dimensions are determined by the configured plugin model. TiDB's unbounded VECTOR\n-- type can store projections from multiple model-specific vector spaces. It cannot have a vector\n-- index, so retain exact model-filtered distance search unless a deployment separates a concrete\n-- model into a fixed-dimensional indexed column/table backed by TiFlash.\nDROP INDEX IF EXISTS `index_projections_dense_vector_hnsw_idx` ON `index_projections`;\nDROP INDEX IF EXISTS `index_projections_visual_vector_hnsw_idx` ON `index_projections`;\n\nALTER TABLE `index_projections` ADD COLUMN IF NOT EXISTS `dense_vector` VECTOR;\nALTER TABLE `index_projections` ADD COLUMN IF NOT EXISTS `visual_vector` VECTOR;\nALTER TABLE `index_projections` ADD COLUMN IF NOT EXISTS `fts_document` TEXT;\nALTER TABLE `index_projections` ADD COLUMN IF NOT EXISTS `updated_at` DATETIME(3);\nALTER TABLE `index_projections`\n ADD COLUMN IF NOT EXISTS `model_key` VARCHAR(255)\n GENERATED ALWAYS AS (COALESCE(`model`, '')) VIRTUAL;\nALTER TABLE `index_projections` MODIFY COLUMN IF EXISTS `dense_vector` VECTOR;\nALTER TABLE `index_projections` MODIFY COLUMN IF EXISTS `visual_vector` VECTOR;\n\nDELETE duplicate FROM `index_projections` AS duplicate\nINNER JOIN `index_projections` AS keeper\n ON duplicate.`node_id` = keeper.`node_id`\n AND duplicate.`type` = keeper.`type`\n AND duplicate.`projection_version` = keeper.`projection_version`\n AND COALESCE(duplicate.`model`, '') = COALESCE(keeper.`model`, '')\n AND duplicate.`id` < keeper.`id`;\nCREATE UNIQUE INDEX IF NOT EXISTS `index_projections_node_type_version_model_uq`\n ON `index_projections` (\n `node_id`,\n `type`,\n `projection_version`,\n `model_key`\n );\n\n-- TiDB v8.5 does not implement FULLTEXT indexes or FTS_MATCH_WORD. Migration 0011 cuts TiDB\n-- retrieval over to bounded, publication-scoped indexed postings; PostgreSQL keeps its native\n-- GIN tsvector index. Do not emit unsupported FULLTEXT DDL here.\n", path: "packages/database/migrations/0002_vector_index_upgrade.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0003_projection_set_publications\n-- Dialect: postgres\n\n-- A publication is an immutable, tenant-scoped candidate generation. The mutable head lives in a\n-- separate row so publication is one compare-and-swap instead of a sequence of visible row flips.\nCREATE TABLE IF NOT EXISTS \"projection_set_publications\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"fingerprint\" VARCHAR(86) NOT NULL,\n \"projection_version\" INTEGER NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"superseded_by_fingerprint\" VARCHAR(86),\n \"metadata\" JSONB NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"projection_set_publications_space_fingerprint_uq\"\n ON \"projection_set_publications\" (\"tenant_id\", \"knowledge_space_id\", \"fingerprint\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"projection_set_publications_space_id_uq\"\n ON \"projection_set_publications\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"projection_set_publications_space_status_updated_idx\"\n ON \"projection_set_publications\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"status\",\n \"updated_at\",\n \"fingerprint\",\n \"id\"\n );\n\nCREATE TABLE IF NOT EXISTS \"projection_set_publication_heads\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"publication_id\" UUID NOT NULL,\n \"head_revision\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE,\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"publication_id\")\n REFERENCES \"projection_set_publications\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"projection_set_publication_heads_space_uq\"\n ON \"projection_set_publication_heads\" (\"tenant_id\", \"knowledge_space_id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"projection_set_publication_heads_publication_uq\"\n ON \"projection_set_publication_heads\" (\"publication_id\");\n", path: "packages/database/migrations/0003_projection_set_publications.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0003_projection_set_publications\n-- Dialect: tidb\n\n-- A publication is an immutable, tenant-scoped candidate generation. The mutable head lives in a\n-- separate row so publication is one compare-and-swap instead of a sequence of visible row flips.\nCREATE TABLE IF NOT EXISTS `projection_set_publications` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `fingerprint` VARCHAR(86) NOT NULL,\n `projection_version` INT NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `superseded_by_fingerprint` VARCHAR(86),\n `metadata` JSON NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `projection_set_publications_space_fingerprint_uq`\n ON `projection_set_publications` (`tenant_id`, `knowledge_space_id`, `fingerprint`);\nCREATE UNIQUE INDEX IF NOT EXISTS `projection_set_publications_space_id_uq`\n ON `projection_set_publications` (`tenant_id`, `knowledge_space_id`, `id`);\nCREATE INDEX IF NOT EXISTS `projection_set_publications_space_status_updated_idx`\n ON `projection_set_publications` (\n `tenant_id`,\n `knowledge_space_id`,\n `status`,\n `updated_at`,\n `fingerprint`,\n `id`\n );\n\nCREATE TABLE IF NOT EXISTS `projection_set_publication_heads` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `publication_id` CHAR(36) NOT NULL,\n `head_revision` INT NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE,\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `publication_id`)\n REFERENCES `projection_set_publications` (`tenant_id`, `knowledge_space_id`, `id`)\n ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `projection_set_publication_heads_space_uq`\n ON `projection_set_publication_heads` (`tenant_id`, `knowledge_space_id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `projection_set_publication_heads_publication_uq`\n ON `projection_set_publication_heads` (`publication_id`);\n", path: "packages/database/migrations/0003_projection_set_publications.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0004_projection_publication_members\n-- Dialect: postgres\n\n-- Derived rows belong to immutable build generations. Nullable columns keep this an expand-safe\n-- migration for legacy writers; the zero UUID in logical unique indexes treats legacy NULL rows as\n-- one generation, preserving their previous retry/idempotency behavior.\nALTER TABLE \"index_projections\"\n ADD COLUMN IF NOT EXISTS \"publication_generation_id\" UUID;\nALTER TABLE \"document_outlines\"\n ADD COLUMN IF NOT EXISTS \"publication_generation_id\" UUID;\nALTER TABLE \"document_multimodal_manifests\"\n ADD COLUMN IF NOT EXISTS \"publication_generation_id\" UUID;\nALTER TABLE \"knowledge_paths\"\n ADD COLUMN IF NOT EXISTS \"publication_generation_id\" UUID;\nALTER TABLE \"graph_entities\"\n ADD COLUMN IF NOT EXISTS \"publication_generation_id\" UUID;\nALTER TABLE \"graph_relations\"\n ADD COLUMN IF NOT EXISTS \"publication_generation_id\" UUID;\n\n-- Generation-scoped readers filter immediately after tenant-space scope. Rebuild the existing\n-- access-path indexes so retained historical generations do not amplify candidate/read scans.\nDROP INDEX IF EXISTS \"index_projections_space_type_status_idx\";\nCREATE INDEX IF NOT EXISTS \"index_projections_space_type_status_idx\"\n ON \"index_projections\" (\n \"knowledge_space_id\",\n \"publication_generation_id\",\n \"type\",\n \"status\",\n \"node_id\",\n \"id\"\n );\nDROP INDEX IF EXISTS \"knowledge_paths_space_view_path_idx\";\nCREATE INDEX IF NOT EXISTS \"knowledge_paths_space_view_path_idx\"\n ON \"knowledge_paths\" (\n \"knowledge_space_id\",\n \"publication_generation_id\",\n \"view_type\",\n \"view_name\",\n \"virtual_path\",\n \"id\"\n );\nDROP INDEX IF EXISTS \"graph_entities_space_type_name_idx\";\nCREATE INDEX IF NOT EXISTS \"graph_entities_space_type_name_idx\"\n ON \"graph_entities\" (\n \"knowledge_space_id\",\n \"publication_generation_id\",\n \"type\",\n \"name\",\n \"id\"\n );\nDROP INDEX IF EXISTS \"graph_relations_subject_traversal_idx\";\nCREATE INDEX IF NOT EXISTS \"graph_relations_subject_traversal_idx\"\n ON \"graph_relations\" (\n \"knowledge_space_id\",\n \"publication_generation_id\",\n \"subject_entity_id\",\n \"type\",\n \"object_entity_id\",\n \"id\"\n );\nDROP INDEX IF EXISTS \"graph_relations_object_traversal_idx\";\nCREATE INDEX IF NOT EXISTS \"graph_relations_object_traversal_idx\"\n ON \"graph_relations\" (\n \"knowledge_space_id\",\n \"publication_generation_id\",\n \"object_entity_id\",\n \"type\",\n \"subject_entity_id\",\n \"id\"\n );\n\nDROP INDEX IF EXISTS \"document_multimodal_manifests_asset_version_uq\";\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_multimodal_manifests_asset_version_uq\"\n ON \"document_multimodal_manifests\" (\n \"document_asset_id\",\n \"version\",\n (COALESCE(\"publication_generation_id\", '00000000-0000-0000-0000-000000000000'::uuid))\n );\n\nDROP INDEX IF EXISTS \"index_projections_node_type_version_model_uq\";\nCREATE UNIQUE INDEX IF NOT EXISTS \"index_projections_node_type_version_model_uq\"\n ON \"index_projections\" (\n \"node_id\",\n \"type\",\n \"projection_version\",\n (COALESCE(\"model\", '')),\n (COALESCE(\"publication_generation_id\", '00000000-0000-0000-0000-000000000000'::uuid))\n );\n\nDROP INDEX IF EXISTS \"knowledge_paths_space_path_uq\";\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_paths_space_path_uq\"\n ON \"knowledge_paths\" (\n \"knowledge_space_id\",\n \"virtual_path\",\n (COALESCE(\"publication_generation_id\", '00000000-0000-0000-0000-000000000000'::uuid))\n );\n\nDROP INDEX IF EXISTS \"graph_entities_space_key_uq\";\nCREATE UNIQUE INDEX IF NOT EXISTS \"graph_entities_space_key_uq\"\n ON \"graph_entities\" (\n \"knowledge_space_id\",\n \"canonical_key\",\n (COALESCE(\"publication_generation_id\", '00000000-0000-0000-0000-000000000000'::uuid))\n );\n\n-- These tables previously relied on keyed overwrite/deterministic IDs rather than database\n-- logical uniqueness. If legacy duplicates exist, unique-index creation intentionally fails\n-- closed: deleting an arbitrary Graph relation could lose evidence or weaken permission scope.\nCREATE UNIQUE INDEX IF NOT EXISTS \"graph_relations_space_edge_version_uq\"\n ON \"graph_relations\" (\n \"knowledge_space_id\",\n \"subject_entity_id\",\n \"type\",\n \"object_entity_id\",\n \"extraction_version\",\n (COALESCE(\"publication_generation_id\", '00000000-0000-0000-0000-000000000000'::uuid))\n );\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_outlines_asset_version_uq\"\n ON \"document_outlines\" (\n \"document_asset_id\",\n \"version\",\n (COALESCE(\"publication_generation_id\", '00000000-0000-0000-0000-000000000000'::uuid))\n );\n\n-- component_key is the UUID of a derived row under component_type, not a free-form logical path.\n-- A publication may retain components from several generations when only one document changes.\nCREATE TABLE IF NOT EXISTS \"projection_set_publication_members\" (\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"publication_id\" UUID NOT NULL,\n \"component_type\" VARCHAR(64) NOT NULL,\n \"component_key\" UUID NOT NULL,\n \"generation_id\" UUID NOT NULL,\n \"document_asset_id\" UUID,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"publication_id\")\n REFERENCES \"projection_set_publications\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"projection_set_publication_members_component_uq\"\n ON \"projection_set_publication_members\" (\"publication_id\", \"component_type\", \"component_key\");\nCREATE INDEX IF NOT EXISTS \"projection_set_publication_members_generation_idx\"\n ON \"projection_set_publication_members\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"generation_id\",\n \"publication_id\",\n \"component_type\",\n \"component_key\"\n );\nCREATE INDEX IF NOT EXISTS \"projection_set_publication_members_document_idx\"\n ON \"projection_set_publication_members\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"publication_id\",\n \"document_asset_id\",\n \"component_type\",\n \"component_key\"\n );\n", path: "packages/database/migrations/0004_projection_publication_members.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0004_projection_publication_members\n-- Dialect: tidb\n\n-- Derived rows belong to immutable build generations. Nullable columns keep this an expand-safe\n-- migration for legacy writers; the zero UUID in logical unique indexes treats legacy NULL rows as\n-- one generation, preserving their previous retry/idempotency behavior.\nALTER TABLE `index_projections`\n ADD COLUMN IF NOT EXISTS `publication_generation_id` CHAR(36);\nALTER TABLE `document_outlines`\n ADD COLUMN IF NOT EXISTS `publication_generation_id` CHAR(36);\nALTER TABLE `document_multimodal_manifests`\n ADD COLUMN IF NOT EXISTS `publication_generation_id` CHAR(36);\nALTER TABLE `knowledge_paths`\n ADD COLUMN IF NOT EXISTS `publication_generation_id` CHAR(36);\nALTER TABLE `graph_entities`\n ADD COLUMN IF NOT EXISTS `publication_generation_id` CHAR(36);\nALTER TABLE `graph_relations`\n ADD COLUMN IF NOT EXISTS `publication_generation_id` CHAR(36);\n\n-- TiDB expression indexes are disabled by default and COALESCE is not accepted in an expression\n-- index without a server-wide compatibility switch. Explicit virtual generated columns preserve\n-- exact NULL-as-legacy-generation uniqueness using ordinary, portable indexes.\nALTER TABLE `index_projections`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `document_outlines`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `document_multimodal_manifests`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `knowledge_paths`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `graph_entities`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `graph_relations`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\n\n-- Generation-scoped readers filter immediately after tenant-space scope. Rebuild the existing\n-- access-path indexes so retained historical generations do not amplify candidate/read scans.\nDROP INDEX IF EXISTS `index_projections_space_type_status_idx` ON `index_projections`;\nCREATE INDEX IF NOT EXISTS `index_projections_space_type_status_idx`\n ON `index_projections` (\n `knowledge_space_id`,\n `publication_generation_id`,\n `type`,\n `status`,\n `node_id`,\n `id`\n );\nDROP INDEX IF EXISTS `knowledge_paths_space_view_path_idx` ON `knowledge_paths`;\nCREATE INDEX IF NOT EXISTS `knowledge_paths_space_view_path_idx`\n ON `knowledge_paths` (\n `knowledge_space_id`,\n `publication_generation_id`,\n `view_type`,\n `view_name`,\n `virtual_path`,\n `id`\n );\nDROP INDEX IF EXISTS `graph_entities_space_type_name_idx` ON `graph_entities`;\nCREATE INDEX IF NOT EXISTS `graph_entities_space_type_name_idx`\n ON `graph_entities` (\n `knowledge_space_id`,\n `publication_generation_id`,\n `type`,\n `name`,\n `id`\n );\nDROP INDEX IF EXISTS `graph_relations_subject_traversal_idx` ON `graph_relations`;\nCREATE INDEX IF NOT EXISTS `graph_relations_subject_traversal_idx`\n ON `graph_relations` (\n `knowledge_space_id`,\n `publication_generation_id`,\n `subject_entity_id`,\n `type`,\n `object_entity_id`,\n `id`\n );\nDROP INDEX IF EXISTS `graph_relations_object_traversal_idx` ON `graph_relations`;\nCREATE INDEX IF NOT EXISTS `graph_relations_object_traversal_idx`\n ON `graph_relations` (\n `knowledge_space_id`,\n `publication_generation_id`,\n `object_entity_id`,\n `type`,\n `subject_entity_id`,\n `id`\n );\n\nDROP INDEX IF EXISTS `document_multimodal_manifests_asset_version_uq`\n ON `document_multimodal_manifests`;\nCREATE UNIQUE INDEX IF NOT EXISTS `document_multimodal_manifests_asset_version_uq`\n ON `document_multimodal_manifests` (\n `document_asset_id`,\n `version`,\n `publication_generation_key`\n );\n\nDROP INDEX IF EXISTS `index_projections_node_type_version_model_uq` ON `index_projections`;\nCREATE UNIQUE INDEX IF NOT EXISTS `index_projections_node_type_version_model_uq`\n ON `index_projections` (\n `node_id`,\n `type`,\n `projection_version`,\n `model_key`,\n `publication_generation_key`\n );\n\nDROP INDEX IF EXISTS `knowledge_paths_space_path_uq` ON `knowledge_paths`;\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_paths_space_path_uq`\n ON `knowledge_paths` (\n `knowledge_space_id`,\n `virtual_path`,\n `publication_generation_key`\n );\n\nDROP INDEX IF EXISTS `graph_entities_space_key_uq` ON `graph_entities`;\nCREATE UNIQUE INDEX IF NOT EXISTS `graph_entities_space_key_uq`\n ON `graph_entities` (\n `knowledge_space_id`,\n `canonical_key`,\n `publication_generation_key`\n );\n\n-- These tables previously relied on keyed overwrite/deterministic IDs rather than database\n-- logical uniqueness. If legacy duplicates exist, unique-index creation intentionally fails\n-- closed: deleting an arbitrary Graph relation could lose evidence or weaken permission scope.\nCREATE UNIQUE INDEX IF NOT EXISTS `graph_relations_space_edge_version_uq`\n ON `graph_relations` (\n `knowledge_space_id`,\n `subject_entity_id`,\n `type`,\n `object_entity_id`,\n `extraction_version`,\n `publication_generation_key`\n );\n\nCREATE UNIQUE INDEX IF NOT EXISTS `document_outlines_asset_version_uq`\n ON `document_outlines` (\n `document_asset_id`,\n `version`,\n `publication_generation_key`\n );\n\n-- component_key is the UUID of a derived row under component_type, not a free-form logical path.\n-- A publication may retain components from several generations when only one document changes.\nCREATE TABLE IF NOT EXISTS `projection_set_publication_members` (\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `publication_id` CHAR(36) NOT NULL,\n `component_type` VARCHAR(64) NOT NULL,\n `component_key` CHAR(36) NOT NULL,\n `generation_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36),\n `created_at` DATETIME(3) NOT NULL,\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `publication_id`)\n REFERENCES `projection_set_publications` (`tenant_id`, `knowledge_space_id`, `id`)\n ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `projection_set_publication_members_component_uq`\n ON `projection_set_publication_members` (`publication_id`, `component_type`, `component_key`);\nCREATE INDEX IF NOT EXISTS `projection_set_publication_members_generation_idx`\n ON `projection_set_publication_members` (\n `tenant_id`,\n `knowledge_space_id`,\n `generation_id`,\n `publication_id`,\n `component_type`,\n `component_key`\n );\nCREATE INDEX IF NOT EXISTS `projection_set_publication_members_document_idx`\n ON `projection_set_publication_members` (\n `tenant_id`,\n `knowledge_space_id`,\n `publication_id`,\n `document_asset_id`,\n `component_type`,\n `component_key`\n );\n", path: "packages/database/migrations/0004_projection_publication_members.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0005_publication_generation_nonzero\n-- Dialect: postgres\n\n-- The zero UUID is reserved exclusively as the unique-index sentinel for legacy NULL generations.\n-- Fail closed if historical data has used it as an actual immutable build generation.\nALTER TABLE \"index_projections\"\n ADD CONSTRAINT \"index_projections_pub_gen_nonzero_ck\"\n CHECK (\n \"publication_generation_id\" IS NULL\n OR \"publication_generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid\n );\nALTER TABLE \"document_outlines\"\n ADD CONSTRAINT \"document_outlines_pub_gen_nonzero_ck\"\n CHECK (\n \"publication_generation_id\" IS NULL\n OR \"publication_generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid\n );\nALTER TABLE \"document_multimodal_manifests\"\n ADD CONSTRAINT \"document_multimodal_pub_gen_nonzero_ck\"\n CHECK (\n \"publication_generation_id\" IS NULL\n OR \"publication_generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid\n );\nALTER TABLE \"knowledge_paths\"\n ADD CONSTRAINT \"knowledge_paths_pub_gen_nonzero_ck\"\n CHECK (\n \"publication_generation_id\" IS NULL\n OR \"publication_generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid\n );\nALTER TABLE \"graph_entities\"\n ADD CONSTRAINT \"graph_entities_pub_gen_nonzero_ck\"\n CHECK (\n \"publication_generation_id\" IS NULL\n OR \"publication_generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid\n );\nALTER TABLE \"graph_relations\"\n ADD CONSTRAINT \"graph_relations_pub_gen_nonzero_ck\"\n CHECK (\n \"publication_generation_id\" IS NULL\n OR \"publication_generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid\n );\nALTER TABLE \"projection_set_publication_members\"\n ADD CONSTRAINT \"publication_members_gen_nonzero_ck\"\n CHECK (\"generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid);\n", path: "packages/database/migrations/0005_publication_generation_nonzero.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0005_publication_generation_nonzero\n-- Dialect: tidb\n\n-- The zero UUID is reserved exclusively as the unique-index sentinel for legacy NULL generations.\n-- Fail closed if historical data has used it as an actual immutable build generation.\n-- TiDB requires v7.2+ and tidb_enable_check_constraint=ON; keep that cluster-level feature enabled\n-- so these constraints are enforced rather than weakening the publication boundary.\nALTER TABLE `index_projections`\n ADD CONSTRAINT `index_projections_pub_gen_nonzero_ck`\n CHECK (\n `publication_generation_id` IS NULL\n OR (`publication_generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `publication_generation_id` <> '00000000-0000-0000-0000-000000000000')\n );\nALTER TABLE `document_outlines`\n ADD CONSTRAINT `document_outlines_pub_gen_nonzero_ck`\n CHECK (\n `publication_generation_id` IS NULL\n OR (`publication_generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `publication_generation_id` <> '00000000-0000-0000-0000-000000000000')\n );\nALTER TABLE `document_multimodal_manifests`\n ADD CONSTRAINT `document_multimodal_pub_gen_nonzero_ck`\n CHECK (\n `publication_generation_id` IS NULL\n OR (`publication_generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `publication_generation_id` <> '00000000-0000-0000-0000-000000000000')\n );\nALTER TABLE `knowledge_paths`\n ADD CONSTRAINT `knowledge_paths_pub_gen_nonzero_ck`\n CHECK (\n `publication_generation_id` IS NULL\n OR (`publication_generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `publication_generation_id` <> '00000000-0000-0000-0000-000000000000')\n );\nALTER TABLE `graph_entities`\n ADD CONSTRAINT `graph_entities_pub_gen_nonzero_ck`\n CHECK (\n `publication_generation_id` IS NULL\n OR (`publication_generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `publication_generation_id` <> '00000000-0000-0000-0000-000000000000')\n );\nALTER TABLE `graph_relations`\n ADD CONSTRAINT `graph_relations_pub_gen_nonzero_ck`\n CHECK (\n `publication_generation_id` IS NULL\n OR (`publication_generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `publication_generation_id` <> '00000000-0000-0000-0000-000000000000')\n );\nALTER TABLE `projection_set_publication_members`\n ADD CONSTRAINT `publication_members_gen_nonzero_ck`\n CHECK (`generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `generation_id` <> '00000000-0000-0000-0000-000000000000');\n", path: "packages/database/migrations/0005_publication_generation_nonzero.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0006_document_compilation_attempts\n-- Dialect: postgres\n\n-- Compilation state and queue publication live in one database transaction. active_slot is 1 only\n-- while work is live; terminal rows are retained unless an explicit manual retry reactivates a\n-- failed logical attempt. The unique key prevents two active attempts for the same tenant-scoped\n-- document version.\nALTER TABLE \"knowledge_spaces\"\n ADD CONSTRAINT \"knowledge_spaces_tenant_id_length_ck\"\n CHECK (CHAR_LENGTH(\"tenant_id\") <= 255);\nALTER TABLE \"knowledge_spaces\"\n ALTER COLUMN \"tenant_id\" TYPE VARCHAR(255)\n USING \"tenant_id\"::VARCHAR(255);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_spaces_tenant_id_uq\"\n ON \"knowledge_spaces\" (\"tenant_id\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_assets_space_id_version_uq\"\n ON \"document_assets\" (\"knowledge_space_id\", \"id\", \"version\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"projection_set_publications_space_id_fingerprint_uq\"\n ON \"projection_set_publications\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"id\",\n \"fingerprint\"\n );\n\nCREATE TABLE IF NOT EXISTS \"document_compilation_attempts\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"document_version\" INTEGER NOT NULL,\n \"publication_generation_id\" UUID NOT NULL,\n \"base_head_revision\" INTEGER NOT NULL,\n \"candidate_publication_id\" UUID,\n \"candidate_fingerprint\" VARCHAR(86),\n \"checkpoint\" VARCHAR(32) NOT NULL,\n \"run_state\" VARCHAR(16) NOT NULL,\n \"active_slot\" INTEGER,\n \"execution_attempts\" INTEGER NOT NULL,\n \"max_execution_attempts\" INTEGER NOT NULL,\n \"queue_job_id\" VARCHAR(255),\n \"external_job_id\" VARCHAR(255),\n \"worker_id\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"heartbeat_at\" TIMESTAMPTZ,\n \"retry_at\" TIMESTAMPTZ,\n \"last_error_code\" VARCHAR(64),\n \"last_error_message\" TEXT,\n \"row_version\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"started_at\" TIMESTAMPTZ,\n \"completed_at\" TIMESTAMPTZ,\n CONSTRAINT \"document_compilation_attempts_generation_nonzero_ck\"\n CHECK (\"publication_generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid),\n CONSTRAINT \"document_compilation_attempts_active_slot_ck\"\n CHECK (\"active_slot\" IS NULL OR \"active_slot\" = 1),\n CONSTRAINT \"document_compilation_attempts_document_version_ck\"\n CHECK (\"document_version\" > 0),\n CONSTRAINT \"document_compilation_attempts_base_revision_ck\"\n CHECK (\"base_head_revision\" >= 0),\n CONSTRAINT \"document_compilation_attempts_execution_count_ck\"\n CHECK (\n \"execution_attempts\" >= 0\n AND \"max_execution_attempts\" > 0\n AND \"execution_attempts\" <= \"max_execution_attempts\"\n ),\n CONSTRAINT \"document_compilation_attempts_row_version_ck\"\n CHECK (\"row_version\" >= 0),\n CONSTRAINT \"document_compilation_attempts_checkpoint_ck\"\n CHECK (\n \"checkpoint\" IN (\n 'queued',\n 'parsed',\n 'outline_built',\n 'nodes_generated',\n 'projection_built',\n 'smoke_eval_passed',\n 'published'\n )\n ),\n CONSTRAINT \"document_compilation_attempts_run_state_ck\"\n CHECK (\n \"run_state\" IN (\n 'dispatch_pending',\n 'queued',\n 'running',\n 'retry_wait',\n 'succeeded',\n 'failed',\n 'canceled',\n 'superseded'\n )\n ),\n CONSTRAINT \"document_compilation_attempts_lifecycle_ck\"\n CHECK (\n (\n \"run_state\" IN ('succeeded', 'failed', 'canceled', 'superseded')\n AND \"active_slot\" IS NULL\n AND \"completed_at\" IS NOT NULL\n )\n OR (\n \"run_state\" IN ('dispatch_pending', 'queued', 'running', 'retry_wait')\n AND \"active_slot\" = 1\n AND \"completed_at\" IS NULL\n )\n ),\n CONSTRAINT \"document_compilation_attempts_retry_schedule_ck\"\n CHECK (\n (\n \"run_state\" = 'retry_wait'\n AND \"retry_at\" IS NOT NULL\n )\n OR (\n \"run_state\" <> 'retry_wait'\n AND \"retry_at\" IS NULL\n )\n ),\n CONSTRAINT \"document_compilation_attempts_candidate_pair_ck\"\n CHECK (\n (\n \"candidate_publication_id\" IS NULL\n AND \"candidate_fingerprint\" IS NULL\n )\n OR (\n \"candidate_publication_id\" IS NOT NULL\n AND \"candidate_fingerprint\" IS NOT NULL\n )\n ),\n CONSTRAINT \"document_compilation_attempts_lease_state_ck\"\n CHECK (\n (\n \"run_state\" = 'running'\n AND \"worker_id\" IS NOT NULL\n AND \"lease_token\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL\n AND \"heartbeat_at\" IS NOT NULL\n )\n OR (\n \"run_state\" <> 'running'\n AND \"worker_id\" IS NULL\n AND \"lease_token\" IS NULL\n AND \"lease_expires_at\" IS NULL\n AND \"heartbeat_at\" IS NULL\n )\n ),\n CONSTRAINT \"document_compilation_attempts_lease_token_ck\"\n CHECK (\n \"lease_token\" IS NULL\n OR \"lease_token\" <> '00000000-0000-0000-0000-000000000000'::uuid\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\")\n ON DELETE CASCADE,\n FOREIGN KEY (\"knowledge_space_id\", \"document_asset_id\", \"document_version\")\n REFERENCES \"document_assets\" (\"knowledge_space_id\", \"id\", \"version\")\n ON DELETE CASCADE,\n FOREIGN KEY (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"candidate_publication_id\",\n \"candidate_fingerprint\"\n )\n REFERENCES \"projection_set_publications\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"id\",\n \"fingerprint\"\n )\n ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_compilation_attempts_scope_version_active_uq\"\n ON \"document_compilation_attempts\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_asset_id\",\n \"document_version\",\n \"active_slot\"\n );\nCREATE INDEX IF NOT EXISTS \"document_compilation_attempts_run_schedule_idx\"\n ON \"document_compilation_attempts\" (\"run_state\", \"retry_at\", \"created_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"document_compilation_attempts_lease_recovery_idx\"\n ON \"document_compilation_attempts\" (\n \"run_state\",\n \"lease_expires_at\",\n \"heartbeat_at\",\n \"id\"\n );\nCREATE INDEX IF NOT EXISTS \"document_compilation_attempts_document_version_idx\"\n ON \"document_compilation_attempts\" (\n \"knowledge_space_id\",\n \"document_asset_id\",\n \"document_version\",\n \"id\"\n );\nCREATE INDEX IF NOT EXISTS \"document_compilation_attempts_candidate_idx\"\n ON \"document_compilation_attempts\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"candidate_publication_id\",\n \"candidate_fingerprint\",\n \"id\"\n );\nCREATE INDEX IF NOT EXISTS \"document_compilation_attempts_tenant_completed_idx\"\n ON \"document_compilation_attempts\" (\"tenant_id\", \"completed_at\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"document_compilation_outbox\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"attempt_id\" UUID NOT NULL,\n \"event_type\" VARCHAR(64) NOT NULL,\n \"schema_version\" INTEGER NOT NULL,\n \"payload\" JSONB NOT NULL,\n \"idempotency_key\" VARCHAR(255) NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"dispatch_attempts\" INTEGER NOT NULL,\n \"available_at\" TIMESTAMPTZ NOT NULL,\n \"locked_by\" VARCHAR(255),\n \"lock_token\" UUID,\n \"locked_until\" TIMESTAMPTZ,\n \"queue_job_id\" VARCHAR(255),\n \"external_job_id\" VARCHAR(255),\n \"delivered_at\" TIMESTAMPTZ,\n \"last_error\" TEXT,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"document_compilation_outbox_event_type_ck\"\n CHECK (\"event_type\" = 'document.compile'),\n CONSTRAINT \"document_compilation_outbox_schema_version_ck\"\n CHECK (\"schema_version\" = 1),\n CONSTRAINT \"document_compilation_outbox_status_ck\"\n CHECK (\n \"status\" IN (\n 'pending',\n 'dispatching',\n 'dispatched',\n 'leased',\n 'completed',\n 'canceled',\n 'dead'\n )\n ),\n CONSTRAINT \"document_compilation_outbox_dispatch_attempts_ck\"\n CHECK (\"dispatch_attempts\" >= 0),\n CONSTRAINT \"document_compilation_outbox_lock_state_ck\"\n CHECK (\n (\n \"status\" = 'dispatching'\n AND \"locked_by\" IS NOT NULL\n AND \"lock_token\" IS NOT NULL\n AND \"locked_until\" IS NOT NULL\n )\n OR (\n \"status\" <> 'dispatching'\n AND \"locked_by\" IS NULL\n AND \"lock_token\" IS NULL\n AND \"locked_until\" IS NULL\n )\n ),\n CONSTRAINT \"document_compilation_outbox_lock_token_ck\"\n CHECK (\n \"lock_token\" IS NULL\n OR \"lock_token\" <> '00000000-0000-0000-0000-000000000000'::uuid\n ),\n FOREIGN KEY (\"attempt_id\")\n REFERENCES \"document_compilation_attempts\" (\"id\")\n ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_compilation_outbox_attempt_event_uq\"\n ON \"document_compilation_outbox\" (\"attempt_id\", \"event_type\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_compilation_outbox_idempotency_uq\"\n ON \"document_compilation_outbox\" (\"idempotency_key\");\nCREATE INDEX IF NOT EXISTS \"document_compilation_outbox_delivery_due_idx\"\n ON \"document_compilation_outbox\" (\"status\", \"available_at\", \"created_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"document_compilation_outbox_lock_recovery_idx\"\n ON \"document_compilation_outbox\" (\"status\", \"locked_until\", \"created_at\", \"id\");\n", path: "packages/database/migrations/0006_document_compilation_attempts.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0006_document_compilation_attempts\n-- Dialect: tidb\n\n-- Compilation state and queue publication live in one database transaction. active_slot is 1 only\n-- while work is live; terminal rows are retained unless an explicit manual retry reactivates a\n-- failed logical attempt. The unique key prevents two active attempts for the same tenant-scoped\n-- document version.\n-- TiDB requires v8.5+ with CHECK and foreign-key enforcement enabled, as verified by the runner.\nALTER TABLE `knowledge_spaces`\n ADD CONSTRAINT `knowledge_spaces_tenant_id_length_ck`\n CHECK (CHAR_LENGTH(`tenant_id`) <= 255);\nALTER TABLE `knowledge_spaces`\n MODIFY COLUMN `tenant_id` VARCHAR(255) NOT NULL;\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_spaces_tenant_id_uq`\n ON `knowledge_spaces` (`tenant_id`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `document_assets_space_id_version_uq`\n ON `document_assets` (`knowledge_space_id`, `id`, `version`);\nCREATE UNIQUE INDEX IF NOT EXISTS `projection_set_publications_space_id_fingerprint_uq`\n ON `projection_set_publications` (\n `tenant_id`,\n `knowledge_space_id`,\n `id`,\n `fingerprint`\n );\n\nCREATE TABLE IF NOT EXISTS `document_compilation_attempts` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `document_version` INT NOT NULL,\n `publication_generation_id` CHAR(36) NOT NULL,\n `base_head_revision` INT NOT NULL,\n `candidate_publication_id` CHAR(36),\n `candidate_fingerprint` VARCHAR(86),\n `checkpoint` VARCHAR(32) NOT NULL,\n `run_state` VARCHAR(16) NOT NULL,\n `active_slot` INT,\n `execution_attempts` INT NOT NULL,\n `max_execution_attempts` INT NOT NULL,\n `queue_job_id` VARCHAR(255),\n `external_job_id` VARCHAR(255),\n `worker_id` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `heartbeat_at` DATETIME(3),\n `retry_at` DATETIME(3),\n `last_error_code` VARCHAR(64),\n `last_error_message` TEXT,\n `row_version` INT NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `started_at` DATETIME(3),\n `completed_at` DATETIME(3),\n CONSTRAINT `document_compilation_attempts_generation_nonzero_ck`\n CHECK (\n `publication_generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `publication_generation_id` <> '00000000-0000-0000-0000-000000000000'\n ),\n CONSTRAINT `document_compilation_attempts_active_slot_ck`\n CHECK (`active_slot` IS NULL OR `active_slot` = 1),\n CONSTRAINT `document_compilation_attempts_base_revision_ck`\n CHECK (`base_head_revision` >= 0),\n CONSTRAINT `document_compilation_attempts_execution_count_ck`\n CHECK (\n `execution_attempts` >= 0\n AND `max_execution_attempts` > 0\n AND `execution_attempts` <= `max_execution_attempts`\n ),\n CONSTRAINT `document_compilation_attempts_row_version_ck`\n CHECK (`row_version` >= 0),\n CONSTRAINT `document_compilation_attempts_checkpoint_ck`\n CHECK (\n `checkpoint` IN (\n 'queued',\n 'parsed',\n 'outline_built',\n 'nodes_generated',\n 'projection_built',\n 'smoke_eval_passed',\n 'published'\n )\n ),\n CONSTRAINT `document_compilation_attempts_run_state_ck`\n CHECK (\n `run_state` IN (\n 'dispatch_pending',\n 'queued',\n 'running',\n 'retry_wait',\n 'succeeded',\n 'failed',\n 'canceled',\n 'superseded'\n )\n ),\n CONSTRAINT `document_compilation_attempts_lifecycle_ck`\n CHECK (\n (\n `run_state` IN ('succeeded', 'failed', 'canceled', 'superseded')\n AND `active_slot` IS NULL\n AND `completed_at` IS NOT NULL\n )\n OR (\n `run_state` IN ('dispatch_pending', 'queued', 'running', 'retry_wait')\n AND `active_slot` = 1\n AND `completed_at` IS NULL\n )\n ),\n CONSTRAINT `document_compilation_attempts_retry_schedule_ck`\n CHECK (\n (\n `run_state` = 'retry_wait'\n AND `retry_at` IS NOT NULL\n )\n OR (\n `run_state` <> 'retry_wait'\n AND `retry_at` IS NULL\n )\n ),\n CONSTRAINT `document_compilation_attempts_lease_state_ck`\n CHECK (\n (\n `run_state` = 'running'\n AND `worker_id` IS NOT NULL\n AND `lease_token` IS NOT NULL\n AND `lease_expires_at` IS NOT NULL\n AND `heartbeat_at` IS NOT NULL\n )\n OR (\n `run_state` <> 'running'\n AND `worker_id` IS NULL\n AND `lease_token` IS NULL\n AND `lease_expires_at` IS NULL\n AND `heartbeat_at` IS NULL\n )\n ),\n CONSTRAINT `document_compilation_attempts_lease_token_ck`\n CHECK (\n `lease_token` IS NULL\n OR (\n `lease_token` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `lease_token` <> '00000000-0000-0000-0000-000000000000'\n )\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`)\n ON DELETE CASCADE,\n FOREIGN KEY (`knowledge_space_id`, `document_asset_id`, `document_version`)\n REFERENCES `document_assets` (`knowledge_space_id`, `id`, `version`)\n ON DELETE CASCADE,\n FOREIGN KEY (\n `tenant_id`,\n `knowledge_space_id`,\n `candidate_publication_id`,\n `candidate_fingerprint`\n )\n REFERENCES `projection_set_publications` (\n `tenant_id`,\n `knowledge_space_id`,\n `id`,\n `fingerprint`\n )\n ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `document_compilation_attempts_scope_version_active_uq`\n ON `document_compilation_attempts` (\n `tenant_id`,\n `knowledge_space_id`,\n `document_asset_id`,\n `document_version`,\n `active_slot`\n );\nCREATE INDEX IF NOT EXISTS `document_compilation_attempts_run_schedule_idx`\n ON `document_compilation_attempts` (`run_state`, `retry_at`, `created_at`, `id`);\nCREATE INDEX IF NOT EXISTS `document_compilation_attempts_lease_recovery_idx`\n ON `document_compilation_attempts` (\n `run_state`,\n `lease_expires_at`,\n `heartbeat_at`,\n `id`\n );\nCREATE INDEX IF NOT EXISTS `document_compilation_attempts_document_version_idx`\n ON `document_compilation_attempts` (\n `knowledge_space_id`,\n `document_asset_id`,\n `document_version`,\n `id`\n );\nCREATE INDEX IF NOT EXISTS `document_compilation_attempts_candidate_idx`\n ON `document_compilation_attempts` (\n `tenant_id`,\n `knowledge_space_id`,\n `candidate_publication_id`,\n `candidate_fingerprint`,\n `id`\n );\nCREATE INDEX IF NOT EXISTS `document_compilation_attempts_tenant_completed_idx`\n ON `document_compilation_attempts` (`tenant_id`, `completed_at`, `id`);\n\nCREATE TABLE IF NOT EXISTS `document_compilation_outbox` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `attempt_id` CHAR(36) NOT NULL,\n `event_type` VARCHAR(64) NOT NULL,\n `schema_version` INT NOT NULL,\n `payload` JSON NOT NULL,\n `idempotency_key` VARCHAR(255) NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `dispatch_attempts` INT NOT NULL,\n `available_at` DATETIME(3) NOT NULL,\n `locked_by` VARCHAR(255),\n `lock_token` CHAR(36),\n `locked_until` DATETIME(3),\n `queue_job_id` VARCHAR(255),\n `external_job_id` VARCHAR(255),\n `delivered_at` DATETIME(3),\n `last_error` TEXT,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `document_compilation_outbox_event_type_ck`\n CHECK (`event_type` = 'document.compile'),\n CONSTRAINT `document_compilation_outbox_schema_version_ck`\n CHECK (`schema_version` = 1),\n CONSTRAINT `document_compilation_outbox_status_ck`\n CHECK (\n `status` IN (\n 'pending',\n 'dispatching',\n 'dispatched',\n 'leased',\n 'completed',\n 'canceled',\n 'dead'\n )\n ),\n CONSTRAINT `document_compilation_outbox_dispatch_attempts_ck`\n CHECK (`dispatch_attempts` >= 0),\n CONSTRAINT `document_compilation_outbox_lock_state_ck`\n CHECK (\n (\n `status` = 'dispatching'\n AND `locked_by` IS NOT NULL\n AND `lock_token` IS NOT NULL\n AND `locked_until` IS NOT NULL\n )\n OR (\n `status` <> 'dispatching'\n AND `locked_by` IS NULL\n AND `lock_token` IS NULL\n AND `locked_until` IS NULL\n )\n ),\n CONSTRAINT `document_compilation_outbox_lock_token_ck`\n CHECK (\n `lock_token` IS NULL\n OR (\n `lock_token` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `lock_token` <> '00000000-0000-0000-0000-000000000000'\n )\n ),\n FOREIGN KEY (`attempt_id`)\n REFERENCES `document_compilation_attempts` (`id`)\n ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `document_compilation_outbox_attempt_event_uq`\n ON `document_compilation_outbox` (`attempt_id`, `event_type`);\nCREATE UNIQUE INDEX IF NOT EXISTS `document_compilation_outbox_idempotency_uq`\n ON `document_compilation_outbox` (`idempotency_key`);\nCREATE INDEX IF NOT EXISTS `document_compilation_outbox_delivery_due_idx`\n ON `document_compilation_outbox` (`status`, `available_at`, `created_at`, `id`);\nCREATE INDEX IF NOT EXISTS `document_compilation_outbox_lock_recovery_idx`\n ON `document_compilation_outbox` (`status`, `locked_until`, `created_at`, `id`);\n", path: "packages/database/migrations/0006_document_compilation_attempts.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0007_knowledge_node_generations\n-- Dialect: postgres\n\n-- Knowledge nodes are immutable-build components. NULL remains the legacy publication scope;\n-- non-NULL generations can be built and evaluated without mutating currently readable nodes.\nALTER TABLE \"knowledge_nodes\"\n ADD COLUMN IF NOT EXISTS \"publication_generation_id\" UUID;\n\n-- A durable attempt cannot claim that a candidate projection snapshot exists until the candidate\n-- publication identity has been atomically bound to the attempt.\nALTER TABLE \"document_compilation_attempts\"\n ADD CONSTRAINT \"document_compilation_attempts_candidate_checkpoint_ck\"\n CHECK (\n \"checkpoint\" NOT IN ('projection_built', 'smoke_eval_passed', 'published')\n OR (\n \"candidate_publication_id\" IS NOT NULL\n AND \"candidate_fingerprint\" IS NOT NULL\n )\n );\n\n-- The zero UUID is reserved only for mapping legacy NULL into the logical unique index. Adding the\n-- constraint before enabling generation writers fails closed if historical data violates it.\nALTER TABLE \"knowledge_nodes\"\n ADD CONSTRAINT \"knowledge_nodes_pub_gen_nonzero_ck\"\n CHECK (\n \"publication_generation_id\" IS NULL\n OR \"publication_generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid\n );\n\n-- Retained generations must not amplify the two high-frequency node walks.\nDROP INDEX IF EXISTS \"knowledge_nodes_space_asset_kind_idx\";\nCREATE INDEX IF NOT EXISTS \"knowledge_nodes_space_asset_kind_idx\"\n ON \"knowledge_nodes\" (\n \"knowledge_space_id\",\n \"publication_generation_id\",\n \"document_asset_id\",\n \"kind\",\n \"id\"\n );\n\nDROP INDEX IF EXISTS \"knowledge_nodes_artifact_offset_idx\";\nCREATE INDEX IF NOT EXISTS \"knowledge_nodes_artifact_offset_idx\"\n ON \"knowledge_nodes\" (\n \"knowledge_space_id\",\n \"parse_artifact_id\",\n \"publication_generation_id\",\n \"start_offset\",\n \"id\"\n );\n\n-- Do not delete or arbitrarily merge historical duplicates. Unique-index creation intentionally\n-- aborts the migration so an operator can reconcile evidence-preserving node identity explicitly.\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_nodes_artifact_kind_offsets_uq\"\n ON \"knowledge_nodes\" (\n \"knowledge_space_id\",\n \"parse_artifact_id\",\n \"kind\",\n \"start_offset\",\n \"end_offset\",\n (COALESCE(\"publication_generation_id\", '00000000-0000-0000-0000-000000000000'::uuid))\n );\n", path: "packages/database/migrations/0007_knowledge_node_generations.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0007_knowledge_node_generations\n-- Dialect: tidb\n\n-- Knowledge nodes are immutable-build components. NULL remains the legacy publication scope;\n-- non-NULL generations can be built and evaluated without mutating currently readable nodes.\n-- TiDB requires v8.5+ with CHECK and foreign-key enforcement enabled, as verified by the runner.\nALTER TABLE `knowledge_nodes`\n ADD COLUMN IF NOT EXISTS `publication_generation_id` CHAR(36);\nALTER TABLE `knowledge_nodes`\n MODIFY COLUMN IF EXISTS `kind` VARCHAR(16) NOT NULL;\nALTER TABLE `knowledge_nodes`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\n\n-- TiDB does not permit CHECK constraints to reference columns used by a foreign key referential\n-- action. Candidate pair/checkpoint invariants remain transactionally enforced by the compilation\n-- repository; PostgreSQL additionally keeps the database CHECK constraints.\n\n-- The zero UUID is reserved only for mapping legacy NULL into the logical unique index. Adding the\n-- constraint before enabling generation writers fails closed if historical data violates it.\nALTER TABLE `knowledge_nodes`\n ADD CONSTRAINT `knowledge_nodes_pub_gen_nonzero_ck`\n CHECK (\n `publication_generation_id` IS NULL\n OR (\n `publication_generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `publication_generation_id` <> '00000000-0000-0000-0000-000000000000'\n )\n );\n\n-- Retained generations must not amplify the two high-frequency node walks.\nDROP INDEX IF EXISTS `knowledge_nodes_space_asset_kind_idx` ON `knowledge_nodes`;\nCREATE INDEX IF NOT EXISTS `knowledge_nodes_space_asset_kind_idx`\n ON `knowledge_nodes` (\n `knowledge_space_id`,\n `publication_generation_id`,\n `document_asset_id`,\n `kind`,\n `id`\n );\n\nDROP INDEX IF EXISTS `knowledge_nodes_artifact_offset_idx` ON `knowledge_nodes`;\nCREATE INDEX IF NOT EXISTS `knowledge_nodes_artifact_offset_idx`\n ON `knowledge_nodes` (\n `knowledge_space_id`,\n `parse_artifact_id`,\n `publication_generation_id`,\n `start_offset`,\n `id`\n );\n\n-- Do not delete or arbitrarily merge historical duplicates. Unique-index creation intentionally\n-- aborts the migration so an operator can reconcile evidence-preserving node identity explicitly.\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_nodes_artifact_kind_offsets_uq`\n ON `knowledge_nodes` (\n `knowledge_space_id`,\n `parse_artifact_id`,\n `kind`,\n `start_offset`,\n `end_offset`,\n `publication_generation_key`\n );\n", path: "packages/database/migrations/0007_knowledge_node_generations.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0008_flattened_page_index\n-- Dialect: postgres\n\nCREATE TABLE IF NOT EXISTS \"page_index_manifests\" (\n \"id\" UUID PRIMARY KEY,\n \"knowledge_space_id\" UUID NOT NULL,\n \"publication_generation_id\" UUID NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"document_outline_id\" UUID NOT NULL,\n \"document_version\" INTEGER NOT NULL,\n \"tokenizer_version\" VARCHAR(64) NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"node_count\" INTEGER NOT NULL,\n \"term_count\" INTEGER NOT NULL,\n \"checksum\" VARCHAR(64) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"page_index_manifests_space_fk\" FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE,\n CONSTRAINT \"page_index_manifests_outline_fk\" FOREIGN KEY (\"document_outline_id\") REFERENCES \"document_outlines\" (\"id\") ON DELETE CASCADE,\n CONSTRAINT \"page_index_manifests_generation_nonzero_ck\" CHECK (\"publication_generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid),\n CONSTRAINT \"page_index_manifests_status_ck\" CHECK (\"status\" IN ('building', 'ready')),\n CONSTRAINT \"page_index_manifests_counts_ck\" CHECK (\"node_count\" >= 0 AND \"term_count\" >= 0)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"page_index_manifests_outline_generation_uq\"\n ON \"page_index_manifests\" (\"knowledge_space_id\", \"document_outline_id\", \"publication_generation_id\");\nCREATE INDEX IF NOT EXISTS \"page_index_manifests_ready_scope_idx\"\n ON \"page_index_manifests\" (\"knowledge_space_id\", \"status\", \"document_outline_id\", \"publication_generation_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"page_index_nodes\" (\n \"id\" UUID PRIMARY KEY,\n \"manifest_id\" UUID NOT NULL,\n \"outline_node_id\" VARCHAR(512) NOT NULL,\n \"parent_outline_node_id\" VARCHAR(512),\n \"title\" TEXT NOT NULL,\n \"summary\" TEXT,\n \"section_path\" JSONB NOT NULL,\n \"visited_node_ids\" JSONB NOT NULL,\n \"level\" INTEGER NOT NULL,\n \"start_offset\" INTEGER,\n \"end_offset\" INTEGER,\n \"toc_source\" VARCHAR(32) NOT NULL,\n CONSTRAINT \"page_index_nodes_manifest_fk\" FOREIGN KEY (\"manifest_id\") REFERENCES \"page_index_manifests\" (\"id\") ON DELETE CASCADE,\n CONSTRAINT \"page_index_nodes_level_ck\" CHECK (\"level\" > 0),\n CONSTRAINT \"page_index_nodes_range_ck\" CHECK (\"start_offset\" IS NULL OR \"end_offset\" IS NULL OR \"end_offset\" >= \"start_offset\")\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"page_index_nodes_manifest_outline_node_uq\"\n ON \"page_index_nodes\" (\"manifest_id\", \"outline_node_id\");\nCREATE INDEX IF NOT EXISTS \"page_index_nodes_manifest_id_idx\"\n ON \"page_index_nodes\" (\"manifest_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"page_index_terms\" (\n \"id\" UUID PRIMARY KEY,\n \"knowledge_space_id\" UUID NOT NULL,\n \"manifest_id\" UUID NOT NULL,\n \"page_index_node_id\" UUID NOT NULL,\n \"term\" VARCHAR(128) NOT NULL,\n \"field_mask\" INTEGER NOT NULL,\n CONSTRAINT \"page_index_terms_space_fk\" FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE,\n CONSTRAINT \"page_index_terms_manifest_fk\" FOREIGN KEY (\"manifest_id\") REFERENCES \"page_index_manifests\" (\"id\") ON DELETE CASCADE,\n CONSTRAINT \"page_index_terms_node_fk\" FOREIGN KEY (\"page_index_node_id\") REFERENCES \"page_index_nodes\" (\"id\") ON DELETE CASCADE,\n CONSTRAINT \"page_index_terms_field_mask_ck\" CHECK (\"field_mask\" BETWEEN 1 AND 7)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"page_index_terms_manifest_node_term_uq\"\n ON \"page_index_terms\" (\"manifest_id\", \"page_index_node_id\", \"term\");\nCREATE INDEX IF NOT EXISTS \"page_index_terms_exact_lookup_idx\"\n ON \"page_index_terms\" (\"knowledge_space_id\", \"term\", \"page_index_node_id\", \"manifest_id\", \"field_mask\");\nCREATE INDEX IF NOT EXISTS \"page_index_terms_manifest_lookup_idx\"\n ON \"page_index_terms\" (\"knowledge_space_id\", \"manifest_id\", \"term\", \"page_index_node_id\", \"field_mask\");\n", path: "packages/database/migrations/0008_flattened_page_index.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0008_flattened_page_index\n-- Dialect: tidb\n\nCREATE TABLE IF NOT EXISTS `page_index_manifests` (\n `id` CHAR(36) PRIMARY KEY,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `publication_generation_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `document_outline_id` CHAR(36) NOT NULL,\n `document_version` INT NOT NULL,\n `tokenizer_version` VARCHAR(64) NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `node_count` INT NOT NULL,\n `term_count` INT NOT NULL,\n `checksum` VARCHAR(64) NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `page_index_manifests_space_fk` FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE,\n CONSTRAINT `page_index_manifests_outline_fk` FOREIGN KEY (`document_outline_id`) REFERENCES `document_outlines` (`id`) ON DELETE CASCADE,\n CONSTRAINT `page_index_manifests_generation_nonzero_ck` CHECK (`publication_generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$' AND `publication_generation_id` <> '00000000-0000-0000-0000-000000000000'),\n CONSTRAINT `page_index_manifests_status_ck` CHECK (`status` IN ('building', 'ready')),\n CONSTRAINT `page_index_manifests_counts_ck` CHECK (`node_count` >= 0 AND `term_count` >= 0)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `page_index_manifests_outline_generation_uq`\n ON `page_index_manifests` (`knowledge_space_id`, `document_outline_id`, `publication_generation_id`);\nCREATE INDEX IF NOT EXISTS `page_index_manifests_ready_scope_idx`\n ON `page_index_manifests` (`knowledge_space_id`, `status`, `document_outline_id`, `publication_generation_id`, `id`);\n\nCREATE TABLE IF NOT EXISTS `page_index_nodes` (\n `id` CHAR(36) PRIMARY KEY,\n `manifest_id` CHAR(36) NOT NULL,\n `outline_node_id` VARCHAR(512) NOT NULL,\n `parent_outline_node_id` VARCHAR(512),\n `title` TEXT NOT NULL,\n `summary` TEXT,\n `section_path` JSON NOT NULL,\n `visited_node_ids` JSON NOT NULL,\n `level` INT NOT NULL,\n `start_offset` INT,\n `end_offset` INT,\n `toc_source` VARCHAR(32) NOT NULL,\n CONSTRAINT `page_index_nodes_manifest_fk` FOREIGN KEY (`manifest_id`) REFERENCES `page_index_manifests` (`id`) ON DELETE CASCADE,\n CONSTRAINT `page_index_nodes_level_ck` CHECK (`level` > 0),\n CONSTRAINT `page_index_nodes_range_ck` CHECK (`start_offset` IS NULL OR `end_offset` IS NULL OR `end_offset` >= `start_offset`)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `page_index_nodes_manifest_outline_node_uq`\n ON `page_index_nodes` (`manifest_id`, `outline_node_id`);\nCREATE INDEX IF NOT EXISTS `page_index_nodes_manifest_id_idx`\n ON `page_index_nodes` (`manifest_id`, `id`);\n\nCREATE TABLE IF NOT EXISTS `page_index_terms` (\n `id` CHAR(36) PRIMARY KEY,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `manifest_id` CHAR(36) NOT NULL,\n `page_index_node_id` CHAR(36) NOT NULL,\n `term` VARCHAR(128) NOT NULL,\n `field_mask` INT NOT NULL,\n CONSTRAINT `page_index_terms_space_fk` FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE,\n CONSTRAINT `page_index_terms_manifest_fk` FOREIGN KEY (`manifest_id`) REFERENCES `page_index_manifests` (`id`) ON DELETE CASCADE,\n CONSTRAINT `page_index_terms_node_fk` FOREIGN KEY (`page_index_node_id`) REFERENCES `page_index_nodes` (`id`) ON DELETE CASCADE,\n CONSTRAINT `page_index_terms_field_mask_ck` CHECK (`field_mask` BETWEEN 1 AND 7)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `page_index_terms_manifest_node_term_uq`\n ON `page_index_terms` (`manifest_id`, `page_index_node_id`, `term`);\nCREATE INDEX IF NOT EXISTS `page_index_terms_exact_lookup_idx`\n ON `page_index_terms` (`knowledge_space_id`, `term`, `page_index_node_id`, `manifest_id`, `field_mask`);\nCREATE INDEX IF NOT EXISTS `page_index_terms_manifest_lookup_idx`\n ON `page_index_terms` (`knowledge_space_id`, `manifest_id`, `term`, `page_index_node_id`, `field_mask`);\n", path: "packages/database/migrations/0008_flattened_page_index.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0009_legacy_space_bootstrap\n-- Dialect: postgres\n\n-- Legacy NULL-generation artifacts cannot be adopted safely: historical Graph entities may span\n-- documents and therefore do not have a recoverable single-document owner. A bootstrap instead\n-- rebuilds the frozen document snapshot through the durable generation writer. The job row is\n-- also the query-readiness latch: strict readers remain unavailable until run_state=succeeded.\nCREATE TABLE IF NOT EXISTS \"legacy_space_publication_bootstraps\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"idempotency_key\" VARCHAR(255) NOT NULL,\n \"checkpoint\" VARCHAR(32) NOT NULL,\n \"run_state\" VARCHAR(16) NOT NULL,\n \"total_documents\" INTEGER NOT NULL,\n \"completed_documents\" INTEGER NOT NULL,\n \"worker_id\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"heartbeat_at\" TIMESTAMPTZ,\n \"last_error_code\" VARCHAR(64),\n \"last_error_message\" TEXT,\n \"row_version\" INTEGER NOT NULL,\n \"published_publication_id\" UUID,\n \"published_fingerprint\" VARCHAR(86),\n \"published_head_revision\" INTEGER,\n \"snapshot_metadata\" JSONB NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"completed_at\" TIMESTAMPTZ,\n CONSTRAINT \"legacy_space_bootstraps_checkpoint_ck\"\n CHECK (\n \"checkpoint\" IN (\n 'pending_snapshot',\n 'snapshot_captured',\n 'rebuilding',\n 'verifying',\n 'published'\n )\n ),\n CONSTRAINT \"legacy_space_bootstraps_run_state_ck\"\n CHECK (\"run_state\" IN ('queued', 'running', 'succeeded', 'failed', 'canceled')),\n CONSTRAINT \"legacy_space_bootstraps_counts_ck\"\n CHECK (\n \"total_documents\" >= 0\n AND \"completed_documents\" >= 0\n AND \"completed_documents\" <= \"total_documents\"\n ),\n CONSTRAINT \"legacy_space_bootstraps_row_version_ck\" CHECK (\"row_version\" >= 0),\n CONSTRAINT \"legacy_space_bootstraps_lease_state_ck\"\n CHECK (\n (\n \"run_state\" = 'running'\n AND \"worker_id\" IS NOT NULL\n AND \"lease_token\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL\n AND \"heartbeat_at\" IS NOT NULL\n AND \"completed_at\" IS NULL\n )\n OR (\n \"run_state\" <> 'running'\n AND \"worker_id\" IS NULL\n AND \"lease_token\" IS NULL\n AND \"lease_expires_at\" IS NULL\n AND \"heartbeat_at\" IS NULL\n )\n ),\n CONSTRAINT \"legacy_space_bootstraps_terminal_ck\"\n CHECK (\n (\"run_state\" IN ('succeeded', 'failed', 'canceled') AND \"completed_at\" IS NOT NULL)\n OR (\"run_state\" IN ('queued', 'running') AND \"completed_at\" IS NULL)\n ),\n CONSTRAINT \"legacy_space_bootstraps_publication_ck\"\n CHECK (\n (\n \"run_state\" = 'succeeded'\n AND \"checkpoint\" = 'published'\n AND \"completed_documents\" = \"total_documents\"\n AND (\n \"total_documents\" = 0\n OR (\n \"published_publication_id\" IS NOT NULL\n AND \"published_fingerprint\" IS NOT NULL\n AND \"published_head_revision\" IS NOT NULL\n AND \"published_head_revision\" > 0\n )\n )\n )\n OR \"run_state\" <> 'succeeded'\n ),\n CONSTRAINT \"legacy_space_bootstraps_lease_token_ck\"\n CHECK (\n \"lease_token\" IS NULL\n OR \"lease_token\" <> '00000000-0000-0000-0000-000000000000'::uuid\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\")\n ON DELETE CASCADE,\n FOREIGN KEY (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"published_publication_id\",\n \"published_fingerprint\"\n )\n REFERENCES \"projection_set_publications\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"id\",\n \"fingerprint\"\n )\n ON DELETE RESTRICT\n);\n\n-- One immutable migration ledger exists per tenant-scoped space. A failed job is retried in place,\n-- so operators cannot accidentally create a second snapshot with different membership.\nCREATE UNIQUE INDEX IF NOT EXISTS \"legacy_space_bootstraps_space_uq\"\n ON \"legacy_space_publication_bootstraps\" (\"tenant_id\", \"knowledge_space_id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"legacy_space_bootstraps_idempotency_uq\"\n ON \"legacy_space_publication_bootstraps\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"idempotency_key\"\n );\nCREATE INDEX IF NOT EXISTS \"legacy_space_bootstraps_claim_idx\"\n ON \"legacy_space_publication_bootstraps\" (\n \"run_state\",\n \"lease_expires_at\",\n \"updated_at\",\n \"id\"\n );\n\n-- Items freeze the complete document/version/hash set before any generation build is admitted.\n-- compilation_attempt_id is intentionally an audit reference rather than an FK: normal retention\n-- may delete terminal compilation attempts without weakening the bootstrap ledger.\nCREATE TABLE IF NOT EXISTS \"legacy_space_publication_bootstrap_items\" (\n \"bootstrap_id\" UUID NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"document_version\" INTEGER NOT NULL,\n \"document_sha256\" VARCHAR(64) NOT NULL,\n \"ordinal\" INTEGER NOT NULL,\n \"compilation_attempt_id\" UUID,\n \"status\" VARCHAR(16) NOT NULL,\n \"last_error\" TEXT,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n PRIMARY KEY (\"bootstrap_id\", \"document_asset_id\"),\n CONSTRAINT \"legacy_space_bootstrap_items_version_ck\" CHECK (\"document_version\" > 0),\n CONSTRAINT \"legacy_space_bootstrap_items_ordinal_ck\" CHECK (\"ordinal\" >= 0),\n CONSTRAINT \"legacy_space_bootstrap_items_status_ck\"\n CHECK (\"status\" IN ('pending', 'running', 'succeeded', 'failed')),\n FOREIGN KEY (\"bootstrap_id\")\n REFERENCES \"legacy_space_publication_bootstraps\" (\"id\")\n ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"legacy_space_bootstrap_items_ordinal_uq\"\n ON \"legacy_space_publication_bootstrap_items\" (\"bootstrap_id\", \"ordinal\");\nCREATE INDEX IF NOT EXISTS \"legacy_space_bootstrap_items_next_idx\"\n ON \"legacy_space_publication_bootstrap_items\" (\n \"bootstrap_id\",\n \"status\",\n \"ordinal\",\n \"document_asset_id\"\n );\nCREATE INDEX IF NOT EXISTS \"legacy_space_bootstrap_items_attempt_idx\"\n ON \"legacy_space_publication_bootstrap_items\" (\n \"compilation_attempt_id\",\n \"bootstrap_id\",\n \"document_asset_id\"\n );\n\n-- A document mutation holds this durable, space-exclusive lease from admission through its final\n-- metadata write. Bootstrap snapshot capture takes the same knowledge_spaces row lock and refuses\n-- to run while a lease exists, closing the check-then-write race. Leases never expire implicitly:\n-- a crashed writer fails closed until an operator proves it stopped and removes the orphan.\nCREATE TABLE IF NOT EXISTS \"knowledge_space_mutation_leases\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"operation\" VARCHAR(64) NOT NULL,\n \"acquired_at\" TIMESTAMPTZ NOT NULL,\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\")\n ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_mutation_leases_space_uq\"\n ON \"knowledge_space_mutation_leases\" (\"tenant_id\", \"knowledge_space_id\");\n\n-- Install the fail-closed latch in the same migration that introduces strict published reads.\n-- The marker deliberately does not copy the document set inside this DDL transaction. The bounded\n-- bootstrap runtime freezes that set under the stable space-row lock before admitting any build.\n-- knowledge_space_id is already a tenant-owned UUID and is safe as the one-time ledger id.\nINSERT INTO \"legacy_space_publication_bootstraps\" (\n \"id\",\n \"tenant_id\",\n \"knowledge_space_id\",\n \"idempotency_key\",\n \"checkpoint\",\n \"run_state\",\n \"total_documents\",\n \"completed_documents\",\n \"row_version\",\n \"snapshot_metadata\",\n \"created_at\",\n \"updated_at\"\n)\nSELECT\n ks.\"id\",\n ks.\"tenant_id\",\n ks.\"id\",\n 'legacy-space-publication-bootstrap-v1',\n 'pending_snapshot',\n 'queued',\n 0,\n 0,\n 0,\n '{\"schemaVersion\":1,\"strategy\":\"full-generation-rebuild\",\"source\":\"migration-marker\"}'::jsonb,\n CURRENT_TIMESTAMP,\n CURRENT_TIMESTAMP\nFROM \"knowledge_spaces\" ks\nWHERE NOT EXISTS (\n SELECT 1\n FROM \"projection_set_publication_heads\" head\n WHERE head.\"tenant_id\" = ks.\"tenant_id\"\n AND head.\"knowledge_space_id\" = ks.\"id\"\n)\nAND (\n EXISTS (\n SELECT 1 FROM \"document_assets\" asset\n WHERE asset.\"knowledge_space_id\" = ks.\"id\"\n )\n OR EXISTS (\n SELECT 1 FROM \"knowledge_nodes\" node\n WHERE node.\"knowledge_space_id\" = ks.\"id\"\n AND node.\"publication_generation_id\" IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM \"index_projections\" projection\n WHERE projection.\"knowledge_space_id\" = ks.\"id\"\n AND projection.\"publication_generation_id\" IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM \"document_outlines\" outline\n WHERE outline.\"knowledge_space_id\" = ks.\"id\"\n AND outline.\"publication_generation_id\" IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM \"document_multimodal_manifests\" manifest\n WHERE manifest.\"knowledge_space_id\" = ks.\"id\"\n AND manifest.\"publication_generation_id\" IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM \"knowledge_paths\" path\n WHERE path.\"knowledge_space_id\" = ks.\"id\"\n AND path.\"publication_generation_id\" IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM \"graph_entities\" entity\n WHERE entity.\"knowledge_space_id\" = ks.\"id\"\n AND entity.\"publication_generation_id\" IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM \"graph_relations\" relation\n WHERE relation.\"knowledge_space_id\" = ks.\"id\"\n AND relation.\"publication_generation_id\" IS NULL\n )\n)\nON CONFLICT (\"tenant_id\", \"knowledge_space_id\") DO NOTHING;\n", path: "packages/database/migrations/0009_legacy_space_bootstrap.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0009_legacy_space_bootstrap\n-- Dialect: tidb\n\n-- Legacy NULL-generation artifacts cannot be adopted safely: historical Graph entities may span\n-- documents and therefore do not have a recoverable single-document owner. A bootstrap instead\n-- rebuilds the frozen document snapshot through the durable generation writer. The job row is\n-- also the query-readiness latch: strict readers remain unavailable until run_state=succeeded.\nCREATE TABLE IF NOT EXISTS `legacy_space_publication_bootstraps` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `idempotency_key` VARCHAR(255) NOT NULL,\n `checkpoint` VARCHAR(32) NOT NULL,\n `run_state` VARCHAR(16) NOT NULL,\n `total_documents` INT NOT NULL,\n `completed_documents` INT NOT NULL,\n `worker_id` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `heartbeat_at` DATETIME(3),\n `last_error_code` VARCHAR(64),\n `last_error_message` TEXT,\n `row_version` INT NOT NULL,\n `published_publication_id` CHAR(36),\n `published_fingerprint` VARCHAR(86),\n `published_head_revision` INT,\n `snapshot_metadata` JSON NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `completed_at` DATETIME(3),\n CONSTRAINT `legacy_space_bootstraps_checkpoint_ck`\n CHECK (\n `checkpoint` IN (\n 'pending_snapshot',\n 'snapshot_captured',\n 'rebuilding',\n 'verifying',\n 'published'\n )\n ),\n CONSTRAINT `legacy_space_bootstraps_run_state_ck`\n CHECK (`run_state` IN ('queued', 'running', 'succeeded', 'failed', 'canceled')),\n CONSTRAINT `legacy_space_bootstraps_counts_ck`\n CHECK (\n `total_documents` >= 0\n AND `completed_documents` >= 0\n AND `completed_documents` <= `total_documents`\n ),\n CONSTRAINT `legacy_space_bootstraps_row_version_ck` CHECK (`row_version` >= 0),\n CONSTRAINT `legacy_space_bootstraps_lease_state_ck`\n CHECK (\n (\n `run_state` = 'running'\n AND `worker_id` IS NOT NULL\n AND `lease_token` IS NOT NULL\n AND `lease_expires_at` IS NOT NULL\n AND `heartbeat_at` IS NOT NULL\n AND `completed_at` IS NULL\n )\n OR (\n `run_state` <> 'running'\n AND `worker_id` IS NULL\n AND `lease_token` IS NULL\n AND `lease_expires_at` IS NULL\n AND `heartbeat_at` IS NULL\n )\n ),\n CONSTRAINT `legacy_space_bootstraps_terminal_ck`\n CHECK (\n (`run_state` IN ('succeeded', 'failed', 'canceled') AND `completed_at` IS NOT NULL)\n OR (`run_state` IN ('queued', 'running') AND `completed_at` IS NULL)\n ),\n CONSTRAINT `legacy_space_bootstraps_lease_token_ck`\n CHECK (\n `lease_token` IS NULL\n OR (\n `lease_token` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `lease_token` <> '00000000-0000-0000-0000-000000000000'\n )\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`)\n ON DELETE CASCADE,\n FOREIGN KEY (\n `tenant_id`,\n `knowledge_space_id`,\n `published_publication_id`,\n `published_fingerprint`\n )\n REFERENCES `projection_set_publications` (\n `tenant_id`,\n `knowledge_space_id`,\n `id`,\n `fingerprint`\n )\n ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `legacy_space_bootstraps_space_uq`\n ON `legacy_space_publication_bootstraps` (`tenant_id`, `knowledge_space_id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `legacy_space_bootstraps_idempotency_uq`\n ON `legacy_space_publication_bootstraps` (\n `tenant_id`,\n `knowledge_space_id`,\n `idempotency_key`\n );\nCREATE INDEX IF NOT EXISTS `legacy_space_bootstraps_claim_idx`\n ON `legacy_space_publication_bootstraps` (\n `run_state`,\n `lease_expires_at`,\n `updated_at`,\n `id`\n );\n\nCREATE TABLE IF NOT EXISTS `legacy_space_publication_bootstrap_items` (\n `bootstrap_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `document_version` INT NOT NULL,\n `document_sha256` VARCHAR(64) NOT NULL,\n `ordinal` INT NOT NULL,\n `compilation_attempt_id` CHAR(36),\n `status` VARCHAR(16) NOT NULL,\n `last_error` TEXT,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n PRIMARY KEY (`bootstrap_id`, `document_asset_id`),\n CONSTRAINT `legacy_space_bootstrap_items_version_ck` CHECK (`document_version` > 0),\n CONSTRAINT `legacy_space_bootstrap_items_ordinal_ck` CHECK (`ordinal` >= 0),\n CONSTRAINT `legacy_space_bootstrap_items_status_ck`\n CHECK (`status` IN ('pending', 'running', 'succeeded', 'failed')),\n FOREIGN KEY (`bootstrap_id`)\n REFERENCES `legacy_space_publication_bootstraps` (`id`)\n ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `legacy_space_bootstrap_items_ordinal_uq`\n ON `legacy_space_publication_bootstrap_items` (`bootstrap_id`, `ordinal`);\nCREATE INDEX IF NOT EXISTS `legacy_space_bootstrap_items_next_idx`\n ON `legacy_space_publication_bootstrap_items` (\n `bootstrap_id`,\n `status`,\n `ordinal`,\n `document_asset_id`\n );\nCREATE INDEX IF NOT EXISTS `legacy_space_bootstrap_items_attempt_idx`\n ON `legacy_space_publication_bootstrap_items` (\n `compilation_attempt_id`,\n `bootstrap_id`,\n `document_asset_id`\n );\n\nCREATE TABLE IF NOT EXISTS `knowledge_space_mutation_leases` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `operation` VARCHAR(64) NOT NULL,\n `acquired_at` DATETIME(3) NOT NULL,\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`)\n ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_mutation_leases_space_uq`\n ON `knowledge_space_mutation_leases` (`tenant_id`, `knowledge_space_id`);\n\n-- Pre-create a fail-closed marker for every pre-cutover space that has data but no published head.\n-- The bounded runtime captures the exact document set later under the stable space-row lock.\nINSERT IGNORE INTO `legacy_space_publication_bootstraps` (\n `id`,\n `tenant_id`,\n `knowledge_space_id`,\n `idempotency_key`,\n `checkpoint`,\n `run_state`,\n `total_documents`,\n `completed_documents`,\n `row_version`,\n `snapshot_metadata`,\n `created_at`,\n `updated_at`\n)\nSELECT\n ks.`id`,\n ks.`tenant_id`,\n ks.`id`,\n 'legacy-space-publication-bootstrap-v1',\n 'pending_snapshot',\n 'queued',\n 0,\n 0,\n 0,\n JSON_OBJECT(\n 'schemaVersion',\n 1,\n 'strategy',\n 'full-generation-rebuild',\n 'source',\n 'migration-marker'\n ),\n CURRENT_TIMESTAMP(3),\n CURRENT_TIMESTAMP(3)\nFROM `knowledge_spaces` ks\nWHERE NOT EXISTS (\n SELECT 1\n FROM `projection_set_publication_heads` head\n WHERE head.`tenant_id` = ks.`tenant_id`\n AND head.`knowledge_space_id` = ks.`id`\n)\nAND (\n EXISTS (\n SELECT 1 FROM `document_assets` asset\n WHERE asset.`knowledge_space_id` = ks.`id`\n )\n OR EXISTS (\n SELECT 1 FROM `knowledge_nodes` node\n WHERE node.`knowledge_space_id` = ks.`id`\n AND node.`publication_generation_id` IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM `index_projections` projection\n WHERE projection.`knowledge_space_id` = ks.`id`\n AND projection.`publication_generation_id` IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM `document_outlines` outline_row\n WHERE outline_row.`knowledge_space_id` = ks.`id`\n AND outline_row.`publication_generation_id` IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM `document_multimodal_manifests` manifest\n WHERE manifest.`knowledge_space_id` = ks.`id`\n AND manifest.`publication_generation_id` IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM `knowledge_paths` path_row\n WHERE path_row.`knowledge_space_id` = ks.`id`\n AND path_row.`publication_generation_id` IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM `graph_entities` entity\n WHERE entity.`knowledge_space_id` = ks.`id`\n AND entity.`publication_generation_id` IS NULL\n )\n OR EXISTS (\n SELECT 1 FROM `graph_relations` relation_row\n WHERE relation_row.`knowledge_space_id` = ks.`id`\n AND relation_row.`publication_generation_id` IS NULL\n )\n);\n", path: "packages/database/migrations/0009_legacy_space_bootstrap.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0010_page_index_upgrade_backfill\n-- Dialect: postgres\n\n-- One immutable upgrade job is frozen against one publication head. A later head is never\n-- accepted as evidence for this job; the runtime marks the old job superseded and creates a new\n-- job for the new head when that head is not already PageIndex-complete.\nCREATE TABLE IF NOT EXISTS \"page_index_upgrade_backfills\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"publication_id\" UUID NOT NULL,\n \"publication_fingerprint\" VARCHAR(86) NOT NULL,\n \"head_revision\" INTEGER NOT NULL,\n \"run_state\" VARCHAR(16) NOT NULL,\n \"total_items\" INTEGER NOT NULL,\n \"completed_items\" INTEGER NOT NULL,\n \"worker_id\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"heartbeat_at\" TIMESTAMPTZ,\n \"retry_count\" INTEGER NOT NULL,\n \"row_version\" INTEGER NOT NULL,\n \"last_error_code\" VARCHAR(64),\n \"last_error_message\" TEXT,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"completed_at\" TIMESTAMPTZ,\n CONSTRAINT \"page_index_upgrade_backfills_state_ck\"\n CHECK (\"run_state\" IN ('queued', 'running', 'succeeded', 'failed', 'superseded')),\n CONSTRAINT \"page_index_upgrade_backfills_counts_ck\"\n CHECK (\n \"total_items\" >= 0\n AND \"completed_items\" >= 0\n AND \"completed_items\" <= \"total_items\"\n ),\n CONSTRAINT \"page_index_upgrade_backfills_revision_ck\" CHECK (\"head_revision\" > 0),\n CONSTRAINT \"page_index_upgrade_backfills_retry_ck\" CHECK (\"retry_count\" >= 0),\n CONSTRAINT \"page_index_upgrade_backfills_row_version_ck\" CHECK (\"row_version\" >= 0),\n CONSTRAINT \"page_index_upgrade_backfills_lease_ck\"\n CHECK (\n (\n \"run_state\" = 'running'\n AND \"worker_id\" IS NOT NULL\n AND \"lease_token\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL\n AND \"heartbeat_at\" IS NOT NULL\n AND \"completed_at\" IS NULL\n )\n OR (\n \"run_state\" <> 'running'\n AND \"worker_id\" IS NULL\n AND \"lease_token\" IS NULL\n AND \"lease_expires_at\" IS NULL\n AND \"heartbeat_at\" IS NULL\n )\n ),\n CONSTRAINT \"page_index_upgrade_backfills_terminal_ck\"\n CHECK (\n (\"run_state\" IN ('succeeded', 'failed', 'superseded') AND \"completed_at\" IS NOT NULL)\n OR (\"run_state\" IN ('queued', 'running') AND \"completed_at\" IS NULL)\n ),\n CONSTRAINT \"page_index_upgrade_backfills_lease_token_ck\"\n CHECK (\n \"lease_token\" IS NULL\n OR \"lease_token\" <> '00000000-0000-0000-0000-000000000000'::uuid\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"publication_id\", \"publication_fingerprint\"\n ) REFERENCES \"projection_set_publications\" (\n \"tenant_id\", \"knowledge_space_id\", \"id\", \"fingerprint\"\n ) ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"page_index_upgrade_backfills_publication_uq\"\n ON \"page_index_upgrade_backfills\" (\"tenant_id\", \"knowledge_space_id\", \"publication_id\");\nCREATE INDEX IF NOT EXISTS \"page_index_upgrade_backfills_scope_idx\"\n ON \"page_index_upgrade_backfills\" (\n \"tenant_id\", \"knowledge_space_id\", \"head_revision\", \"updated_at\", \"id\"\n );\nCREATE INDEX IF NOT EXISTS \"page_index_upgrade_backfills_claim_idx\"\n ON \"page_index_upgrade_backfills\" (\n \"run_state\", \"lease_expires_at\", \"updated_at\", \"id\"\n );\n\nCREATE TABLE IF NOT EXISTS \"page_index_upgrade_backfill_items\" (\n \"backfill_id\" UUID NOT NULL,\n \"document_outline_id\" UUID NOT NULL,\n \"publication_generation_id\" UUID NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"document_version\" INTEGER NOT NULL,\n \"ordinal\" INTEGER NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n PRIMARY KEY (\"backfill_id\", \"document_outline_id\"),\n CONSTRAINT \"page_index_upgrade_items_generation_ck\"\n CHECK (\"publication_generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid),\n CONSTRAINT \"page_index_upgrade_items_version_ck\" CHECK (\"document_version\" > 0),\n CONSTRAINT \"page_index_upgrade_items_ordinal_ck\" CHECK (\"ordinal\" >= 0),\n CONSTRAINT \"page_index_upgrade_items_status_ck\" CHECK (\"status\" IN ('pending', 'succeeded')),\n FOREIGN KEY (\"backfill_id\")\n REFERENCES \"page_index_upgrade_backfills\" (\"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"page_index_upgrade_items_ordinal_uq\"\n ON \"page_index_upgrade_backfill_items\" (\"backfill_id\", \"ordinal\");\nCREATE INDEX IF NOT EXISTS \"page_index_upgrade_items_next_idx\"\n ON \"page_index_upgrade_backfill_items\" (\n \"backfill_id\", \"status\", \"ordinal\", \"document_outline_id\"\n );\n\n-- Freeze every current published head whose document-outline closure does not have a complete,\n-- immutable flattened PageIndex. The publication id is a stable UUID and doubles as the\n-- deterministic one-job-per-publication id.\nINSERT INTO \"page_index_upgrade_backfills\" (\n \"id\", \"tenant_id\", \"knowledge_space_id\", \"publication_id\",\n \"publication_fingerprint\", \"head_revision\", \"run_state\", \"total_items\",\n \"completed_items\", \"retry_count\", \"row_version\", \"created_at\", \"updated_at\"\n)\nSELECT\n head.\"publication_id\",\n head.\"tenant_id\",\n head.\"knowledge_space_id\",\n head.\"publication_id\",\n pub.\"fingerprint\",\n head.\"head_revision\",\n 'queued',\n (\n SELECT COUNT(*)\n FROM \"projection_set_publication_members\" all_pm\n WHERE all_pm.\"tenant_id\" = head.\"tenant_id\"\n AND all_pm.\"knowledge_space_id\" = head.\"knowledge_space_id\"\n AND all_pm.\"publication_id\" = head.\"publication_id\"\n AND all_pm.\"component_type\" = 'document-outline'\n ),\n 0,\n 0,\n 0,\n CURRENT_TIMESTAMP,\n CURRENT_TIMESTAMP\nFROM \"projection_set_publication_heads\" head\nJOIN \"projection_set_publications\" pub\n ON pub.\"tenant_id\" = head.\"tenant_id\"\n AND pub.\"knowledge_space_id\" = head.\"knowledge_space_id\"\n AND pub.\"id\" = head.\"publication_id\"\n AND pub.\"status\" = 'published'\nWHERE EXISTS (\n SELECT 1\n FROM \"projection_set_publication_members\" pm\n LEFT JOIN \"document_outlines\" outline_row\n ON outline_row.\"id\" = pm.\"component_key\"\n AND outline_row.\"knowledge_space_id\" = pm.\"knowledge_space_id\"\n AND outline_row.\"publication_generation_id\" = pm.\"generation_id\"\n AND outline_row.\"document_asset_id\" = pm.\"document_asset_id\"\n LEFT JOIN \"page_index_manifests\" manifest\n ON manifest.\"knowledge_space_id\" = pm.\"knowledge_space_id\"\n AND manifest.\"document_outline_id\" = pm.\"component_key\"\n AND manifest.\"publication_generation_id\" = pm.\"generation_id\"\n AND manifest.\"document_asset_id\" = pm.\"document_asset_id\"\n AND manifest.\"document_version\" = outline_row.\"version\"\n AND manifest.\"tokenizer_version\" = 'pageindex-nfkc-exact-v1'\n AND manifest.\"status\" = 'ready'\n WHERE pm.\"tenant_id\" = head.\"tenant_id\"\n AND pm.\"knowledge_space_id\" = head.\"knowledge_space_id\"\n AND pm.\"publication_id\" = head.\"publication_id\"\n AND pm.\"component_type\" = 'document-outline'\n AND (\n pm.\"generation_id\" = '00000000-0000-0000-0000-000000000000'::uuid\n OR outline_row.\"id\" IS NULL\n OR manifest.\"id\" IS NULL\n OR manifest.\"checksum\" !~ '^[0-9a-f]{64}$'\n OR manifest.\"node_count\" <= 0\n OR manifest.\"term_count\" <= 0\n OR manifest.\"node_count\" <> (\n SELECT COUNT(*) FROM \"page_index_nodes\" node_row\n WHERE node_row.\"manifest_id\" = manifest.\"id\"\n )\n OR manifest.\"term_count\" <> (\n SELECT COUNT(*) FROM \"page_index_terms\" term_row\n WHERE term_row.\"manifest_id\" = manifest.\"id\"\n )\n OR EXISTS (\n SELECT 1 FROM \"page_index_terms\" term_row\n LEFT JOIN \"page_index_nodes\" node_row\n ON node_row.\"id\" = term_row.\"page_index_node_id\"\n AND node_row.\"manifest_id\" = term_row.\"manifest_id\"\n WHERE term_row.\"manifest_id\" = manifest.\"id\"\n AND (\n term_row.\"knowledge_space_id\" <> pm.\"knowledge_space_id\"\n OR node_row.\"id\" IS NULL\n )\n )\n )\n)\nON CONFLICT (\"tenant_id\", \"knowledge_space_id\", \"publication_id\") DO NOTHING;\n\nINSERT INTO \"page_index_upgrade_backfill_items\" (\n \"backfill_id\", \"document_outline_id\", \"publication_generation_id\",\n \"document_asset_id\", \"document_version\", \"ordinal\", \"status\", \"created_at\", \"updated_at\"\n)\nSELECT\n job.\"id\",\n pm.\"component_key\",\n pm.\"generation_id\",\n pm.\"document_asset_id\",\n outline_row.\"version\",\n ROW_NUMBER() OVER (\n PARTITION BY job.\"id\" ORDER BY pm.\"component_key\", pm.\"generation_id\"\n ) - 1,\n 'pending',\n CURRENT_TIMESTAMP,\n CURRENT_TIMESTAMP\nFROM \"page_index_upgrade_backfills\" job\nJOIN \"projection_set_publication_members\" pm\n ON pm.\"tenant_id\" = job.\"tenant_id\"\n AND pm.\"knowledge_space_id\" = job.\"knowledge_space_id\"\n AND pm.\"publication_id\" = job.\"publication_id\"\n AND pm.\"component_type\" = 'document-outline'\nJOIN \"document_outlines\" outline_row\n ON outline_row.\"id\" = pm.\"component_key\"\n AND outline_row.\"knowledge_space_id\" = pm.\"knowledge_space_id\"\n AND outline_row.\"publication_generation_id\" = pm.\"generation_id\"\n AND outline_row.\"document_asset_id\" = pm.\"document_asset_id\"\nWHERE job.\"run_state\" = 'queued'\nON CONFLICT (\"backfill_id\", \"document_outline_id\") DO NOTHING;\n", path: "packages/database/migrations/0010_page_index_upgrade_backfill.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0010_page_index_upgrade_backfill\n-- Dialect: tidb\n\nCREATE TABLE IF NOT EXISTS `page_index_upgrade_backfills` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `publication_id` CHAR(36) NOT NULL,\n `publication_fingerprint` VARCHAR(86) NOT NULL,\n `head_revision` INT NOT NULL,\n `run_state` VARCHAR(16) NOT NULL,\n `total_items` INT NOT NULL,\n `completed_items` INT NOT NULL,\n `worker_id` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `heartbeat_at` DATETIME(3),\n `retry_count` INT NOT NULL,\n `row_version` INT NOT NULL,\n `last_error_code` VARCHAR(64),\n `last_error_message` TEXT,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `completed_at` DATETIME(3),\n CONSTRAINT `page_index_upgrade_backfills_state_ck`\n CHECK (`run_state` IN ('queued', 'running', 'succeeded', 'failed', 'superseded')),\n CONSTRAINT `page_index_upgrade_backfills_counts_ck`\n CHECK (`total_items` >= 0 AND `completed_items` >= 0 AND `completed_items` <= `total_items`),\n CONSTRAINT `page_index_upgrade_backfills_revision_ck` CHECK (`head_revision` > 0),\n CONSTRAINT `page_index_upgrade_backfills_retry_ck` CHECK (`retry_count` >= 0),\n CONSTRAINT `page_index_upgrade_backfills_row_version_ck` CHECK (`row_version` >= 0),\n CONSTRAINT `page_index_upgrade_backfills_lease_ck`\n CHECK (\n (\n `run_state` = 'running'\n AND `worker_id` IS NOT NULL\n AND `lease_token` IS NOT NULL\n AND `lease_expires_at` IS NOT NULL\n AND `heartbeat_at` IS NOT NULL\n AND `completed_at` IS NULL\n )\n OR (\n `run_state` <> 'running'\n AND `worker_id` IS NULL\n AND `lease_token` IS NULL\n AND `lease_expires_at` IS NULL\n AND `heartbeat_at` IS NULL\n )\n ),\n CONSTRAINT `page_index_upgrade_backfills_terminal_ck`\n CHECK (\n (`run_state` IN ('succeeded', 'failed', 'superseded') AND `completed_at` IS NOT NULL)\n OR (`run_state` IN ('queued', 'running') AND `completed_at` IS NULL)\n ),\n CONSTRAINT `page_index_upgrade_backfills_lease_token_ck`\n CHECK (\n `lease_token` IS NULL\n OR (\n `lease_token` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `lease_token` <> '00000000-0000-0000-0000-000000000000'\n )\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `publication_id`, `publication_fingerprint`\n ) REFERENCES `projection_set_publications` (\n `tenant_id`, `knowledge_space_id`, `id`, `fingerprint`\n ) ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `page_index_upgrade_backfills_publication_uq`\n ON `page_index_upgrade_backfills` (`tenant_id`, `knowledge_space_id`, `publication_id`);\nCREATE INDEX IF NOT EXISTS `page_index_upgrade_backfills_scope_idx`\n ON `page_index_upgrade_backfills` (\n `tenant_id`, `knowledge_space_id`, `head_revision`, `updated_at`, `id`\n );\nCREATE INDEX IF NOT EXISTS `page_index_upgrade_backfills_claim_idx`\n ON `page_index_upgrade_backfills` (`run_state`, `lease_expires_at`, `updated_at`, `id`);\n\nCREATE TABLE IF NOT EXISTS `page_index_upgrade_backfill_items` (\n `backfill_id` CHAR(36) NOT NULL,\n `document_outline_id` CHAR(36) NOT NULL,\n `publication_generation_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `document_version` INT NOT NULL,\n `ordinal` INT NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n PRIMARY KEY (`backfill_id`, `document_outline_id`),\n CONSTRAINT `page_index_upgrade_items_generation_ck`\n CHECK (\n `publication_generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `publication_generation_id` <> '00000000-0000-0000-0000-000000000000'\n ),\n CONSTRAINT `page_index_upgrade_items_version_ck` CHECK (`document_version` > 0),\n CONSTRAINT `page_index_upgrade_items_ordinal_ck` CHECK (`ordinal` >= 0),\n CONSTRAINT `page_index_upgrade_items_status_ck` CHECK (`status` IN ('pending', 'succeeded')),\n FOREIGN KEY (`backfill_id`) REFERENCES `page_index_upgrade_backfills` (`id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `page_index_upgrade_items_ordinal_uq`\n ON `page_index_upgrade_backfill_items` (`backfill_id`, `ordinal`);\nCREATE INDEX IF NOT EXISTS `page_index_upgrade_items_next_idx`\n ON `page_index_upgrade_backfill_items` (\n `backfill_id`, `status`, `ordinal`, `document_outline_id`\n );\n\nINSERT IGNORE INTO `page_index_upgrade_backfills` (\n `id`, `tenant_id`, `knowledge_space_id`, `publication_id`,\n `publication_fingerprint`, `head_revision`, `run_state`, `total_items`,\n `completed_items`, `retry_count`, `row_version`, `created_at`, `updated_at`\n)\nSELECT\n head.`publication_id`,\n head.`tenant_id`,\n head.`knowledge_space_id`,\n head.`publication_id`,\n pub.`fingerprint`,\n head.`head_revision`,\n 'queued',\n (\n SELECT COUNT(*)\n FROM `projection_set_publication_members` all_pm\n WHERE all_pm.`tenant_id` = head.`tenant_id`\n AND all_pm.`knowledge_space_id` = head.`knowledge_space_id`\n AND all_pm.`publication_id` = head.`publication_id`\n AND all_pm.`component_type` = 'document-outline'\n ),\n 0,\n 0,\n 0,\n CURRENT_TIMESTAMP(3),\n CURRENT_TIMESTAMP(3)\nFROM `projection_set_publication_heads` head\nJOIN `projection_set_publications` pub\n ON pub.`tenant_id` = head.`tenant_id`\n AND pub.`knowledge_space_id` = head.`knowledge_space_id`\n AND pub.`id` = head.`publication_id`\n AND pub.`status` = 'published'\nWHERE EXISTS (\n SELECT 1\n FROM `projection_set_publication_members` pm\n LEFT JOIN `document_outlines` outline_row\n ON outline_row.`id` = pm.`component_key`\n AND outline_row.`knowledge_space_id` = pm.`knowledge_space_id`\n AND outline_row.`publication_generation_id` = pm.`generation_id`\n AND outline_row.`document_asset_id` = pm.`document_asset_id`\n LEFT JOIN `page_index_manifests` manifest\n ON manifest.`knowledge_space_id` = pm.`knowledge_space_id`\n AND manifest.`document_outline_id` = pm.`component_key`\n AND manifest.`publication_generation_id` = pm.`generation_id`\n AND manifest.`document_asset_id` = pm.`document_asset_id`\n AND manifest.`document_version` = outline_row.`version`\n AND manifest.`tokenizer_version` = 'pageindex-nfkc-exact-v1'\n AND manifest.`status` = 'ready'\n WHERE pm.`tenant_id` = head.`tenant_id`\n AND pm.`knowledge_space_id` = head.`knowledge_space_id`\n AND pm.`publication_id` = head.`publication_id`\n AND pm.`component_type` = 'document-outline'\n AND (\n pm.`generation_id` = '00000000-0000-0000-0000-000000000000'\n OR outline_row.`id` IS NULL\n OR manifest.`id` IS NULL\n OR manifest.`checksum` NOT REGEXP '^[0-9a-f]{64}$'\n OR manifest.`node_count` <= 0\n OR manifest.`term_count` <= 0\n OR manifest.`node_count` <> (\n SELECT COUNT(*) FROM `page_index_nodes` node_row\n WHERE node_row.`manifest_id` = manifest.`id`\n )\n OR manifest.`term_count` <> (\n SELECT COUNT(*) FROM `page_index_terms` term_row\n WHERE term_row.`manifest_id` = manifest.`id`\n )\n OR EXISTS (\n SELECT 1 FROM `page_index_terms` term_row\n LEFT JOIN `page_index_nodes` node_row\n ON node_row.`id` = term_row.`page_index_node_id`\n AND node_row.`manifest_id` = term_row.`manifest_id`\n WHERE term_row.`manifest_id` = manifest.`id`\n AND (\n term_row.`knowledge_space_id` <> pm.`knowledge_space_id`\n OR node_row.`id` IS NULL\n )\n )\n )\n);\n\nINSERT IGNORE INTO `page_index_upgrade_backfill_items` (\n `backfill_id`, `document_outline_id`, `publication_generation_id`,\n `document_asset_id`, `document_version`, `ordinal`, `status`, `created_at`, `updated_at`\n)\nSELECT\n job.`id`,\n pm.`component_key`,\n pm.`generation_id`,\n pm.`document_asset_id`,\n outline_row.`version`,\n ROW_NUMBER() OVER (\n PARTITION BY job.`id` ORDER BY pm.`component_key`, pm.`generation_id`\n ) - 1,\n 'pending',\n CURRENT_TIMESTAMP(3),\n CURRENT_TIMESTAMP(3)\nFROM `page_index_upgrade_backfills` job\nJOIN `projection_set_publication_members` pm\n ON pm.`tenant_id` = job.`tenant_id`\n AND pm.`knowledge_space_id` = job.`knowledge_space_id`\n AND pm.`publication_id` = job.`publication_id`\n AND pm.`component_type` = 'document-outline'\nJOIN `document_outlines` outline_row\n ON outline_row.`id` = pm.`component_key`\n AND outline_row.`knowledge_space_id` = pm.`knowledge_space_id`\n AND outline_row.`publication_generation_id` = pm.`generation_id`\n AND outline_row.`document_asset_id` = pm.`document_asset_id`\nWHERE job.`run_state` = 'queued';\n", path: "packages/database/migrations/0010_page_index_upgrade_backfill.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0011_tidb_fts_postings\n-- Dialect: postgres\n\n-- PostgreSQL continues to query index_projections.fts_document through its native GIN index.\n-- Keep the portable posting catalog available so the schema and lifecycle contract remain the\n-- same across database dialects; application writes populate it only for TiDB.\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1\n FROM pg_constraint\n WHERE conname = 'projection_set_publications_status_ck'\n AND conrelid = 'projection_set_publications'::regclass\n ) THEN\n ALTER TABLE \"projection_set_publications\"\n ADD CONSTRAINT \"projection_set_publications_status_ck\"\n CHECK (\"status\" IN ('candidate', 'inactive', 'published', 'superseded', 'validating'));\n END IF;\nEND\n$$;\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"index_projections_space_id_uq\"\n ON \"index_projections\" (\"knowledge_space_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"index_projections_fts_backfill_idx\"\n ON \"index_projections\" (\"knowledge_space_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"index_projection_fts_postings\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"projection_id\" UUID NOT NULL,\n \"tokenizer_version\" VARCHAR(64) NOT NULL,\n \"term_hash\" CHAR(64) NOT NULL,\n \"term\" VARCHAR(128) NOT NULL,\n \"term_frequency\" INTEGER NOT NULL,\n \"document_token_count\" INTEGER NOT NULL,\n CONSTRAINT \"index_projection_fts_postings_frequency_ck\"\n CHECK (\"term_frequency\" > 0 AND \"document_token_count\" >= \"term_frequency\"),\n FOREIGN KEY (\"knowledge_space_id\") REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE,\n FOREIGN KEY (\"knowledge_space_id\", \"projection_id\")\n REFERENCES \"index_projections\" (\"knowledge_space_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"index_projection_fts_postings_projection_term_uq\"\n ON \"index_projection_fts_postings\" (\"projection_id\", \"tokenizer_version\", \"term_hash\");\nCREATE INDEX IF NOT EXISTS \"index_projection_fts_postings_lookup_idx\"\n ON \"index_projection_fts_postings\" (\"knowledge_space_id\", \"term_hash\", \"projection_id\");\n\n-- A durable, tenant-scoped cursor replaces any migration-runner backfill. Deployments can first\n-- enable the transactional dual writer, then let the bounded runtime lease and repair old spaces.\n-- PostgreSQL does not consume this ledger for native GIN reads, but retaining the table in both\n-- dialects keeps schema replay, operational tooling, and disaster-recovery artifacts symmetric.\nCREATE TABLE IF NOT EXISTS \"tidb_fts_posting_backfills\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"tokenizer_version\" VARCHAR(64) NOT NULL,\n \"run_state\" VARCHAR(16) NOT NULL,\n \"cursor_projection_id\" UUID,\n \"scanned_projections\" INTEGER NOT NULL,\n \"written_postings\" INTEGER NOT NULL,\n \"worker_id\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"heartbeat_at\" TIMESTAMPTZ,\n \"retry_count\" INTEGER NOT NULL,\n \"row_version\" INTEGER NOT NULL,\n \"last_error_code\" VARCHAR(64),\n \"last_error_message\" TEXT,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"completed_at\" TIMESTAMPTZ,\n CONSTRAINT \"tidb_fts_posting_backfills_state_ck\"\n CHECK (\"run_state\" IN ('queued', 'running', 'succeeded', 'failed')),\n CONSTRAINT \"tidb_fts_posting_backfills_counts_ck\"\n CHECK (\n \"scanned_projections\" >= 0\n AND \"written_postings\" >= 0\n AND \"retry_count\" >= 0\n AND \"row_version\" >= 0\n ),\n CONSTRAINT \"tidb_fts_posting_backfills_lease_ck\"\n CHECK (\n (\n \"run_state\" = 'running'\n AND \"worker_id\" IS NOT NULL\n AND \"lease_token\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL\n AND \"heartbeat_at\" IS NOT NULL\n AND \"completed_at\" IS NULL\n )\n OR (\n \"run_state\" <> 'running'\n AND \"worker_id\" IS NULL\n AND \"lease_token\" IS NULL\n AND \"lease_expires_at\" IS NULL\n AND \"heartbeat_at\" IS NULL\n )\n ),\n CONSTRAINT \"tidb_fts_posting_backfills_terminal_ck\"\n CHECK (\n (\"run_state\" IN ('succeeded', 'failed') AND \"completed_at\" IS NOT NULL)\n OR (\"run_state\" IN ('queued', 'running') AND \"completed_at\" IS NULL)\n ),\n CONSTRAINT \"tidb_fts_posting_backfills_lease_token_ck\"\n CHECK (\n \"lease_token\" IS NULL\n OR \"lease_token\" <> '00000000-0000-0000-0000-000000000000'::uuid\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"tidb_fts_posting_backfills_space_tokenizer_uq\"\n ON \"tidb_fts_posting_backfills\" (\n \"tenant_id\", \"knowledge_space_id\", \"tokenizer_version\"\n );\nCREATE INDEX IF NOT EXISTS \"tidb_fts_posting_backfills_claim_idx\"\n ON \"tidb_fts_posting_backfills\" (\n \"run_state\", \"lease_expires_at\", \"updated_at\", \"id\"\n );\nCREATE INDEX IF NOT EXISTS \"tidb_fts_posting_backfills_scope_idx\"\n ON \"tidb_fts_posting_backfills\" (\n \"tenant_id\", \"knowledge_space_id\", \"tokenizer_version\", \"id\"\n );\n", path: "packages/database/migrations/0011_tidb_fts_postings.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0011_tidb_fts_postings\n-- Dialect: tidb\n\n-- TiDB v8.5 has no executable FULLTEXT index. Store deterministic, projection-scoped postings\n-- instead of scanning index_projections.fts_document with INSTR/LIKE.\n-- TiDB v8.5 does not support ADD CONSTRAINT IF NOT EXISTS. The migration runner records its\n-- ledger row after executing this artifact, so a process failure between those operations must be\n-- safely replayable.\nSET @projection_publication_status_constraint_exists = (\n SELECT COUNT(*)\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'projection_set_publications'\n AND constraint_name = 'projection_set_publications_status_ck'\n);\nSET @projection_publication_status_constraint_ddl = IF(\n @projection_publication_status_constraint_exists = 0,\n 'ALTER TABLE `projection_set_publications` ADD CONSTRAINT `projection_set_publications_status_ck` CHECK (`status` IN (''candidate'', ''inactive'', ''published'', ''superseded'', ''validating''))',\n 'DO 0'\n);\nPREPARE projection_publication_status_constraint_statement\n FROM @projection_publication_status_constraint_ddl;\nEXECUTE projection_publication_status_constraint_statement;\nDEALLOCATE PREPARE projection_publication_status_constraint_statement;\n\nCREATE UNIQUE INDEX IF NOT EXISTS `index_projections_space_id_uq`\n ON `index_projections` (`knowledge_space_id`, `id`);\nCREATE INDEX IF NOT EXISTS `index_projections_fts_backfill_idx`\n ON `index_projections` (`knowledge_space_id`, `id`);\n\nCREATE TABLE IF NOT EXISTS `index_projection_fts_postings` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `projection_id` CHAR(36) NOT NULL,\n `tokenizer_version` VARCHAR(64) NOT NULL,\n `term_hash` CHAR(64) NOT NULL,\n `term` VARCHAR(128) NOT NULL,\n `term_frequency` INT NOT NULL,\n `document_token_count` INT NOT NULL,\n CONSTRAINT `index_projection_fts_postings_frequency_ck`\n CHECK (`term_frequency` > 0 AND `document_token_count` >= `term_frequency`),\n FOREIGN KEY (`knowledge_space_id`) REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE,\n FOREIGN KEY (`knowledge_space_id`, `projection_id`)\n REFERENCES `index_projections` (`knowledge_space_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `index_projection_fts_postings_projection_term_uq`\n ON `index_projection_fts_postings` (`projection_id`, `tokenizer_version`, `term_hash`);\nCREATE INDEX IF NOT EXISTS `index_projection_fts_postings_lookup_idx`\n ON `index_projection_fts_postings` (`knowledge_space_id`, `term_hash`, `projection_id`);\n\n-- Do not run an unbounded recursive backfill inside the migration transaction. The API runtime\n-- discovers old spaces and advances this fenced cursor one immutable projection at a time after\n-- the transactional dual writer is live.\nCREATE TABLE IF NOT EXISTS `tidb_fts_posting_backfills` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `tokenizer_version` VARCHAR(64) NOT NULL,\n `run_state` VARCHAR(16) NOT NULL,\n `cursor_projection_id` CHAR(36),\n `scanned_projections` INT NOT NULL,\n `written_postings` INT NOT NULL,\n `worker_id` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `heartbeat_at` DATETIME(3),\n `retry_count` INT NOT NULL,\n `row_version` INT NOT NULL,\n `last_error_code` VARCHAR(64),\n `last_error_message` TEXT,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `completed_at` DATETIME(3),\n CONSTRAINT `tidb_fts_posting_backfills_state_ck`\n CHECK (`run_state` IN ('queued', 'running', 'succeeded', 'failed')),\n CONSTRAINT `tidb_fts_posting_backfills_counts_ck`\n CHECK (\n `scanned_projections` >= 0\n AND `written_postings` >= 0\n AND `retry_count` >= 0\n AND `row_version` >= 0\n ),\n CONSTRAINT `tidb_fts_posting_backfills_lease_ck`\n CHECK (\n (\n `run_state` = 'running'\n AND `worker_id` IS NOT NULL\n AND `lease_token` IS NOT NULL\n AND `lease_expires_at` IS NOT NULL\n AND `heartbeat_at` IS NOT NULL\n AND `completed_at` IS NULL\n )\n OR (\n `run_state` <> 'running'\n AND `worker_id` IS NULL\n AND `lease_token` IS NULL\n AND `lease_expires_at` IS NULL\n AND `heartbeat_at` IS NULL\n )\n ),\n CONSTRAINT `tidb_fts_posting_backfills_terminal_ck`\n CHECK (\n (`run_state` IN ('succeeded', 'failed') AND `completed_at` IS NOT NULL)\n OR (`run_state` IN ('queued', 'running') AND `completed_at` IS NULL)\n ),\n CONSTRAINT `tidb_fts_posting_backfills_lease_token_ck`\n CHECK (\n `lease_token` IS NULL\n OR `lease_token` <> '00000000-0000-0000-0000-000000000000'\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `tidb_fts_posting_backfills_space_tokenizer_uq`\n ON `tidb_fts_posting_backfills` (\n `tenant_id`, `knowledge_space_id`, `tokenizer_version`\n );\nCREATE INDEX IF NOT EXISTS `tidb_fts_posting_backfills_claim_idx`\n ON `tidb_fts_posting_backfills` (\n `run_state`, `lease_expires_at`, `updated_at`, `id`\n );\nCREATE INDEX IF NOT EXISTS `tidb_fts_posting_backfills_scope_idx`\n ON `tidb_fts_posting_backfills` (\n `tenant_id`, `knowledge_space_id`, `tokenizer_version`, `id`\n );\n", path: "packages/database/migrations/0011_tidb_fts_postings.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0012_tidb_baseline_repair\n-- Dialect: postgres\n\n-- This forward repair exists because the pre-release TiDB baseline migrations were corrected in\n-- place before their first supported production release. PostgreSQL never emitted the TiDB-only\n-- TEXT-key, expression-index, FULLTEXT, or CHECK/foreign-key combinations being repaired. Keep a\n-- paired, immutable artifact so both dialects advance through the same migration id.\nSELECT 1 WHERE FALSE;\n", path: "packages/database/migrations/0012_tidb_baseline_repair.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0012_tidb_baseline_repair\n-- Dialect: tidb\n\n-- The pre-release TiDB artifacts 0001, 0002, 0004, 0006, and 0007 originally contained key TEXT\n-- columns, unsupported JSON/expression/FULLTEXT indexes, and CHECK constraints that TiDB cannot\n-- combine with foreign-key referential actions. Their checked-in clean-install definitions were\n-- corrected before the first supported production release. This forward migration gives any\n-- environment that nevertheless recorded those historical ids the same final schema.\n--\n-- There is deliberately no DELETE, truncation, or duplicate merge here. VARCHAR narrowing aborts\n-- on an overlong value, repair guard indexes abort on conflicting logical identities, and repaired\n-- foreign keys abort on orphaned data. Operators must reconcile incompatible data explicitly and\n-- rerun the migration; a failed run is never recorded in schema_migrations.\n\n-- TiDB rejects CHECK constraints on columns participating in a foreign-key referential action.\n-- Older artifacts could only retain these checks when the corresponding foreign key was ignored or\n-- invalid. Drop just those known incompatible checks, using information_schema so the repair is\n-- safe to rerun and also remains a no-op on a corrected clean install.\nSET @kfs_baseline_repair_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_document_version_ck'\n ),\n 'ALTER TABLE `document_compilation_attempts` DROP CONSTRAINT `document_compilation_attempts_document_version_ck`',\n 'DO 0'\n);\nPREPARE kfs_baseline_repair_stmt FROM @kfs_baseline_repair_sql;\nEXECUTE kfs_baseline_repair_stmt;\nDEALLOCATE PREPARE kfs_baseline_repair_stmt;\n\nSET @kfs_baseline_repair_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_candidate_pair_ck'\n ),\n 'ALTER TABLE `document_compilation_attempts` DROP CONSTRAINT `document_compilation_attempts_candidate_pair_ck`',\n 'DO 0'\n);\nPREPARE kfs_baseline_repair_stmt FROM @kfs_baseline_repair_sql;\nEXECUTE kfs_baseline_repair_stmt;\nDEALLOCATE PREPARE kfs_baseline_repair_stmt;\n\nSET @kfs_baseline_repair_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_candidate_checkpoint_ck'\n ),\n 'ALTER TABLE `document_compilation_attempts` DROP CONSTRAINT `document_compilation_attempts_candidate_checkpoint_ck`',\n 'DO 0'\n);\nPREPARE kfs_baseline_repair_stmt FROM @kfs_baseline_repair_sql;\nEXECUTE kfs_baseline_repair_stmt;\nDEALLOCATE PREPARE kfs_baseline_repair_stmt;\n\n-- Every column below either participates in a TiDB key/foreign key or has a bounded application\n-- invariant. TiDB fails ALTER ... MODIFY rather than truncating an incompatible existing value.\nALTER TABLE `knowledge_spaces`\n MODIFY COLUMN `tenant_id` VARCHAR(255) NOT NULL,\n MODIFY COLUMN `slug` VARCHAR(160) NOT NULL;\nALTER TABLE `knowledge_space_manifests`\n MODIFY COLUMN `tenant_id` VARCHAR(255) NOT NULL;\nALTER TABLE `sources`\n MODIFY COLUMN `status` VARCHAR(16) NOT NULL;\nALTER TABLE `resource_mounts`\n MODIFY COLUMN `tenant_id` VARCHAR(255) NOT NULL,\n MODIFY COLUMN `mount_path` VARCHAR(384) NOT NULL,\n MODIFY COLUMN `resource_type` VARCHAR(64) NOT NULL;\nALTER TABLE `document_assets`\n MODIFY COLUMN `parser_status` VARCHAR(16) NOT NULL;\nALTER TABLE `parse_artifacts`\n MODIFY COLUMN `artifact_hash` VARCHAR(64) NOT NULL;\nALTER TABLE `artifact_segments`\n MODIFY COLUMN `checksum` VARCHAR(64) NOT NULL;\nALTER TABLE `knowledge_space_staged_commits`\n MODIFY COLUMN `tenant_id` VARCHAR(255) NOT NULL,\n MODIFY COLUMN `idempotency_key` VARCHAR(255) NOT NULL,\n MODIFY COLUMN `status` VARCHAR(32) NOT NULL;\nALTER TABLE `knowledge_fs_sessions`\n MODIFY COLUMN `tenant_id` VARCHAR(255) NOT NULL;\nALTER TABLE `knowledge_fs_leases`\n MODIFY COLUMN `tenant_id` VARCHAR(255) NOT NULL,\n MODIFY COLUMN `virtual_path` VARCHAR(384) NOT NULL,\n MODIFY COLUMN `status` VARCHAR(16) NOT NULL;\nALTER TABLE `knowledge_nodes`\n MODIFY COLUMN `kind` VARCHAR(16) NOT NULL;\nALTER TABLE `index_projections`\n MODIFY COLUMN `type` VARCHAR(32) NOT NULL,\n MODIFY COLUMN `status` VARCHAR(16) NOT NULL,\n MODIFY COLUMN `fts_document` TEXT;\nALTER TABLE `embedding_models`\n MODIFY COLUMN `provider` VARCHAR(64) NOT NULL,\n MODIFY COLUMN `model_id` VARCHAR(255) NOT NULL,\n MODIFY COLUMN `version` VARCHAR(128) NOT NULL,\n MODIFY COLUMN `status` VARCHAR(16) NOT NULL;\nALTER TABLE `knowledge_paths`\n MODIFY COLUMN `virtual_path` VARCHAR(384) NOT NULL,\n MODIFY COLUMN `resource_type` VARCHAR(64) NOT NULL,\n MODIFY COLUMN `target_id` VARCHAR(512) NOT NULL,\n MODIFY COLUMN `view_type` VARCHAR(16) NOT NULL,\n MODIFY COLUMN `view_name` VARCHAR(64) NOT NULL;\nALTER TABLE `evidence_bundles`\n MODIFY COLUMN `state` VARCHAR(16) NOT NULL;\nALTER TABLE `answer_trace_steps`\n MODIFY COLUMN `name` VARCHAR(64) NOT NULL,\n MODIFY COLUMN `status` VARCHAR(16) NOT NULL;\nALTER TABLE `graph_entities`\n MODIFY COLUMN `canonical_key` VARCHAR(512) NOT NULL,\n MODIFY COLUMN `type` VARCHAR(64) NOT NULL,\n MODIFY COLUMN `name` VARCHAR(255) NOT NULL;\nALTER TABLE `graph_relations`\n MODIFY COLUMN `type` VARCHAR(64) NOT NULL;\n\n-- A clean install already has model_key, and TiDB correctly prevents changing a base column with a\n-- generated-column dependency. Historical schemas do not have model_key, so narrow model before\n-- adding it. A partially repaired schema with model_key but the wrong model type deliberately takes\n-- the ALTER branch and fails closed instead of silently accepting a mismatched generated column.\nSET @kfs_baseline_repair_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.columns\n WHERE table_schema = DATABASE()\n AND table_name = 'index_projections'\n AND column_name = 'model'\n AND data_type = 'varchar'\n AND character_maximum_length = 255\n ),\n 'DO 0',\n 'ALTER TABLE `index_projections` MODIFY COLUMN `model` VARCHAR(255)'\n);\nPREPARE kfs_baseline_repair_stmt FROM @kfs_baseline_repair_sql;\nEXECUTE kfs_baseline_repair_stmt;\nDEALLOCATE PREPARE kfs_baseline_repair_stmt;\n\n-- Explicit virtual columns replace disabled-by-default TiDB expression indexes. ADD repairs the\n-- historical schema; MODIFY also verifies the exact type/expression when this is a clean install\n-- or a retry after a partially completed repair.\nALTER TABLE `index_projections`\n ADD COLUMN IF NOT EXISTS `model_key` VARCHAR(255)\n GENERATED ALWAYS AS (COALESCE(`model`, '')) VIRTUAL,\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `index_projections`\n MODIFY COLUMN `model_key` VARCHAR(255)\n GENERATED ALWAYS AS (COALESCE(`model`, '')) VIRTUAL,\n MODIFY COLUMN `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `document_multimodal_manifests`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `document_multimodal_manifests`\n MODIFY COLUMN `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `knowledge_nodes`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `knowledge_nodes`\n MODIFY COLUMN `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `knowledge_paths`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `knowledge_paths`\n MODIFY COLUMN `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `graph_entities`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `graph_entities`\n MODIFY COLUMN `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `graph_relations`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `graph_relations`\n MODIFY COLUMN `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `document_outlines`\n ADD COLUMN IF NOT EXISTS `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\nALTER TABLE `document_outlines`\n MODIFY COLUMN `publication_generation_key` CHAR(36)\n GENERATED ALWAYS AS (\n COALESCE(`publication_generation_id`, '00000000-0000-0000-0000-000000000000')\n ) VIRTUAL;\n\n-- Replace unsupported historical JSON/FULLTEXT indexes if an experimental cluster managed to\n-- create them. TiDB FTS retrieval uses the bounded posting index introduced by migration 0011.\nDROP INDEX IF EXISTS `resource_mounts_permission_scope_idx` ON `resource_mounts`;\nDROP INDEX IF EXISTS `knowledge_nodes_permission_scope_idx` ON `knowledge_nodes`;\nDROP INDEX IF EXISTS `index_projections_fts_document_idx` ON `index_projections`;\nDROP INDEX IF EXISTS `graph_entities_permission_scope_idx` ON `graph_entities`;\nDROP INDEX IF EXISTS `graph_relations_permission_scope_idx` ON `graph_relations`;\n\n-- TiDB can retain the pre-0004 projection identity index under an empty internal name when a\n-- same-name DROP/CREATE replaces it in one migration artifact. That shadow key omits generation\n-- and would incorrectly reject the same node/model identity in a later immutable generation.\nDROP INDEX IF EXISTS `` ON `index_projections`;\n\n-- Build an additional known-good foreign key before removing any legacy/invalidly named one. This\n-- validates every existing child row first, so the forward repair cannot silently orphan data.\nSET @kfs_baseline_repair_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_space_fk'\n ),\n 'DO 0',\n 'ALTER TABLE `document_compilation_attempts` ADD CONSTRAINT `document_compilation_attempts_space_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE'\n);\nPREPARE kfs_baseline_repair_stmt FROM @kfs_baseline_repair_sql;\nEXECUTE kfs_baseline_repair_stmt;\nDEALLOCATE PREPARE kfs_baseline_repair_stmt;\n\nSET @kfs_baseline_repair_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_asset_version_fk'\n ),\n 'DO 0',\n 'ALTER TABLE `document_compilation_attempts` ADD CONSTRAINT `document_compilation_attempts_asset_version_fk` FOREIGN KEY (`knowledge_space_id`, `document_asset_id`, `document_version`) REFERENCES `document_assets` (`knowledge_space_id`, `id`, `version`) ON DELETE CASCADE'\n);\nPREPARE kfs_baseline_repair_stmt FROM @kfs_baseline_repair_sql;\nEXECUTE kfs_baseline_repair_stmt;\nDEALLOCATE PREPARE kfs_baseline_repair_stmt;\n\nSET @kfs_baseline_repair_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_candidate_fk'\n ),\n 'DO 0',\n 'ALTER TABLE `document_compilation_attempts` ADD CONSTRAINT `document_compilation_attempts_candidate_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `candidate_publication_id`, `candidate_fingerprint`) REFERENCES `projection_set_publications` (`tenant_id`, `knowledge_space_id`, `id`, `fingerprint`) ON DELETE RESTRICT'\n);\nPREPARE kfs_baseline_repair_stmt FROM @kfs_baseline_repair_sql;\nEXECUTE kfs_baseline_repair_stmt;\nDEALLOCATE PREPARE kfs_baseline_repair_stmt;\n\nSET @kfs_baseline_repair_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_compilation_outbox'\n AND constraint_name = 'document_compilation_outbox_attempt_fk'\n ),\n 'DO 0',\n 'ALTER TABLE `document_compilation_outbox` ADD CONSTRAINT `document_compilation_outbox_attempt_fk` FOREIGN KEY (`attempt_id`) REFERENCES `document_compilation_attempts` (`id`) ON DELETE CASCADE'\n);\nPREPARE kfs_baseline_repair_stmt FROM @kfs_baseline_repair_sql;\nEXECUTE kfs_baseline_repair_stmt;\nDEALLOCATE PREPARE kfs_baseline_repair_stmt;\n\nSET @kfs_baseline_repair_attempt_fk_drops = (\n SELECT GROUP_CONCAT(\n CONCAT('DROP FOREIGN KEY `', REPLACE(constraint_name, '`', '``'), '`')\n ORDER BY constraint_name SEPARATOR ', '\n )\n FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_compilation_attempts'\n AND constraint_name NOT IN (\n 'document_compilation_attempts_space_fk',\n 'document_compilation_attempts_asset_version_fk',\n 'document_compilation_attempts_candidate_fk'\n )\n);\nSET @kfs_baseline_repair_sql = IF(\n @kfs_baseline_repair_attempt_fk_drops IS NULL,\n 'DO 0',\n CONCAT('ALTER TABLE `document_compilation_attempts` ', @kfs_baseline_repair_attempt_fk_drops)\n);\nPREPARE kfs_baseline_repair_stmt FROM @kfs_baseline_repair_sql;\nEXECUTE kfs_baseline_repair_stmt;\nDEALLOCATE PREPARE kfs_baseline_repair_stmt;\n\nSET @kfs_baseline_repair_outbox_fk_drops = (\n SELECT GROUP_CONCAT(\n CONCAT('DROP FOREIGN KEY `', REPLACE(constraint_name, '`', '``'), '`')\n ORDER BY constraint_name SEPARATOR ', '\n )\n FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_compilation_outbox'\n AND constraint_name <> 'document_compilation_outbox_attempt_fk'\n);\nSET @kfs_baseline_repair_sql = IF(\n @kfs_baseline_repair_outbox_fk_drops IS NULL,\n 'DO 0',\n CONCAT('ALTER TABLE `document_compilation_outbox` ', @kfs_baseline_repair_outbox_fk_drops)\n);\nPREPARE kfs_baseline_repair_stmt FROM @kfs_baseline_repair_sql;\nEXECUTE kfs_baseline_repair_stmt;\nDEALLOCATE PREPARE kfs_baseline_repair_stmt;\n\n-- Create guard indexes before replacing historical indexes with their generated-column forms.\n-- If incompatible duplicates exist, guard creation fails while the old unique index is untouched.\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_knowledge_spaces_slug_guard_uq`\n ON `knowledge_spaces` (`tenant_id`, `slug`);\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_manifests_space_guard_uq`\n ON `knowledge_space_manifests` (`tenant_id`, `knowledge_space_id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_mounts_path_guard_uq`\n ON `resource_mounts` (`knowledge_space_id`, `mount_path`);\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_staged_commits_guard_uq`\n ON `knowledge_space_staged_commits` (`tenant_id`, `knowledge_space_id`, `idempotency_key`);\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_multimodal_guard_uq`\n ON `document_multimodal_manifests` (\n `document_asset_id`, `version`, `publication_generation_key`\n );\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_nodes_identity_guard_uq`\n ON `knowledge_nodes` (\n `knowledge_space_id`, `parse_artifact_id`, `kind`, `start_offset`, `end_offset`,\n `publication_generation_key`\n );\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_projection_identity_guard_uq`\n ON `index_projections` (\n `node_id`, `type`, `projection_version`, `model_key`, `publication_generation_key`\n );\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_models_identity_guard_uq`\n ON `embedding_models` (`model_id`, `version`);\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_paths_identity_guard_uq`\n ON `knowledge_paths` (`knowledge_space_id`, `virtual_path`, `publication_generation_key`);\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_graph_entities_guard_uq`\n ON `graph_entities` (`knowledge_space_id`, `canonical_key`, `publication_generation_key`);\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_graph_relations_guard_uq`\n ON `graph_relations` (\n `knowledge_space_id`, `subject_entity_id`, `type`, `object_entity_id`,\n `extraction_version`, `publication_generation_key`\n );\nCREATE UNIQUE INDEX IF NOT EXISTS `kfs_repair_outlines_guard_uq`\n ON `document_outlines` (`document_asset_id`, `version`, `publication_generation_key`);\n\nDROP INDEX IF EXISTS `knowledge_spaces_tenant_slug_uq` ON `knowledge_spaces`;\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_spaces_tenant_slug_uq`\n ON `knowledge_spaces` (`tenant_id`, `slug`);\nDROP INDEX IF EXISTS `knowledge_space_manifests_tenant_space_uq`\n ON `knowledge_space_manifests`;\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_manifests_tenant_space_uq`\n ON `knowledge_space_manifests` (`tenant_id`, `knowledge_space_id`);\nDROP INDEX IF EXISTS `knowledge_space_manifests_tenant_space_idx`\n ON `knowledge_space_manifests`;\nCREATE INDEX IF NOT EXISTS `knowledge_space_manifests_tenant_space_idx`\n ON `knowledge_space_manifests` (`tenant_id`, `knowledge_space_id`, `id`);\nDROP INDEX IF EXISTS `sources_space_status_idx` ON `sources`;\nCREATE INDEX IF NOT EXISTS `sources_space_status_idx`\n ON `sources` (`knowledge_space_id`, `status`);\nDROP INDEX IF EXISTS `resource_mounts_space_path_uq` ON `resource_mounts`;\nCREATE UNIQUE INDEX IF NOT EXISTS `resource_mounts_space_path_uq`\n ON `resource_mounts` (`knowledge_space_id`, `mount_path`);\nDROP INDEX IF EXISTS `resource_mounts_space_type_path_idx` ON `resource_mounts`;\nCREATE INDEX IF NOT EXISTS `resource_mounts_space_type_path_idx`\n ON `resource_mounts` (`knowledge_space_id`, `resource_type`, `mount_path`, `id`);\nDROP INDEX IF EXISTS `document_assets_space_status_created_idx` ON `document_assets`;\nCREATE INDEX IF NOT EXISTS `document_assets_space_status_created_idx`\n ON `document_assets` (`knowledge_space_id`, `parser_status`, `created_at`, `id`);\nDROP INDEX IF EXISTS `parse_artifacts_hash_idx` ON `parse_artifacts`;\nCREATE INDEX IF NOT EXISTS `parse_artifacts_hash_idx`\n ON `parse_artifacts` (`artifact_hash`);\nDROP INDEX IF EXISTS `artifact_segments_space_checksum_idx` ON `artifact_segments`;\nCREATE INDEX IF NOT EXISTS `artifact_segments_space_checksum_idx`\n ON `artifact_segments` (`knowledge_space_id`, `checksum`, `id`);\nDROP INDEX IF EXISTS `knowledge_space_staged_commits_idempotency_uq`\n ON `knowledge_space_staged_commits`;\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_staged_commits_idempotency_uq`\n ON `knowledge_space_staged_commits` (`tenant_id`, `knowledge_space_id`, `idempotency_key`);\nDROP INDEX IF EXISTS `knowledge_space_staged_commits_status_updated_idx`\n ON `knowledge_space_staged_commits`;\nCREATE INDEX IF NOT EXISTS `knowledge_space_staged_commits_status_updated_idx`\n ON `knowledge_space_staged_commits` (\n `tenant_id`, `knowledge_space_id`, `status`, `updated_at`, `id`\n );\nDROP INDEX IF EXISTS `knowledge_space_staged_commits_expiry_idx`\n ON `knowledge_space_staged_commits`;\nCREATE INDEX IF NOT EXISTS `knowledge_space_staged_commits_expiry_idx`\n ON `knowledge_space_staged_commits` (\n `tenant_id`, `knowledge_space_id`, `expires_at`, `id`\n );\nDROP INDEX IF EXISTS `knowledge_fs_sessions_space_expiry_idx` ON `knowledge_fs_sessions`;\nCREATE INDEX IF NOT EXISTS `knowledge_fs_sessions_space_expiry_idx`\n ON `knowledge_fs_sessions` (`tenant_id`, `knowledge_space_id`, `expires_at`, `id`);\nDROP INDEX IF EXISTS `knowledge_fs_sessions_expiry_idx` ON `knowledge_fs_sessions`;\nCREATE INDEX IF NOT EXISTS `knowledge_fs_sessions_expiry_idx`\n ON `knowledge_fs_sessions` (`tenant_id`, `expires_at`, `id`);\nDROP INDEX IF EXISTS `knowledge_fs_leases_active_path_idx` ON `knowledge_fs_leases`;\nCREATE INDEX IF NOT EXISTS `knowledge_fs_leases_active_path_idx`\n ON `knowledge_fs_leases` (\n `tenant_id`, `knowledge_space_id`, `status`, `virtual_path`, `expires_at`, `id`\n );\nDROP INDEX IF EXISTS `knowledge_fs_leases_expiry_idx` ON `knowledge_fs_leases`;\nCREATE INDEX IF NOT EXISTS `knowledge_fs_leases_expiry_idx`\n ON `knowledge_fs_leases` (`tenant_id`, `expires_at`, `id`);\nDROP INDEX IF EXISTS `knowledge_fs_leases_session_idx` ON `knowledge_fs_leases`;\nCREATE INDEX IF NOT EXISTS `knowledge_fs_leases_session_idx`\n ON `knowledge_fs_leases` (`tenant_id`, `session_id`, `status`, `id`);\n\nDROP INDEX IF EXISTS `document_multimodal_manifests_asset_version_uq`\n ON `document_multimodal_manifests`;\nCREATE UNIQUE INDEX IF NOT EXISTS `document_multimodal_manifests_asset_version_uq`\n ON `document_multimodal_manifests` (\n `document_asset_id`, `version`, `publication_generation_key`\n );\nDROP INDEX IF EXISTS `knowledge_nodes_space_asset_kind_idx` ON `knowledge_nodes`;\nCREATE INDEX IF NOT EXISTS `knowledge_nodes_space_asset_kind_idx`\n ON `knowledge_nodes` (\n `knowledge_space_id`, `publication_generation_id`, `document_asset_id`, `kind`, `id`\n );\nDROP INDEX IF EXISTS `knowledge_nodes_artifact_offset_idx` ON `knowledge_nodes`;\nCREATE INDEX IF NOT EXISTS `knowledge_nodes_artifact_offset_idx`\n ON `knowledge_nodes` (\n `knowledge_space_id`, `parse_artifact_id`, `publication_generation_id`, `start_offset`, `id`\n );\nDROP INDEX IF EXISTS `knowledge_nodes_artifact_kind_offsets_uq` ON `knowledge_nodes`;\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_nodes_artifact_kind_offsets_uq`\n ON `knowledge_nodes` (\n `knowledge_space_id`, `parse_artifact_id`, `kind`, `start_offset`, `end_offset`,\n `publication_generation_key`\n );\nDROP INDEX IF EXISTS `index_projections_space_type_status_idx` ON `index_projections`;\nCREATE INDEX IF NOT EXISTS `index_projections_space_type_status_idx`\n ON `index_projections` (\n `knowledge_space_id`, `publication_generation_id`, `type`, `status`, `node_id`, `id`\n );\nDROP INDEX IF EXISTS `index_projections_node_type_version_idx` ON `index_projections`;\nCREATE INDEX IF NOT EXISTS `index_projections_node_type_version_idx`\n ON `index_projections` (`node_id`, `type`, `projection_version`);\nDROP INDEX IF EXISTS `index_projections_fts_backfill_idx` ON `index_projections`;\nCREATE INDEX IF NOT EXISTS `index_projections_fts_backfill_idx`\n ON `index_projections` (`knowledge_space_id`, `type`, `id`);\nDROP INDEX IF EXISTS `index_projections_node_type_version_model_uq` ON `index_projections`;\nCREATE UNIQUE INDEX IF NOT EXISTS `index_projections_node_type_version_model_uq`\n ON `index_projections` (\n `node_id`, `type`, `projection_version`, `model_key`, `publication_generation_key`\n );\nDROP INDEX IF EXISTS `embedding_models_model_version_uq` ON `embedding_models`;\nCREATE UNIQUE INDEX IF NOT EXISTS `embedding_models_model_version_uq`\n ON `embedding_models` (`model_id`, `version`);\nDROP INDEX IF EXISTS `embedding_models_status_provider_idx` ON `embedding_models`;\nCREATE INDEX IF NOT EXISTS `embedding_models_status_provider_idx`\n ON `embedding_models` (`status`, `provider`, `model_id`, `id`);\nDROP INDEX IF EXISTS `embedding_models_status_model_idx` ON `embedding_models`;\nCREATE INDEX IF NOT EXISTS `embedding_models_status_model_idx`\n ON `embedding_models` (`status`, `model_id`, `id`);\nDROP INDEX IF EXISTS `knowledge_paths_space_path_uq` ON `knowledge_paths`;\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_paths_space_path_uq`\n ON `knowledge_paths` (`knowledge_space_id`, `virtual_path`, `publication_generation_key`);\nDROP INDEX IF EXISTS `knowledge_paths_target_idx` ON `knowledge_paths`;\nCREATE INDEX IF NOT EXISTS `knowledge_paths_target_idx`\n ON `knowledge_paths` (`resource_type`, `target_id`);\nDROP INDEX IF EXISTS `knowledge_paths_space_view_path_idx` ON `knowledge_paths`;\nCREATE INDEX IF NOT EXISTS `knowledge_paths_space_view_path_idx`\n ON `knowledge_paths` (\n `knowledge_space_id`, `publication_generation_id`, `view_type`, `view_name`, `virtual_path`, `id`\n );\nDROP INDEX IF EXISTS `evidence_bundles_state_created_idx` ON `evidence_bundles`;\nCREATE INDEX IF NOT EXISTS `evidence_bundles_state_created_idx`\n ON `evidence_bundles` (`state`, `created_at`, `id`);\nDROP INDEX IF EXISTS `graph_entities_space_key_uq` ON `graph_entities`;\nCREATE UNIQUE INDEX IF NOT EXISTS `graph_entities_space_key_uq`\n ON `graph_entities` (`knowledge_space_id`, `canonical_key`, `publication_generation_key`);\nDROP INDEX IF EXISTS `graph_entities_space_type_name_idx` ON `graph_entities`;\nCREATE INDEX IF NOT EXISTS `graph_entities_space_type_name_idx`\n ON `graph_entities` (\n `knowledge_space_id`, `publication_generation_id`, `type`, `name`, `id`\n );\nDROP INDEX IF EXISTS `graph_relations_subject_traversal_idx` ON `graph_relations`;\nCREATE INDEX IF NOT EXISTS `graph_relations_subject_traversal_idx`\n ON `graph_relations` (\n `knowledge_space_id`, `publication_generation_id`, `subject_entity_id`, `type`,\n `object_entity_id`, `id`\n );\nDROP INDEX IF EXISTS `graph_relations_object_traversal_idx` ON `graph_relations`;\nCREATE INDEX IF NOT EXISTS `graph_relations_object_traversal_idx`\n ON `graph_relations` (\n `knowledge_space_id`, `publication_generation_id`, `object_entity_id`, `type`,\n `subject_entity_id`, `id`\n );\nDROP INDEX IF EXISTS `graph_relations_space_edge_version_uq` ON `graph_relations`;\nCREATE UNIQUE INDEX IF NOT EXISTS `graph_relations_space_edge_version_uq`\n ON `graph_relations` (\n `knowledge_space_id`, `subject_entity_id`, `type`, `object_entity_id`,\n `extraction_version`, `publication_generation_key`\n );\nDROP INDEX IF EXISTS `document_outlines_asset_version_uq` ON `document_outlines`;\nCREATE UNIQUE INDEX IF NOT EXISTS `document_outlines_asset_version_uq`\n ON `document_outlines` (`document_asset_id`, `version`, `publication_generation_key`);\n\nDROP INDEX IF EXISTS `kfs_repair_knowledge_spaces_slug_guard_uq` ON `knowledge_spaces`;\nDROP INDEX IF EXISTS `kfs_repair_manifests_space_guard_uq` ON `knowledge_space_manifests`;\nDROP INDEX IF EXISTS `kfs_repair_mounts_path_guard_uq` ON `resource_mounts`;\nDROP INDEX IF EXISTS `kfs_repair_staged_commits_guard_uq` ON `knowledge_space_staged_commits`;\nDROP INDEX IF EXISTS `kfs_repair_multimodal_guard_uq` ON `document_multimodal_manifests`;\nDROP INDEX IF EXISTS `kfs_repair_nodes_identity_guard_uq` ON `knowledge_nodes`;\nDROP INDEX IF EXISTS `kfs_repair_projection_identity_guard_uq` ON `index_projections`;\nDROP INDEX IF EXISTS `kfs_repair_models_identity_guard_uq` ON `embedding_models`;\nDROP INDEX IF EXISTS `kfs_repair_paths_identity_guard_uq` ON `knowledge_paths`;\nDROP INDEX IF EXISTS `kfs_repair_graph_entities_guard_uq` ON `graph_entities`;\nDROP INDEX IF EXISTS `kfs_repair_graph_relations_guard_uq` ON `graph_relations`;\nDROP INDEX IF EXISTS `kfs_repair_outlines_guard_uq` ON `document_outlines`;\n", path: "packages/database/migrations/0012_tidb_baseline_repair.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0013_space_access_control\n-- Dialect: postgres\n\nCREATE TABLE IF NOT EXISTS \"knowledge_space_members\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"subject_id\" VARCHAR(255) NOT NULL,\n \"role\" VARCHAR(16) NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"created_by_subject_id\" VARCHAR(255) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"knowledge_space_members_role_ck\"\n CHECK (\"role\" IN ('owner', 'editor', 'viewer')),\n CONSTRAINT \"knowledge_space_members_revision_ck\" CHECK (\"revision\" >= 1),\n CONSTRAINT \"knowledge_space_members_space_fk\" FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_members_scope_subject_uq\"\n ON \"knowledge_space_members\" (\"tenant_id\", \"knowledge_space_id\", \"subject_id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_space_members_scope_role_idx\"\n ON \"knowledge_space_members\" (\n \"tenant_id\", \"knowledge_space_id\", \"role\", \"subject_id\", \"id\"\n );\n\nCREATE TABLE IF NOT EXISTS \"knowledge_space_access_policies\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"visibility\" VARCHAR(24) NOT NULL,\n \"owner_subject_id\" VARCHAR(255) NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"updated_by_subject_id\" VARCHAR(255) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"knowledge_space_access_policies_visibility_ck\"\n CHECK (\"visibility\" IN ('only_me', 'all_members', 'partial_members')),\n CONSTRAINT \"knowledge_space_access_policies_revision_ck\" CHECK (\"revision\" >= 1),\n CONSTRAINT \"knowledge_space_access_policies_space_fk\" FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"knowledge_space_access_policies_owner_fk\" FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"owner_subject_id\")\n REFERENCES \"knowledge_space_members\" (\"tenant_id\", \"knowledge_space_id\", \"subject_id\")\n ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_access_policies_scope_uq\"\n ON \"knowledge_space_access_policies\" (\"tenant_id\", \"knowledge_space_id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_access_policies_scope_id_uq\"\n ON \"knowledge_space_access_policies\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"knowledge_space_access_policy_members\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"access_policy_id\" UUID NOT NULL,\n \"subject_id\" VARCHAR(255) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"knowledge_space_access_policy_members_policy_fk\" FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"access_policy_id\")\n REFERENCES \"knowledge_space_access_policies\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE CASCADE,\n CONSTRAINT \"knowledge_space_access_policy_members_member_fk\" FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"subject_id\")\n REFERENCES \"knowledge_space_members\" (\"tenant_id\", \"knowledge_space_id\", \"subject_id\")\n ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_access_policy_members_policy_subject_uq\"\n ON \"knowledge_space_access_policy_members\" (\"access_policy_id\", \"subject_id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_space_access_policy_members_scope_subject_idx\"\n ON \"knowledge_space_access_policy_members\" (\n \"tenant_id\", \"knowledge_space_id\", \"subject_id\", \"access_policy_id\"\n );\n\nCREATE TABLE IF NOT EXISTS \"knowledge_space_api_access\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"enabled\" BOOLEAN NOT NULL,\n \"disabled_at\" TIMESTAMPTZ,\n \"revision\" INTEGER NOT NULL,\n \"updated_by_subject_id\" VARCHAR(255) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"knowledge_space_api_access_revision_ck\" CHECK (\"revision\" >= 1),\n CONSTRAINT \"knowledge_space_api_access_disabled_ck\" CHECK (\n (\"enabled\" AND \"disabled_at\" IS NULL)\n OR (NOT \"enabled\" AND \"disabled_at\" IS NOT NULL)\n ),\n CONSTRAINT \"knowledge_space_api_access_space_fk\" FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_api_access_scope_uq\"\n ON \"knowledge_space_api_access\" (\"tenant_id\", \"knowledge_space_id\");\n\nCREATE TABLE IF NOT EXISTS \"knowledge_space_api_keys\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"name\" VARCHAR(160) NOT NULL,\n \"key_prefix\" VARCHAR(24) NOT NULL,\n \"key_hash\" VARCHAR(64) NOT NULL,\n \"principal_subject_id\" VARCHAR(255) NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"created_by_subject_id\" VARCHAR(255) NOT NULL,\n \"last_used_at\" TIMESTAMPTZ,\n \"expires_at\" TIMESTAMPTZ,\n \"revoked_at\" TIMESTAMPTZ,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"knowledge_space_api_keys_status_ck\"\n CHECK (\"status\" IN ('active', 'revoked')),\n CONSTRAINT \"knowledge_space_api_keys_revision_ck\" CHECK (\"revision\" >= 1),\n CONSTRAINT \"knowledge_space_api_keys_revocation_ck\" CHECK (\n (\"status\" = 'active' AND \"revoked_at\" IS NULL)\n OR (\"status\" = 'revoked' AND \"revoked_at\" IS NOT NULL)\n ),\n CONSTRAINT \"knowledge_space_api_keys_space_fk\" FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"knowledge_space_api_keys_principal_fk\" FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"principal_subject_id\")\n REFERENCES \"knowledge_space_members\" (\"tenant_id\", \"knowledge_space_id\", \"subject_id\")\n ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_api_keys_hash_uq\"\n ON \"knowledge_space_api_keys\" (\"key_hash\");\nCREATE INDEX IF NOT EXISTS \"knowledge_space_api_keys_scope_status_idx\"\n ON \"knowledge_space_api_keys\" (\n \"tenant_id\", \"knowledge_space_id\", \"status\", \"created_at\", \"id\"\n );\nCREATE INDEX IF NOT EXISTS \"knowledge_space_api_keys_scope_created_idx\"\n ON \"knowledge_space_api_keys\" (\n \"tenant_id\", \"knowledge_space_id\", \"created_at\", \"id\"\n );\n\nCREATE TABLE IF NOT EXISTS \"knowledge_space_permission_snapshots\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"subject_id\" VARCHAR(255) NOT NULL,\n \"role\" VARCHAR(16) NOT NULL,\n \"visibility\" VARCHAR(24) NOT NULL,\n \"access_channel\" VARCHAR(16) NOT NULL,\n \"member_revision\" INTEGER NOT NULL,\n \"access_policy_revision\" INTEGER NOT NULL,\n \"api_access_revision\" INTEGER NOT NULL,\n \"permission_scopes\" JSONB NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"expires_at\" TIMESTAMPTZ NOT NULL,\n \"revoked_at\" TIMESTAMPTZ,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"knowledge_space_permission_snapshots_role_ck\"\n CHECK (\"role\" IN ('owner', 'editor', 'viewer')),\n CONSTRAINT \"knowledge_space_permission_snapshots_visibility_ck\"\n CHECK (\"visibility\" IN ('only_me', 'all_members', 'partial_members')),\n CONSTRAINT \"knowledge_space_permission_snapshots_channel_ck\"\n CHECK (\"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')),\n CONSTRAINT \"knowledge_space_permission_snapshots_status_ck\"\n CHECK (\"status\" IN ('active', 'revoked', 'expired')),\n CONSTRAINT \"knowledge_space_permission_snapshots_revisions_ck\" CHECK (\n \"revision\" >= 1\n AND \"member_revision\" >= 1\n AND \"access_policy_revision\" >= 1\n AND \"api_access_revision\" >= 1\n ),\n CONSTRAINT \"knowledge_space_permission_snapshots_revocation_ck\" CHECK (\n (\"status\" = 'revoked' AND \"revoked_at\" IS NOT NULL)\n OR (\"status\" <> 'revoked' AND \"revoked_at\" IS NULL)\n ),\n CONSTRAINT \"knowledge_space_permission_snapshots_space_fk\" FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS \"knowledge_space_permission_snapshots_scope_subject_idx\"\n ON \"knowledge_space_permission_snapshots\" (\n \"tenant_id\", \"knowledge_space_id\", \"subject_id\", \"status\", \"expires_at\", \"id\"\n );\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_permission_snapshots_scope_id_uq\"\n ON \"knowledge_space_permission_snapshots\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\n", path: "packages/database/migrations/0013_space_access_control.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0013_space_access_control\n-- Dialect: tidb\n\n-- Skip all existing ACL table DDL on crash replay. TiDB revalidates inbound as well as\n-- outbound foreign keys for CREATE TABLE IF NOT EXISTS and can otherwise reject a valid schema.\nSET @acl_members_exists = (\n SELECT COUNT(*) FROM information_schema.tables\n WHERE table_schema = DATABASE() AND table_name = 'knowledge_space_members'\n);\nSET @acl_members_ddl = IF(\n @acl_members_exists = 0,\n 'CREATE TABLE IF NOT EXISTS `knowledge_space_members` ( `id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `subject_id` VARCHAR(255) NOT NULL, `role` VARCHAR(16) NOT NULL, `revision` INT NOT NULL, `created_by_subject_id` VARCHAR(255) NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, CONSTRAINT `knowledge_space_members_role_ck` CHECK (`role` IN (''owner'', ''editor'', ''viewer'')), CONSTRAINT `knowledge_space_members_revision_ck` CHECK (`revision` >= 1), CONSTRAINT `knowledge_space_members_space_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE );',\n 'DO 0'\n);\nPREPARE acl_members_statement FROM @acl_members_ddl;\nEXECUTE acl_members_statement;\nDEALLOCATE PREPARE acl_members_statement;\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_members_scope_subject_uq`\n ON `knowledge_space_members` (`tenant_id`, `knowledge_space_id`, `subject_id`);\nCREATE INDEX IF NOT EXISTS `knowledge_space_members_scope_role_idx`\n ON `knowledge_space_members` (\n `tenant_id`, `knowledge_space_id`, `role`, `subject_id`, `id`\n );\n\n-- TiDB validates foreign keys even for an existing CREATE TABLE IF NOT EXISTS and can reject a\n-- crash replay after the referenced composite index was created separately. Skip the table DDL\n-- entirely once it exists; the migration ledger still makes the normal path execute it exactly once.\nSET @acl_access_policies_exists = (\n SELECT COUNT(*) FROM information_schema.tables\n WHERE table_schema = DATABASE() AND table_name = 'knowledge_space_access_policies'\n);\nSET @acl_access_policies_ddl = IF(\n @acl_access_policies_exists = 0,\n 'CREATE TABLE IF NOT EXISTS `knowledge_space_access_policies` ( `id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `visibility` VARCHAR(24) NOT NULL, `owner_subject_id` VARCHAR(255) NOT NULL, `revision` INT NOT NULL, `updated_by_subject_id` VARCHAR(255) NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, CONSTRAINT `knowledge_space_access_policies_visibility_ck` CHECK (`visibility` IN (''only_me'', ''all_members'', ''partial_members'')), CONSTRAINT `knowledge_space_access_policies_revision_ck` CHECK (`revision` >= 1), CONSTRAINT `knowledge_space_access_policies_space_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE, CONSTRAINT `knowledge_space_access_policies_owner_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `owner_subject_id`) REFERENCES `knowledge_space_members` (`tenant_id`, `knowledge_space_id`, `subject_id`) ON DELETE RESTRICT );',\n 'DO 0'\n);\nPREPARE acl_access_policies_statement FROM @acl_access_policies_ddl;\nEXECUTE acl_access_policies_statement;\nDEALLOCATE PREPARE acl_access_policies_statement;\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_access_policies_scope_uq`\n ON `knowledge_space_access_policies` (`tenant_id`, `knowledge_space_id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_access_policies_scope_id_uq`\n ON `knowledge_space_access_policies` (`tenant_id`, `knowledge_space_id`, `id`);\n\n-- TiDB validates foreign keys even for an existing CREATE TABLE IF NOT EXISTS and can reject a\n-- crash replay after the referenced composite index was created separately. Skip the table DDL\n-- entirely once it exists; the migration ledger still makes the normal path execute it exactly once.\nSET @acl_access_policy_members_exists = (\n SELECT COUNT(*) FROM information_schema.tables\n WHERE table_schema = DATABASE() AND table_name = 'knowledge_space_access_policy_members'\n);\nSET @acl_access_policy_members_ddl = IF(\n @acl_access_policy_members_exists = 0,\n 'CREATE TABLE IF NOT EXISTS `knowledge_space_access_policy_members` ( `id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `access_policy_id` CHAR(36) NOT NULL, `subject_id` VARCHAR(255) NOT NULL, `created_at` DATETIME(3) NOT NULL, CONSTRAINT `knowledge_space_access_policy_members_policy_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `access_policy_id`) REFERENCES `knowledge_space_access_policies` (`tenant_id`, `knowledge_space_id`, `id`) ON DELETE CASCADE, CONSTRAINT `knowledge_space_access_policy_members_member_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `subject_id`) REFERENCES `knowledge_space_members` (`tenant_id`, `knowledge_space_id`, `subject_id`) ON DELETE CASCADE );',\n 'DO 0'\n);\nPREPARE acl_access_policy_members_statement FROM @acl_access_policy_members_ddl;\nEXECUTE acl_access_policy_members_statement;\nDEALLOCATE PREPARE acl_access_policy_members_statement;\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_access_policy_members_policy_subject_uq`\n ON `knowledge_space_access_policy_members` (`access_policy_id`, `subject_id`);\nCREATE INDEX IF NOT EXISTS `knowledge_space_access_policy_members_scope_subject_idx`\n ON `knowledge_space_access_policy_members` (\n `tenant_id`, `knowledge_space_id`, `subject_id`, `access_policy_id`\n );\n\n-- Skip all existing ACL table DDL on crash replay. TiDB revalidates inbound as well as\n-- outbound foreign keys for CREATE TABLE IF NOT EXISTS and can otherwise reject a valid schema.\nSET @acl_api_access_exists = (\n SELECT COUNT(*) FROM information_schema.tables\n WHERE table_schema = DATABASE() AND table_name = 'knowledge_space_api_access'\n);\nSET @acl_api_access_ddl = IF(\n @acl_api_access_exists = 0,\n 'CREATE TABLE IF NOT EXISTS `knowledge_space_api_access` ( `id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `enabled` BOOLEAN NOT NULL, `disabled_at` DATETIME(3), `revision` INT NOT NULL, `updated_by_subject_id` VARCHAR(255) NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, CONSTRAINT `knowledge_space_api_access_revision_ck` CHECK (`revision` >= 1), CONSTRAINT `knowledge_space_api_access_disabled_ck` CHECK ( (`enabled` AND `disabled_at` IS NULL) OR (NOT `enabled` AND `disabled_at` IS NOT NULL) ), CONSTRAINT `knowledge_space_api_access_space_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE );',\n 'DO 0'\n);\nPREPARE acl_api_access_statement FROM @acl_api_access_ddl;\nEXECUTE acl_api_access_statement;\nDEALLOCATE PREPARE acl_api_access_statement;\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_api_access_scope_uq`\n ON `knowledge_space_api_access` (`tenant_id`, `knowledge_space_id`);\n\n-- TiDB validates foreign keys even for an existing CREATE TABLE IF NOT EXISTS and can reject a\n-- crash replay after the referenced composite index was created separately. Skip the table DDL\n-- entirely once it exists; the migration ledger still makes the normal path execute it exactly once.\nSET @acl_api_keys_exists = (\n SELECT COUNT(*) FROM information_schema.tables\n WHERE table_schema = DATABASE() AND table_name = 'knowledge_space_api_keys'\n);\nSET @acl_api_keys_ddl = IF(\n @acl_api_keys_exists = 0,\n 'CREATE TABLE IF NOT EXISTS `knowledge_space_api_keys` ( `id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `name` VARCHAR(160) NOT NULL, `key_prefix` VARCHAR(24) NOT NULL, `key_hash` VARCHAR(64) NOT NULL, `principal_subject_id` VARCHAR(255) NOT NULL, `status` VARCHAR(16) NOT NULL, `revision` INT NOT NULL, `created_by_subject_id` VARCHAR(255) NOT NULL, `last_used_at` DATETIME(3), `expires_at` DATETIME(3), `revoked_at` DATETIME(3), `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, CONSTRAINT `knowledge_space_api_keys_status_ck` CHECK (`status` IN (''active'', ''revoked'')), CONSTRAINT `knowledge_space_api_keys_revision_ck` CHECK (`revision` >= 1), CONSTRAINT `knowledge_space_api_keys_revocation_ck` CHECK ( (`status` = ''active'' AND `revoked_at` IS NULL) OR (`status` = ''revoked'' AND `revoked_at` IS NOT NULL) ), CONSTRAINT `knowledge_space_api_keys_space_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE, CONSTRAINT `knowledge_space_api_keys_principal_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `principal_subject_id`) REFERENCES `knowledge_space_members` (`tenant_id`, `knowledge_space_id`, `subject_id`) ON DELETE CASCADE );',\n 'DO 0'\n);\nPREPARE acl_api_keys_statement FROM @acl_api_keys_ddl;\nEXECUTE acl_api_keys_statement;\nDEALLOCATE PREPARE acl_api_keys_statement;\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_api_keys_hash_uq`\n ON `knowledge_space_api_keys` (`key_hash`);\nCREATE INDEX IF NOT EXISTS `knowledge_space_api_keys_scope_status_idx`\n ON `knowledge_space_api_keys` (\n `tenant_id`, `knowledge_space_id`, `status`, `created_at`, `id`\n );\nCREATE INDEX IF NOT EXISTS `knowledge_space_api_keys_scope_created_idx`\n ON `knowledge_space_api_keys` (\n `tenant_id`, `knowledge_space_id`, `created_at`, `id`\n );\n\n-- Skip all existing ACL table DDL on crash replay. TiDB revalidates inbound as well as\n-- outbound foreign keys for CREATE TABLE IF NOT EXISTS and can otherwise reject a valid schema.\nSET @acl_permission_snapshots_exists = (\n SELECT COUNT(*) FROM information_schema.tables\n WHERE table_schema = DATABASE() AND table_name = 'knowledge_space_permission_snapshots'\n);\nSET @acl_permission_snapshots_ddl = IF(\n @acl_permission_snapshots_exists = 0,\n 'CREATE TABLE IF NOT EXISTS `knowledge_space_permission_snapshots` ( `id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `subject_id` VARCHAR(255) NOT NULL, `role` VARCHAR(16) NOT NULL, `visibility` VARCHAR(24) NOT NULL, `access_channel` VARCHAR(16) NOT NULL, `member_revision` INT NOT NULL, `access_policy_revision` INT NOT NULL, `api_access_revision` INT NOT NULL, `permission_scopes` JSON NOT NULL, `status` VARCHAR(16) NOT NULL, `revision` INT NOT NULL, `expires_at` DATETIME(3) NOT NULL, `revoked_at` DATETIME(3), `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, CONSTRAINT `knowledge_space_permission_snapshots_role_ck` CHECK (`role` IN (''owner'', ''editor'', ''viewer'')), CONSTRAINT `knowledge_space_permission_snapshots_visibility_ck` CHECK (`visibility` IN (''only_me'', ''all_members'', ''partial_members'')), CONSTRAINT `knowledge_space_permission_snapshots_channel_ck` CHECK (`access_channel` IN (''interactive'', ''service_api'', ''mcp'', ''agent'')), CONSTRAINT `knowledge_space_permission_snapshots_status_ck` CHECK (`status` IN (''active'', ''revoked'', ''expired'')), CONSTRAINT `knowledge_space_permission_snapshots_revisions_ck` CHECK ( `revision` >= 1 AND `member_revision` >= 1 AND `access_policy_revision` >= 1 AND `api_access_revision` >= 1 ), CONSTRAINT `knowledge_space_permission_snapshots_revocation_ck` CHECK ( (`status` = ''revoked'' AND `revoked_at` IS NOT NULL) OR (`status` <> ''revoked'' AND `revoked_at` IS NULL) ), CONSTRAINT `knowledge_space_permission_snapshots_space_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE );',\n 'DO 0'\n);\nPREPARE acl_permission_snapshots_statement FROM @acl_permission_snapshots_ddl;\nEXECUTE acl_permission_snapshots_statement;\nDEALLOCATE PREPARE acl_permission_snapshots_statement;\n\nCREATE INDEX IF NOT EXISTS `knowledge_space_permission_snapshots_scope_subject_idx`\n ON `knowledge_space_permission_snapshots` (\n `tenant_id`, `knowledge_space_id`, `subject_id`, `status`, `expires_at`, `id`\n );\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_permission_snapshots_scope_id_uq`\n ON `knowledge_space_permission_snapshots` (`tenant_id`, `knowledge_space_id`, `id`);\n", path: "packages/database/migrations/0013_space_access_control.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0014_source_credential_refs\n-- Dialect: postgres\n\n-- Source rows retain only an opaque reference. Secret bytes live in the configured SecretStore;\n-- legacy metadata.credentials values are moved by a fenced, restart-safe application worker.\nALTER TABLE \"sources\"\n ADD COLUMN IF NOT EXISTS \"credential_ref\" TEXT;\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"sources_credential_ref_uq\"\n ON \"sources\" (\"credential_ref\")\n WHERE \"credential_ref\" IS NOT NULL;\nCREATE INDEX IF NOT EXISTS \"sources_credential_backfill_discovery_idx\"\n ON \"sources\" (\"id\")\n WHERE \"credential_ref\" IS NULL;\nCREATE UNIQUE INDEX IF NOT EXISTS \"sources_space_id_uq\"\n ON \"sources\" (\"knowledge_space_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"source_credential_backfills\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" TEXT NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"source_id\" UUID NOT NULL,\n \"source_version\" INTEGER NOT NULL,\n \"candidate_credential_ref\" TEXT NOT NULL,\n \"secret_fingerprint\" CHAR(64) NOT NULL,\n \"run_state\" TEXT NOT NULL,\n \"worker_id\" TEXT,\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"heartbeat_at\" TIMESTAMPTZ,\n \"retry_count\" INTEGER NOT NULL,\n \"row_version\" INTEGER NOT NULL,\n \"last_error_code\" TEXT,\n \"last_error_message\" TEXT,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"completed_at\" TIMESTAMPTZ,\n CONSTRAINT \"source_credential_backfills_source_version_ck\"\n CHECK (\"source_version\" >= 1),\n CONSTRAINT \"source_credential_backfills_counts_ck\"\n CHECK (\"retry_count\" >= 0 AND \"row_version\" >= 0),\n CONSTRAINT \"source_credential_backfills_state_ck\"\n CHECK (\"run_state\" IN ('queued', 'running', 'succeeded', 'failed')),\n CONSTRAINT \"source_credential_backfills_lease_ck\"\n CHECK (\n (\n \"run_state\" = 'running'\n AND \"worker_id\" IS NOT NULL\n AND \"lease_token\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL\n AND \"heartbeat_at\" IS NOT NULL\n AND \"completed_at\" IS NULL\n )\n OR (\n \"run_state\" <> 'running'\n AND \"worker_id\" IS NULL\n AND \"lease_token\" IS NULL\n AND \"lease_expires_at\" IS NULL\n AND \"heartbeat_at\" IS NULL\n )\n ),\n CONSTRAINT \"source_credential_backfills_terminal_ck\"\n CHECK (\n (\"run_state\" IN ('succeeded', 'failed') AND \"completed_at\" IS NOT NULL)\n OR (\"run_state\" IN ('queued', 'running') AND \"completed_at\" IS NULL)\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\"knowledge_space_id\", \"source_id\")\n REFERENCES \"sources\" (\"knowledge_space_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_credential_backfills_source_uq\"\n ON \"source_credential_backfills\" (\"tenant_id\", \"knowledge_space_id\", \"source_id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_credential_backfills_candidate_ref_uq\"\n ON \"source_credential_backfills\" (\"candidate_credential_ref\");\nCREATE INDEX IF NOT EXISTS \"source_credential_backfills_claim_idx\"\n ON \"source_credential_backfills\" (\"run_state\", \"lease_expires_at\", \"updated_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"source_credential_backfills_scope_idx\"\n ON \"source_credential_backfills\" (\"tenant_id\", \"knowledge_space_id\", \"source_id\", \"id\");\n\n-- This ledger intentionally has no FK to sources/spaces: credential erasure must survive resource\n-- deletion long enough for the cleanup worker to remove encrypted bytes from SecretStore.\nCREATE TABLE IF NOT EXISTS \"source_secret_lifecycle_refs\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" TEXT NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"source_id\" UUID NOT NULL,\n \"credential_ref\" TEXT NOT NULL,\n \"operation_id\" TEXT NOT NULL,\n \"purpose\" TEXT NOT NULL,\n \"state\" TEXT NOT NULL,\n \"source_version\" INTEGER,\n \"recover_after\" TIMESTAMPTZ NOT NULL,\n \"next_delete_at\" TIMESTAMPTZ,\n \"worker_id\" TEXT,\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"heartbeat_at\" TIMESTAMPTZ,\n \"delete_attempts\" INTEGER NOT NULL,\n \"row_version\" INTEGER NOT NULL,\n \"last_error_code\" TEXT,\n \"last_error_message\" TEXT,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"deleted_at\" TIMESTAMPTZ,\n CONSTRAINT \"source_secret_lifecycle_refs_source_version_ck\"\n CHECK (\"source_version\" IS NULL OR \"source_version\" >= 1),\n CONSTRAINT \"source_secret_lifecycle_refs_purpose_ck\"\n CHECK (\"purpose\" IN ('create', 'rotate', 'backfill')),\n CONSTRAINT \"source_secret_lifecycle_refs_counts_ck\"\n CHECK (\"delete_attempts\" >= 0 AND \"row_version\" >= 0),\n CONSTRAINT \"source_secret_lifecycle_refs_state_ck\"\n CHECK (\"state\" IN ('staged', 'candidate', 'active', 'retired', 'deleting', 'deleted')),\n CONSTRAINT \"source_secret_lifecycle_refs_lease_ck\"\n CHECK (\n (\n \"state\" = 'deleting'\n AND \"worker_id\" IS NOT NULL\n AND \"lease_token\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL\n AND \"heartbeat_at\" IS NOT NULL\n AND \"deleted_at\" IS NULL\n )\n OR (\n \"state\" <> 'deleting'\n AND \"worker_id\" IS NULL\n AND \"lease_token\" IS NULL\n AND \"lease_expires_at\" IS NULL\n AND \"heartbeat_at\" IS NULL\n )\n ),\n CONSTRAINT \"source_secret_lifecycle_refs_terminal_ck\"\n CHECK (\n (\"state\" = 'deleted' AND \"deleted_at\" IS NOT NULL)\n OR (\"state\" <> 'deleted' AND \"deleted_at\" IS NULL)\n )\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_secret_lifecycle_refs_ref_uq\"\n ON \"source_secret_lifecycle_refs\" (\"credential_ref\");\nCREATE INDEX IF NOT EXISTS \"source_secret_lifecycle_refs_operation_idx\"\n ON \"source_secret_lifecycle_refs\" (\"operation_id\", \"state\", \"id\");\nCREATE INDEX IF NOT EXISTS \"source_secret_lifecycle_refs_claim_idx\"\n ON \"source_secret_lifecycle_refs\"\n (\"state\", \"next_delete_at\", \"lease_expires_at\", \"updated_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"source_secret_lifecycle_refs_recovery_idx\"\n ON \"source_secret_lifecycle_refs\" (\"state\", \"recover_after\", \"id\");\nCREATE INDEX IF NOT EXISTS \"source_secret_lifecycle_refs_scope_idx\"\n ON \"source_secret_lifecycle_refs\" (\"tenant_id\", \"knowledge_space_id\", \"source_id\", \"id\");\n\n-- Rolling upgrades may replay this migration after credential refs were already written. Register\n-- those refs as active before application traffic can rotate/revoke them; source ids are stable UUIDs\n-- and are safe deterministic lifecycle ids in this table's independent keyspace.\nINSERT INTO \"source_secret_lifecycle_refs\" (\n \"id\", \"tenant_id\", \"knowledge_space_id\", \"source_id\", \"credential_ref\", \"operation_id\",\n \"purpose\", \"state\", \"source_version\", \"recover_after\", \"delete_attempts\", \"row_version\",\n \"created_at\", \"updated_at\"\n)\nSELECT\n src.\"id\", space.\"tenant_id\", src.\"knowledge_space_id\", src.\"id\", src.\"credential_ref\",\n 'legacy-source:' || src.\"id\"::TEXT || ':' || src.\"version\"::TEXT,\n 'rotate', 'active', src.\"version\", src.\"updated_at\", 0, 0, src.\"updated_at\", src.\"updated_at\"\nFROM \"sources\" src\nINNER JOIN \"knowledge_spaces\" space ON space.\"id\" = src.\"knowledge_space_id\"\nWHERE src.\"credential_ref\" IS NOT NULL\nON CONFLICT DO NOTHING;\n\n-- ON CONFLICT makes crash replay safe, but it must never hide a ref/id collision or a partial\n-- rolling-upgrade registry. Verify both directions and abort before this migration is recorded.\nDO $kfs_source_secret_lifecycle_guard$\nBEGIN\n IF EXISTS (\n SELECT 1\n FROM \"sources\" src\n INNER JOIN \"knowledge_spaces\" space ON space.\"id\" = src.\"knowledge_space_id\"\n LEFT JOIN \"source_secret_lifecycle_refs\" lifecycle\n ON lifecycle.\"credential_ref\" = src.\"credential_ref\"\n WHERE src.\"credential_ref\" IS NOT NULL\n AND (\n lifecycle.\"id\" IS NULL\n OR lifecycle.\"state\" <> 'active'\n OR lifecycle.\"tenant_id\" <> space.\"tenant_id\"\n OR lifecycle.\"knowledge_space_id\" <> src.\"knowledge_space_id\"\n OR lifecycle.\"source_id\" <> src.\"id\"\n )\n ) THEN\n RAISE EXCEPTION\n 'source credential_ref is missing a matching active lifecycle registry row';\n END IF;\n\n IF EXISTS (\n SELECT 1\n FROM \"source_secret_lifecycle_refs\" lifecycle\n LEFT JOIN \"sources\" src\n ON src.\"id\" = lifecycle.\"source_id\"\n AND src.\"knowledge_space_id\" = lifecycle.\"knowledge_space_id\"\n LEFT JOIN \"knowledge_spaces\" space\n ON space.\"id\" = lifecycle.\"knowledge_space_id\"\n WHERE lifecycle.\"state\" = 'active'\n AND (\n src.\"id\" IS NULL\n OR src.\"credential_ref\" IS DISTINCT FROM lifecycle.\"credential_ref\"\n OR space.\"id\" IS NULL\n OR space.\"tenant_id\" IS DISTINCT FROM lifecycle.\"tenant_id\"\n )\n ) THEN\n RAISE EXCEPTION\n 'active source secret lifecycle row is orphaned or does not match its source';\n END IF;\nEND\n$kfs_source_secret_lifecycle_guard$;\n", path: "packages/database/migrations/0014_source_credential_refs.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0014_source_credential_refs\n-- Dialect: tidb\n\n-- Source rows retain only an opaque reference. Secret bytes live in the configured SecretStore;\n-- legacy metadata.credentials values are moved by a fenced, restart-safe application worker.\nALTER TABLE `sources`\n ADD COLUMN IF NOT EXISTS `credential_ref` VARCHAR(255);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `sources_credential_ref_uq`\n ON `sources` (`credential_ref`);\nCREATE INDEX IF NOT EXISTS `sources_credential_backfill_discovery_idx`\n ON `sources` (`credential_ref`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `sources_space_id_uq`\n ON `sources` (`knowledge_space_id`, `id`);\n\nCREATE TABLE IF NOT EXISTS `source_credential_backfills` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `source_id` CHAR(36) NOT NULL,\n `source_version` INT NOT NULL,\n `candidate_credential_ref` VARCHAR(255) NOT NULL,\n `secret_fingerprint` CHAR(64) NOT NULL,\n `run_state` VARCHAR(16) NOT NULL,\n `worker_id` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `heartbeat_at` DATETIME(3),\n `retry_count` INT NOT NULL,\n `row_version` INT NOT NULL,\n `last_error_code` VARCHAR(64),\n `last_error_message` TEXT,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `completed_at` DATETIME(3),\n CONSTRAINT `source_credential_backfills_source_version_ck`\n CHECK (`source_version` >= 1),\n CONSTRAINT `source_credential_backfills_counts_ck`\n CHECK (`retry_count` >= 0 AND `row_version` >= 0),\n CONSTRAINT `source_credential_backfills_state_ck`\n CHECK (`run_state` IN ('queued', 'running', 'succeeded', 'failed')),\n CONSTRAINT `source_credential_backfills_lease_ck`\n CHECK (\n (\n `run_state` = 'running'\n AND `worker_id` IS NOT NULL\n AND `lease_token` IS NOT NULL\n AND `lease_expires_at` IS NOT NULL\n AND `heartbeat_at` IS NOT NULL\n AND `completed_at` IS NULL\n )\n OR (\n `run_state` <> 'running'\n AND `worker_id` IS NULL\n AND `lease_token` IS NULL\n AND `lease_expires_at` IS NULL\n AND `heartbeat_at` IS NULL\n )\n ),\n CONSTRAINT `source_credential_backfills_terminal_ck`\n CHECK (\n (`run_state` IN ('succeeded', 'failed') AND `completed_at` IS NOT NULL)\n OR (`run_state` IN ('queued', 'running') AND `completed_at` IS NULL)\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (`knowledge_space_id`, `source_id`)\n REFERENCES `sources` (`knowledge_space_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `source_credential_backfills_source_uq`\n ON `source_credential_backfills` (`tenant_id`, `knowledge_space_id`, `source_id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_credential_backfills_candidate_ref_uq`\n ON `source_credential_backfills` (`candidate_credential_ref`);\nCREATE INDEX IF NOT EXISTS `source_credential_backfills_claim_idx`\n ON `source_credential_backfills` (`run_state`, `lease_expires_at`, `updated_at`, `id`);\nCREATE INDEX IF NOT EXISTS `source_credential_backfills_scope_idx`\n ON `source_credential_backfills` (`tenant_id`, `knowledge_space_id`, `source_id`, `id`);\n\n-- This ledger intentionally has no FK to sources/spaces: credential erasure must survive resource\n-- deletion long enough for the cleanup worker to remove encrypted bytes from SecretStore.\nCREATE TABLE IF NOT EXISTS `source_secret_lifecycle_refs` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `source_id` CHAR(36) NOT NULL,\n `credential_ref` VARCHAR(255) NOT NULL,\n `operation_id` VARCHAR(255) NOT NULL,\n `purpose` VARCHAR(16) NOT NULL,\n `state` VARCHAR(16) NOT NULL,\n `source_version` INT,\n `recover_after` DATETIME(3) NOT NULL,\n `next_delete_at` DATETIME(3),\n `worker_id` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `heartbeat_at` DATETIME(3),\n `delete_attempts` INT NOT NULL,\n `row_version` INT NOT NULL,\n `last_error_code` VARCHAR(64),\n `last_error_message` TEXT,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `deleted_at` DATETIME(3),\n CONSTRAINT `source_secret_lifecycle_refs_source_version_ck`\n CHECK (`source_version` IS NULL OR `source_version` >= 1),\n CONSTRAINT `source_secret_lifecycle_refs_purpose_ck`\n CHECK (`purpose` IN ('create', 'rotate', 'backfill')),\n CONSTRAINT `source_secret_lifecycle_refs_counts_ck`\n CHECK (`delete_attempts` >= 0 AND `row_version` >= 0),\n CONSTRAINT `source_secret_lifecycle_refs_state_ck`\n CHECK (`state` IN ('staged', 'candidate', 'active', 'retired', 'deleting', 'deleted')),\n CONSTRAINT `source_secret_lifecycle_refs_lease_ck`\n CHECK (\n (\n `state` = 'deleting'\n AND `worker_id` IS NOT NULL\n AND `lease_token` IS NOT NULL\n AND `lease_expires_at` IS NOT NULL\n AND `heartbeat_at` IS NOT NULL\n AND `deleted_at` IS NULL\n )\n OR (\n `state` <> 'deleting'\n AND `worker_id` IS NULL\n AND `lease_token` IS NULL\n AND `lease_expires_at` IS NULL\n AND `heartbeat_at` IS NULL\n )\n ),\n CONSTRAINT `source_secret_lifecycle_refs_terminal_ck`\n CHECK (\n (`state` = 'deleted' AND `deleted_at` IS NOT NULL)\n OR (`state` <> 'deleted' AND `deleted_at` IS NULL)\n )\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `source_secret_lifecycle_refs_ref_uq`\n ON `source_secret_lifecycle_refs` (`credential_ref`);\nCREATE INDEX IF NOT EXISTS `source_secret_lifecycle_refs_operation_idx`\n ON `source_secret_lifecycle_refs` (`operation_id`, `state`, `id`);\nCREATE INDEX IF NOT EXISTS `source_secret_lifecycle_refs_claim_idx`\n ON `source_secret_lifecycle_refs`\n (`state`, `next_delete_at`, `lease_expires_at`, `updated_at`, `id`);\nCREATE INDEX IF NOT EXISTS `source_secret_lifecycle_refs_recovery_idx`\n ON `source_secret_lifecycle_refs` (`state`, `recover_after`, `id`);\nCREATE INDEX IF NOT EXISTS `source_secret_lifecycle_refs_scope_idx`\n ON `source_secret_lifecycle_refs` (`tenant_id`, `knowledge_space_id`, `source_id`, `id`);\n\n-- Rolling upgrades may replay this migration after credential refs were already written. Register\n-- those refs as active before application traffic can rotate/revoke them; source ids are stable UUIDs\n-- and are safe deterministic lifecycle ids in this table's independent keyspace.\nINSERT IGNORE INTO `source_secret_lifecycle_refs` (\n `id`, `tenant_id`, `knowledge_space_id`, `source_id`, `credential_ref`, `operation_id`,\n `purpose`, `state`, `source_version`, `recover_after`, `delete_attempts`, `row_version`,\n `created_at`, `updated_at`\n)\nSELECT\n src.`id`, space.`tenant_id`, src.`knowledge_space_id`, src.`id`, src.`credential_ref`,\n CONCAT('legacy-source:', src.`id`, ':', src.`version`),\n 'rotate', 'active', src.`version`, src.`updated_at`, 0, 0, src.`updated_at`, src.`updated_at`\nFROM `sources` src\nINNER JOIN `knowledge_spaces` space ON space.`id` = src.`knowledge_space_id`\nWHERE src.`credential_ref` IS NOT NULL;\n\n-- INSERT IGNORE makes crash replay safe, but it must never hide a ref/id collision or a partial\n-- rolling-upgrade registry. TiDB does not support stored routines, so a temporary NOT NULL guard\n-- receives an invalid row only when a mismatch exists. The conditional INSERT then fails closed.\nDROP TEMPORARY TABLE IF EXISTS `kfs_source_secret_lifecycle_registry_guard`;\nCREATE TEMPORARY TABLE `kfs_source_secret_lifecycle_registry_guard` (\n `valid` TINYINT NOT NULL\n);\n\nINSERT INTO `kfs_source_secret_lifecycle_registry_guard` (`valid`)\nSELECT NULL\nWHERE EXISTS (\n SELECT 1\n FROM `sources` src\n INNER JOIN `knowledge_spaces` space ON space.`id` = src.`knowledge_space_id`\n LEFT JOIN `source_secret_lifecycle_refs` lifecycle\n ON lifecycle.`credential_ref` = src.`credential_ref`\n WHERE src.`credential_ref` IS NOT NULL\n AND (\n lifecycle.`id` IS NULL\n OR lifecycle.`state` <> 'active'\n OR lifecycle.`tenant_id` <> space.`tenant_id`\n OR lifecycle.`knowledge_space_id` <> src.`knowledge_space_id`\n OR lifecycle.`source_id` <> src.`id`\n )\n );\n\nINSERT INTO `kfs_source_secret_lifecycle_registry_guard` (`valid`)\nSELECT NULL\nWHERE EXISTS (\n SELECT 1\n FROM `source_secret_lifecycle_refs` lifecycle\n LEFT JOIN `sources` src\n ON src.`id` = lifecycle.`source_id`\n AND src.`knowledge_space_id` = lifecycle.`knowledge_space_id`\n LEFT JOIN `knowledge_spaces` space\n ON space.`id` = lifecycle.`knowledge_space_id`\n WHERE lifecycle.`state` = 'active'\n AND (\n src.`id` IS NULL\n OR NOT (src.`credential_ref` <=> lifecycle.`credential_ref`)\n OR space.`id` IS NULL\n OR NOT (space.`tenant_id` <=> lifecycle.`tenant_id`)\n )\n );\n\nDROP TEMPORARY TABLE `kfs_source_secret_lifecycle_registry_guard`;\n", path: "packages/database/migrations/0014_source_credential_refs.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0015_research_task_jobs\n-- Dialect: postgres\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_permission_snapshots_provenance_uq\"\n ON \"knowledge_space_permission_snapshots\" (\n \"tenant_id\", \"knowledge_space_id\", \"id\", \"subject_id\", \"access_channel\"\n );\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_permission_snapshots_trace_provenance_uq\"\n ON \"knowledge_space_permission_snapshots\" (\n \"knowledge_space_id\", \"id\", \"subject_id\", \"access_channel\"\n );\n\nCREATE TABLE IF NOT EXISTS \"research_task_jobs\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"subject_id\" VARCHAR(255) NOT NULL,\n \"permission_snapshot_id\" UUID NOT NULL,\n \"permission_snapshot_revision\" INTEGER NOT NULL,\n \"access_channel\" VARCHAR(16) NOT NULL,\n \"query\" TEXT NOT NULL,\n \"mode\" VARCHAR(16),\n \"top_k\" INTEGER,\n \"budget_usd\" DOUBLE PRECISION,\n \"limits\" JSONB NOT NULL,\n \"metadata\" JSONB NOT NULL,\n \"cost\" JSONB NOT NULL,\n \"stage\" VARCHAR(16) NOT NULL,\n \"paused_from_stage\" VARCHAR(16),\n \"queue_job_id\" VARCHAR(255),\n \"error\" TEXT,\n \"resume_after\" BIGINT,\n \"paused_at\" BIGINT,\n \"completed_at\" BIGINT,\n \"row_version\" INTEGER NOT NULL,\n \"execution_attempts\" INTEGER NOT NULL,\n \"max_execution_attempts\" INTEGER NOT NULL,\n \"worker_id\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" BIGINT,\n \"heartbeat_at\" BIGINT,\n \"retry_at\" BIGINT,\n \"created_at\" BIGINT NOT NULL,\n \"updated_at\" BIGINT NOT NULL,\n CONSTRAINT \"research_task_jobs_stage_ck\" CHECK (\n \"stage\" IN (\n 'queued', 'planning', 'retrieving', 'analyzing', 'generating',\n 'paused', 'completed', 'failed', 'canceled'\n )\n ),\n CONSTRAINT \"research_task_jobs_mode_ck\"\n CHECK (\"mode\" IS NULL OR \"mode\" IN ('auto', 'fast', 'research', 'deep')),\n CONSTRAINT \"research_task_jobs_channel_ck\"\n CHECK (\"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')),\n CONSTRAINT \"research_task_jobs_positive_ck\" CHECK (\n \"permission_snapshot_revision\" >= 1\n AND \"row_version\" >= 1\n AND \"execution_attempts\" >= 0\n AND \"max_execution_attempts\" >= 1\n AND (\"top_k\" IS NULL OR \"top_k\" >= 1)\n AND (\"budget_usd\" IS NULL OR \"budget_usd\" >= 0)\n ),\n CONSTRAINT \"research_task_jobs_lease_ck\" CHECK (\n (\"lease_token\" IS NULL AND \"worker_id\" IS NULL AND \"lease_expires_at\" IS NULL)\n OR (\"lease_token\" IS NOT NULL AND \"worker_id\" IS NOT NULL AND \"lease_expires_at\" IS NOT NULL)\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"permission_snapshot_id\", \"subject_id\", \"access_channel\"\n )\n REFERENCES \"knowledge_space_permission_snapshots\" (\n \"tenant_id\", \"knowledge_space_id\", \"id\", \"subject_id\", \"access_channel\"\n ) ON DELETE RESTRICT\n);\n\nCREATE INDEX IF NOT EXISTS \"research_task_jobs_scope_updated_idx\"\n ON \"research_task_jobs\" (\n \"tenant_id\", \"knowledge_space_id\", \"updated_at\", \"id\"\n );\nCREATE INDEX IF NOT EXISTS \"research_task_jobs_queue_idx\"\n ON \"research_task_jobs\" (\"queue_job_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"research_task_jobs_lease_idx\"\n ON \"research_task_jobs\" (\"stage\", \"lease_expires_at\", \"retry_at\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"research_task_jobs_scope_id_uq\"\n ON \"research_task_jobs\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"research_task_outbox\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"research_task_job_id\" UUID NOT NULL,\n \"delivery_revision\" INTEGER NOT NULL,\n \"event_type\" VARCHAR(32) NOT NULL,\n \"schema_version\" INTEGER NOT NULL,\n \"idempotency_key\" VARCHAR(512) NOT NULL,\n \"payload\" JSONB NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"available_at\" BIGINT NOT NULL,\n \"dispatch_attempts\" INTEGER NOT NULL,\n \"locked_by\" VARCHAR(255),\n \"locked_until\" BIGINT,\n \"lock_token\" UUID,\n \"queue_job_id\" VARCHAR(255),\n \"last_error\" TEXT,\n \"delivered_at\" BIGINT,\n \"created_at\" BIGINT NOT NULL,\n \"updated_at\" BIGINT NOT NULL,\n CONSTRAINT \"research_task_outbox_event_ck\" CHECK (\"event_type\" = 'research.task'),\n CONSTRAINT \"research_task_outbox_schema_ck\" CHECK (\"schema_version\" = 1),\n CONSTRAINT \"research_task_outbox_status_ck\" CHECK (\n \"status\" IN ('pending', 'dispatching', 'dispatched', 'leased', 'completed', 'canceled', 'dead')\n ),\n CONSTRAINT \"research_task_outbox_positive_ck\" CHECK (\n \"delivery_revision\" >= 1 AND \"dispatch_attempts\" >= 0\n ),\n CONSTRAINT \"research_task_outbox_lock_ck\" CHECK (\n (\"lock_token\" IS NULL AND \"locked_by\" IS NULL AND \"locked_until\" IS NULL)\n OR (\"lock_token\" IS NOT NULL AND \"locked_by\" IS NOT NULL AND \"locked_until\" IS NOT NULL)\n ),\n FOREIGN KEY (\"research_task_job_id\") REFERENCES \"research_task_jobs\" (\"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"research_task_outbox_idempotency_uq\"\n ON \"research_task_outbox\" (\"idempotency_key\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"research_task_outbox_job_delivery_uq\"\n ON \"research_task_outbox\" (\"research_task_job_id\", \"delivery_revision\");\nCREATE INDEX IF NOT EXISTS \"research_task_outbox_claim_idx\"\n ON \"research_task_outbox\" (\"status\", \"available_at\", \"locked_until\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"research_task_partial_results\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"research_task_job_id\" UUID NOT NULL,\n \"sequence\" INTEGER NOT NULL,\n \"idempotency_key\" VARCHAR(512) NOT NULL,\n \"evidence_bundle\" JSONB NOT NULL,\n \"created_at\" BIGINT NOT NULL,\n FOREIGN KEY (\"research_task_job_id\") REFERENCES \"research_task_jobs\" (\"id\") ON DELETE CASCADE,\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"research_task_partials_job_sequence_uq\"\n ON \"research_task_partial_results\" (\"research_task_job_id\", \"sequence\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"research_task_partials_job_idempotency_uq\"\n ON \"research_task_partial_results\" (\"research_task_job_id\", \"idempotency_key\");\nCREATE INDEX IF NOT EXISTS \"research_task_partials_scope_job_sequence_idx\"\n ON \"research_task_partial_results\" (\n \"tenant_id\", \"research_task_job_id\", \"sequence\", \"id\"\n );\n\nCREATE TABLE IF NOT EXISTS \"research_task_progress_events\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"research_task_job_id\" UUID NOT NULL,\n \"sequence\" INTEGER NOT NULL,\n \"idempotency_key\" VARCHAR(512) NOT NULL,\n \"event_type\" VARCHAR(64) NOT NULL,\n \"stage\" VARCHAR(16) NOT NULL,\n \"payload\" JSONB NOT NULL,\n \"created_at\" BIGINT NOT NULL,\n CONSTRAINT \"research_task_progress_sequence_ck\" CHECK (\"sequence\" >= 1),\n CONSTRAINT \"research_task_progress_event_ck\" CHECK (\n \"event_type\" IN (\n 'research_task.canceled', 'research_task.failed', 'research_task.paused',\n 'research_task.resumed', 'research_task.stage_changed', 'research_task.started'\n )\n ),\n CONSTRAINT \"research_task_progress_stage_ck\" CHECK (\n \"stage\" IN (\n 'queued', 'planning', 'retrieving', 'analyzing', 'generating',\n 'paused', 'completed', 'failed', 'canceled'\n )\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"research_task_job_id\")\n REFERENCES \"research_task_jobs\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"research_task_progress_job_sequence_uq\"\n ON \"research_task_progress_events\" (\"research_task_job_id\", \"sequence\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"research_task_progress_job_idempotency_uq\"\n ON \"research_task_progress_events\" (\"research_task_job_id\", \"idempotency_key\");\nCREATE INDEX IF NOT EXISTS \"research_task_progress_scope_job_sequence_idx\"\n ON \"research_task_progress_events\" (\n \"tenant_id\", \"research_task_job_id\", \"sequence\", \"id\"\n );\n\n-- Durable grants issued from service API keys must remain coupled to the exact credential\n-- revision and expiry. Null means the snapshot was issued by a non-key principal.\nALTER TABLE \"knowledge_space_permission_snapshots\"\n ADD COLUMN IF NOT EXISTS \"api_key_id\" UUID;\nALTER TABLE \"knowledge_space_permission_snapshots\"\n ADD COLUMN IF NOT EXISTS \"api_key_revision\" INTEGER;\nALTER TABLE \"knowledge_space_permission_snapshots\"\n ADD COLUMN IF NOT EXISTS \"api_key_expires_at\" TIMESTAMPTZ;\n-- The migration runner records schema_migrations after executing this artifact. If the process\n-- exits between those operations, the complete artifact is replayed. PostgreSQL has no\n-- ADD CONSTRAINT IF NOT EXISTS, so every incremental constraint must be conditionally installed.\nDO $kfs_0015_permission_snapshot_api_key_binding_ck$\nBEGIN\n IF NOT EXISTS (\n SELECT 1\n FROM \"pg_constraint\"\n WHERE \"conrelid\" = 'knowledge_space_permission_snapshots'::regclass\n AND \"conname\" = 'knowledge_space_permission_snapshots_api_key_binding_ck'\n ) THEN\n ALTER TABLE \"knowledge_space_permission_snapshots\"\n ADD CONSTRAINT \"knowledge_space_permission_snapshots_api_key_binding_ck\" CHECK (\n (\n \"api_key_id\" IS NULL\n AND \"api_key_revision\" IS NULL\n AND \"api_key_expires_at\" IS NULL\n )\n OR (\"api_key_id\" IS NOT NULL AND \"api_key_revision\" >= 1)\n );\n END IF;\nEND\n$kfs_0015_permission_snapshot_api_key_binding_ck$;\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_api_keys_scope_id_uq\"\n ON \"knowledge_space_api_keys\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\nDO $kfs_0015_permission_snapshot_api_key_fk$\nBEGIN\n IF NOT EXISTS (\n SELECT 1\n FROM \"pg_constraint\"\n WHERE \"conrelid\" = 'knowledge_space_permission_snapshots'::regclass\n AND \"conname\" = 'knowledge_space_permission_snapshots_api_key_fk'\n ) THEN\n ALTER TABLE \"knowledge_space_permission_snapshots\"\n ADD CONSTRAINT \"knowledge_space_permission_snapshots_api_key_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"api_key_id\")\n REFERENCES \"knowledge_space_api_keys\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE RESTRICT;\n END IF;\nEND\n$kfs_0015_permission_snapshot_api_key_fk$;\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_permission_snapshots_space_id_uq\"\n ON \"knowledge_space_permission_snapshots\" (\"knowledge_space_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_space_permission_snapshots_api_key_idx\"\n ON \"knowledge_space_permission_snapshots\" (\n \"tenant_id\", \"knowledge_space_id\", \"api_key_id\", \"api_key_revision\"\n );\n\n-- Legacy rows intentionally remain unowned and are denied by the API. New AnswerTrace writers\n-- always persist the authenticated subject so EvidenceBundle reads cannot cross members.\nALTER TABLE \"answer_traces\"\n ADD COLUMN IF NOT EXISTS \"subject_id\" VARCHAR(255);\nALTER TABLE \"answer_traces\"\n ADD COLUMN IF NOT EXISTS \"permission_snapshot_id\" UUID;\nALTER TABLE \"answer_traces\"\n ADD COLUMN IF NOT EXISTS \"permission_snapshot_revision\" INTEGER;\nALTER TABLE \"answer_traces\"\n ADD COLUMN IF NOT EXISTS \"access_channel\" VARCHAR(16);\nDO $kfs_0015_answer_trace_permission_snapshot_binding_ck$\nBEGIN\n IF NOT EXISTS (\n SELECT 1\n FROM \"pg_constraint\"\n WHERE \"conrelid\" = 'answer_traces'::regclass\n AND \"conname\" = 'answer_traces_permission_snapshot_binding_ck'\n ) THEN\n ALTER TABLE \"answer_traces\"\n ADD CONSTRAINT \"answer_traces_permission_snapshot_binding_ck\" CHECK (\n (\n \"permission_snapshot_id\" IS NULL\n AND \"permission_snapshot_revision\" IS NULL\n AND \"access_channel\" IS NULL\n )\n OR (\n \"subject_id\" IS NOT NULL\n AND\n \"permission_snapshot_id\" IS NOT NULL\n AND \"permission_snapshot_revision\" >= 1\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')\n )\n );\n END IF;\nEND\n$kfs_0015_answer_trace_permission_snapshot_binding_ck$;\nDO $kfs_0015_answer_trace_permission_snapshot_fk$\nBEGIN\n IF NOT EXISTS (\n SELECT 1\n FROM \"pg_constraint\"\n WHERE \"conrelid\" = 'answer_traces'::regclass\n AND \"conname\" = 'answer_traces_permission_snapshot_fk'\n ) THEN\n ALTER TABLE \"answer_traces\"\n ADD CONSTRAINT \"answer_traces_permission_snapshot_fk\"\n FOREIGN KEY (\n \"knowledge_space_id\", \"permission_snapshot_id\", \"subject_id\", \"access_channel\"\n )\n REFERENCES \"knowledge_space_permission_snapshots\" (\n \"knowledge_space_id\", \"id\", \"subject_id\", \"access_channel\"\n )\n ON DELETE RESTRICT;\n END IF;\nEND\n$kfs_0015_answer_trace_permission_snapshot_fk$;\nCREATE INDEX IF NOT EXISTS \"answer_traces_space_subject_created_idx\"\n ON \"answer_traces\" (\"knowledge_space_id\", \"subject_id\", \"created_at\", \"id\");\n", path: "packages/database/migrations/0015_research_task_jobs.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0015_research_task_jobs\n-- Dialect: tidb\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_permission_snapshots_provenance_uq`\n ON `knowledge_space_permission_snapshots` (\n `tenant_id`, `knowledge_space_id`, `id`, `subject_id`, `access_channel`\n );\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_permission_snapshots_trace_provenance_uq`\n ON `knowledge_space_permission_snapshots` (\n `knowledge_space_id`, `id`, `subject_id`, `access_channel`\n );\n\n-- TiDB revalidates inbound foreign keys for CREATE TABLE IF NOT EXISTS. Once the outbox and\n-- progress tables exist, replaying this otherwise-idempotent statement can fail while resolving\n-- their references back to research_task_jobs, so skip the table DDL after its first commit.\nSET @kfs_0015_research_task_jobs_exists = (\n SELECT COUNT(*)\n FROM information_schema.tables\n WHERE table_schema = DATABASE() AND table_name = 'research_task_jobs'\n);\nSET @kfs_0015_research_task_jobs_ddl = IF(\n @kfs_0015_research_task_jobs_exists = 0,\n 'CREATE TABLE IF NOT EXISTS `research_task_jobs` ( `id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `subject_id` VARCHAR(255) NOT NULL, `permission_snapshot_id` CHAR(36) NOT NULL, `permission_snapshot_revision` INT NOT NULL, `access_channel` VARCHAR(16) NOT NULL, `query` TEXT NOT NULL, `mode` VARCHAR(16), `top_k` INT, `budget_usd` DOUBLE, `limits` JSON NOT NULL, `metadata` JSON NOT NULL, `cost` JSON NOT NULL, `stage` VARCHAR(16) NOT NULL, `paused_from_stage` VARCHAR(16), `queue_job_id` VARCHAR(255), `error` TEXT, `resume_after` BIGINT, `paused_at` BIGINT, `completed_at` BIGINT, `row_version` INT NOT NULL, `execution_attempts` INT NOT NULL, `max_execution_attempts` INT NOT NULL, `worker_id` VARCHAR(255), `lease_token` CHAR(36), `lease_expires_at` BIGINT, `heartbeat_at` BIGINT, `retry_at` BIGINT, `created_at` BIGINT NOT NULL, `updated_at` BIGINT NOT NULL, CONSTRAINT `research_task_jobs_stage_ck` CHECK ( `stage` IN ( ''queued'', ''planning'', ''retrieving'', ''analyzing'', ''generating'', ''paused'', ''completed'', ''failed'', ''canceled'' ) ), CONSTRAINT `research_task_jobs_mode_ck` CHECK (`mode` IS NULL OR `mode` IN (''auto'', ''fast'', ''research'', ''deep'')), CONSTRAINT `research_task_jobs_channel_ck` CHECK (`access_channel` IN (''interactive'', ''service_api'', ''mcp'', ''agent'')), CONSTRAINT `research_task_jobs_positive_ck` CHECK ( `permission_snapshot_revision` >= 1 AND `row_version` >= 1 AND `execution_attempts` >= 0 AND `max_execution_attempts` >= 1 AND (`top_k` IS NULL OR `top_k` >= 1) AND (`budget_usd` IS NULL OR `budget_usd` >= 0) ), CONSTRAINT `research_task_jobs_lease_ck` CHECK ( (`lease_token` IS NULL AND `worker_id` IS NULL AND `lease_expires_at` IS NULL) OR (`lease_token` IS NOT NULL AND `worker_id` IS NOT NULL AND `lease_expires_at` IS NOT NULL) ), FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE, FOREIGN KEY ( `tenant_id`, `knowledge_space_id`, `permission_snapshot_id`, `subject_id`, `access_channel` ) REFERENCES `knowledge_space_permission_snapshots` ( `tenant_id`, `knowledge_space_id`, `id`, `subject_id`, `access_channel` ) );',\n 'DO 0'\n);\nPREPARE kfs_0015_research_task_jobs_statement FROM @kfs_0015_research_task_jobs_ddl;\nEXECUTE kfs_0015_research_task_jobs_statement;\nDEALLOCATE PREPARE kfs_0015_research_task_jobs_statement;\n\nCREATE INDEX IF NOT EXISTS `research_task_jobs_scope_updated_idx`\n ON `research_task_jobs` (`tenant_id`, `knowledge_space_id`, `updated_at`, `id`);\nCREATE INDEX IF NOT EXISTS `research_task_jobs_queue_idx`\n ON `research_task_jobs` (`queue_job_id`, `id`);\nCREATE INDEX IF NOT EXISTS `research_task_jobs_lease_idx`\n ON `research_task_jobs` (`stage`, `lease_expires_at`, `retry_at`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `research_task_jobs_scope_id_uq`\n ON `research_task_jobs` (`tenant_id`, `knowledge_space_id`, `id`);\n\nCREATE TABLE IF NOT EXISTS `research_task_outbox` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `research_task_job_id` CHAR(36) NOT NULL,\n `delivery_revision` INT NOT NULL,\n `event_type` VARCHAR(32) NOT NULL,\n `schema_version` INT NOT NULL,\n `idempotency_key` VARCHAR(512) NOT NULL,\n `payload` JSON NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `available_at` BIGINT NOT NULL,\n `dispatch_attempts` INT NOT NULL,\n `locked_by` VARCHAR(255),\n `locked_until` BIGINT,\n `lock_token` CHAR(36),\n `queue_job_id` VARCHAR(255),\n `last_error` TEXT,\n `delivered_at` BIGINT,\n `created_at` BIGINT NOT NULL,\n `updated_at` BIGINT NOT NULL,\n CONSTRAINT `research_task_outbox_event_ck` CHECK (`event_type` = 'research.task'),\n CONSTRAINT `research_task_outbox_schema_ck` CHECK (`schema_version` = 1),\n CONSTRAINT `research_task_outbox_status_ck` CHECK (\n `status` IN ('pending', 'dispatching', 'dispatched', 'leased', 'completed', 'canceled', 'dead')\n ),\n CONSTRAINT `research_task_outbox_positive_ck` CHECK (\n `delivery_revision` >= 1 AND `dispatch_attempts` >= 0\n ),\n CONSTRAINT `research_task_outbox_lock_ck` CHECK (\n (`lock_token` IS NULL AND `locked_by` IS NULL AND `locked_until` IS NULL)\n OR (`lock_token` IS NOT NULL AND `locked_by` IS NOT NULL AND `locked_until` IS NOT NULL)\n ),\n FOREIGN KEY (`research_task_job_id`) REFERENCES `research_task_jobs` (`id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `research_task_outbox_idempotency_uq`\n ON `research_task_outbox` (`idempotency_key`);\nCREATE UNIQUE INDEX IF NOT EXISTS `research_task_outbox_job_delivery_uq`\n ON `research_task_outbox` (`research_task_job_id`, `delivery_revision`);\nCREATE INDEX IF NOT EXISTS `research_task_outbox_claim_idx`\n ON `research_task_outbox` (`status`, `available_at`, `locked_until`, `id`);\n\nCREATE TABLE IF NOT EXISTS `research_task_partial_results` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `research_task_job_id` CHAR(36) NOT NULL,\n `sequence` INT NOT NULL,\n `idempotency_key` VARCHAR(512) NOT NULL,\n `evidence_bundle` JSON NOT NULL,\n `created_at` BIGINT NOT NULL,\n FOREIGN KEY (`research_task_job_id`) REFERENCES `research_task_jobs` (`id`) ON DELETE CASCADE,\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `research_task_partials_job_sequence_uq`\n ON `research_task_partial_results` (`research_task_job_id`, `sequence`);\nCREATE UNIQUE INDEX IF NOT EXISTS `research_task_partials_job_idempotency_uq`\n ON `research_task_partial_results` (`research_task_job_id`, `idempotency_key`);\nCREATE INDEX IF NOT EXISTS `research_task_partials_scope_job_sequence_idx`\n ON `research_task_partial_results` (\n `tenant_id`, `research_task_job_id`, `sequence`, `id`\n );\n\nCREATE TABLE IF NOT EXISTS `research_task_progress_events` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `research_task_job_id` CHAR(36) NOT NULL,\n `sequence` INT NOT NULL,\n `idempotency_key` VARCHAR(512) NOT NULL,\n `event_type` VARCHAR(64) NOT NULL,\n `stage` VARCHAR(16) NOT NULL,\n `payload` JSON NOT NULL,\n `created_at` BIGINT NOT NULL,\n CONSTRAINT `research_task_progress_sequence_ck` CHECK (`sequence` >= 1),\n CONSTRAINT `research_task_progress_event_ck` CHECK (\n `event_type` IN (\n 'research_task.canceled', 'research_task.failed', 'research_task.paused',\n 'research_task.resumed', 'research_task.stage_changed', 'research_task.started'\n )\n ),\n CONSTRAINT `research_task_progress_stage_ck` CHECK (\n `stage` IN (\n 'queued', 'planning', 'retrieving', 'analyzing', 'generating',\n 'paused', 'completed', 'failed', 'canceled'\n )\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `research_task_job_id`)\n REFERENCES `research_task_jobs` (`tenant_id`, `knowledge_space_id`, `id`)\n ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `research_task_progress_job_sequence_uq`\n ON `research_task_progress_events` (`research_task_job_id`, `sequence`);\nCREATE UNIQUE INDEX IF NOT EXISTS `research_task_progress_job_idempotency_uq`\n ON `research_task_progress_events` (`research_task_job_id`, `idempotency_key`);\nCREATE INDEX IF NOT EXISTS `research_task_progress_scope_job_sequence_idx`\n ON `research_task_progress_events` (\n `tenant_id`, `research_task_job_id`, `sequence`, `id`\n );\n\n-- Durable grants issued from service API keys must remain coupled to the exact credential\n-- revision and expiry. Null means the snapshot was issued by a non-key principal.\nALTER TABLE `knowledge_space_permission_snapshots`\n ADD COLUMN IF NOT EXISTS `api_key_id` CHAR(36);\nALTER TABLE `knowledge_space_permission_snapshots`\n ADD COLUMN IF NOT EXISTS `api_key_revision` INT;\nALTER TABLE `knowledge_space_permission_snapshots`\n ADD COLUMN IF NOT EXISTS `api_key_expires_at` DATETIME(3);\n-- The migration runner records schema_migrations after executing this artifact. TiDB DDL commits\n-- independently, so a process exit in that gap replays the complete artifact. TiDB has no portable\n-- ADD CONSTRAINT IF NOT EXISTS; select either the ALTER or a no-op from information_schema.\nSET @kfs_0015_snapshot_api_key_binding_exists = (\n SELECT COUNT(*)\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'knowledge_space_permission_snapshots'\n AND constraint_name = 'knowledge_space_permission_snapshots_api_key_binding_ck'\n);\nSET @kfs_0015_snapshot_api_key_binding_ddl = IF(\n @kfs_0015_snapshot_api_key_binding_exists = 0,\n 'ALTER TABLE `knowledge_space_permission_snapshots` ADD CONSTRAINT `knowledge_space_permission_snapshots_api_key_binding_ck` CHECK ((`api_key_id` IS NULL AND `api_key_revision` IS NULL AND `api_key_expires_at` IS NULL) OR (`api_key_id` IS NOT NULL AND `api_key_revision` >= 1))',\n 'DO 0'\n);\nPREPARE kfs_0015_snapshot_api_key_binding_statement\n FROM @kfs_0015_snapshot_api_key_binding_ddl;\nEXECUTE kfs_0015_snapshot_api_key_binding_statement;\nDEALLOCATE PREPARE kfs_0015_snapshot_api_key_binding_statement;\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_api_keys_scope_id_uq`\n ON `knowledge_space_api_keys` (`tenant_id`, `knowledge_space_id`, `id`);\nSET @kfs_0015_snapshot_api_key_fk_exists = (\n SELECT COUNT(*)\n FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'knowledge_space_permission_snapshots'\n AND constraint_name = 'knowledge_space_permission_snapshots_api_key_fk'\n);\nSET @kfs_0015_snapshot_api_key_fk_ddl = IF(\n @kfs_0015_snapshot_api_key_fk_exists = 0,\n 'ALTER TABLE `knowledge_space_permission_snapshots` ADD CONSTRAINT `knowledge_space_permission_snapshots_api_key_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `api_key_id`) REFERENCES `knowledge_space_api_keys` (`tenant_id`, `knowledge_space_id`, `id`)',\n 'DO 0'\n);\nPREPARE kfs_0015_snapshot_api_key_fk_statement FROM @kfs_0015_snapshot_api_key_fk_ddl;\nEXECUTE kfs_0015_snapshot_api_key_fk_statement;\nDEALLOCATE PREPARE kfs_0015_snapshot_api_key_fk_statement;\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_permission_snapshots_space_id_uq`\n ON `knowledge_space_permission_snapshots` (`knowledge_space_id`, `id`);\nCREATE INDEX IF NOT EXISTS `knowledge_space_permission_snapshots_api_key_idx`\n ON `knowledge_space_permission_snapshots` (\n `tenant_id`, `knowledge_space_id`, `api_key_id`, `api_key_revision`\n );\n\n-- Legacy rows intentionally remain unowned and are denied by the API. New AnswerTrace writers\n-- always persist the authenticated subject so EvidenceBundle reads cannot cross members.\nALTER TABLE `answer_traces`\n ADD COLUMN IF NOT EXISTS `subject_id` VARCHAR(255);\nALTER TABLE `answer_traces`\n ADD COLUMN IF NOT EXISTS `permission_snapshot_id` CHAR(36);\nALTER TABLE `answer_traces`\n ADD COLUMN IF NOT EXISTS `permission_snapshot_revision` INT;\nALTER TABLE `answer_traces`\n ADD COLUMN IF NOT EXISTS `access_channel` VARCHAR(16);\nSET @kfs_0015_answer_trace_snapshot_binding_exists = (\n SELECT COUNT(*)\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'answer_traces'\n AND constraint_name = 'answer_traces_permission_snapshot_binding_ck'\n);\nSET @kfs_0015_answer_trace_snapshot_binding_ddl = IF(\n @kfs_0015_answer_trace_snapshot_binding_exists = 0,\n 'ALTER TABLE `answer_traces` ADD CONSTRAINT `answer_traces_permission_snapshot_binding_ck` CHECK ((`permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL AND `access_channel` IS NULL) OR (`subject_id` IS NOT NULL AND `permission_snapshot_id` IS NOT NULL AND `permission_snapshot_revision` >= 1 AND `access_channel` IN (''interactive'', ''service_api'', ''mcp'', ''agent'')))',\n 'DO 0'\n);\nPREPARE kfs_0015_answer_trace_snapshot_binding_statement\n FROM @kfs_0015_answer_trace_snapshot_binding_ddl;\nEXECUTE kfs_0015_answer_trace_snapshot_binding_statement;\nDEALLOCATE PREPARE kfs_0015_answer_trace_snapshot_binding_statement;\n\nSET @kfs_0015_answer_trace_snapshot_fk_exists = (\n SELECT COUNT(*)\n FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'answer_traces'\n AND constraint_name = 'answer_traces_permission_snapshot_fk'\n);\nSET @kfs_0015_answer_trace_snapshot_fk_ddl = IF(\n @kfs_0015_answer_trace_snapshot_fk_exists = 0,\n 'ALTER TABLE `answer_traces` ADD CONSTRAINT `answer_traces_permission_snapshot_fk` FOREIGN KEY (`knowledge_space_id`, `permission_snapshot_id`, `subject_id`, `access_channel`) REFERENCES `knowledge_space_permission_snapshots` (`knowledge_space_id`, `id`, `subject_id`, `access_channel`)',\n 'DO 0'\n);\nPREPARE kfs_0015_answer_trace_snapshot_fk_statement\n FROM @kfs_0015_answer_trace_snapshot_fk_ddl;\nEXECUTE kfs_0015_answer_trace_snapshot_fk_statement;\nDEALLOCATE PREPARE kfs_0015_answer_trace_snapshot_fk_statement;\nCREATE INDEX IF NOT EXISTS `answer_traces_space_subject_created_idx`\n ON `answer_traces` (`knowledge_space_id`, `subject_id`, `created_at`, `id`);\n", path: "packages/database/migrations/0015_research_task_jobs.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0016_compilation_job_requester_binding\n-- Dialect: postgres\n\n-- Public compilation-job control is bound to the exact durable permission provenance. NULL across\n-- the full binding denotes legacy/internal attempts, which public handlers treat as inaccessible.\nALTER TABLE \"document_compilation_attempts\"\n ADD COLUMN IF NOT EXISTS \"requested_by_subject_id\" VARCHAR(255),\n ADD COLUMN IF NOT EXISTS \"permission_snapshot_id\" UUID,\n ADD COLUMN IF NOT EXISTS \"permission_snapshot_revision\" INTEGER,\n ADD COLUMN IF NOT EXISTS \"access_channel\" VARCHAR(16);\n\n-- The ledger marker is written after this artifact. Guard every incremental constraint so a\n-- process exit after PostgreSQL commits DDL can safely replay the complete migration.\nDO $kfs_0016_compilation_permission_binding_ck$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM \"pg_constraint\"\n WHERE \"conname\" = 'document_compilation_attempts_permission_binding_ck'\n AND \"conrelid\" = 'document_compilation_attempts'::regclass\n ) THEN\n ALTER TABLE \"document_compilation_attempts\"\n ADD CONSTRAINT \"document_compilation_attempts_permission_binding_ck\"\n CHECK (\n (\n \"requested_by_subject_id\" IS NULL\n AND \"permission_snapshot_id\" IS NULL\n AND \"permission_snapshot_revision\" IS NULL\n AND \"access_channel\" IS NULL\n )\n OR (\n \"requested_by_subject_id\" IS NOT NULL\n AND \"permission_snapshot_id\" IS NOT NULL\n AND \"permission_snapshot_revision\" >= 1\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')\n )\n );\n END IF;\nEND\n$kfs_0016_compilation_permission_binding_ck$;\n\nDO $kfs_0016_compilation_permission_snapshot_fk$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM \"pg_constraint\"\n WHERE \"conname\" = 'document_compilation_attempts_permission_snapshot_fk'\n AND \"conrelid\" = 'document_compilation_attempts'::regclass\n ) THEN\n ALTER TABLE \"document_compilation_attempts\"\n ADD CONSTRAINT \"document_compilation_attempts_permission_snapshot_fk\"\n FOREIGN KEY (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"permission_snapshot_id\",\n \"requested_by_subject_id\",\n \"access_channel\"\n ) REFERENCES \"knowledge_space_permission_snapshots\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"id\",\n \"subject_id\",\n \"access_channel\"\n ) ON DELETE RESTRICT;\n END IF;\nEND\n$kfs_0016_compilation_permission_snapshot_fk$;\n", path: "packages/database/migrations/0016_compilation_job_requester_binding.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0016_compilation_job_requester_binding\n-- Dialect: tidb\n\n-- Public compilation-job control is bound to the exact durable permission provenance. NULL across\n-- the full binding denotes legacy/internal attempts, which public handlers treat as inaccessible.\nALTER TABLE `document_compilation_attempts`\n ADD COLUMN IF NOT EXISTS `requested_by_subject_id` VARCHAR(255);\nALTER TABLE `document_compilation_attempts`\n ADD COLUMN IF NOT EXISTS `permission_snapshot_id` CHAR(36);\nALTER TABLE `document_compilation_attempts`\n ADD COLUMN IF NOT EXISTS `permission_snapshot_revision` INT;\nALTER TABLE `document_compilation_attempts`\n ADD COLUMN IF NOT EXISTS `access_channel` VARCHAR(16);\n\n-- TiDB exposes CHECK constraints and foreign keys through distinct information_schema views.\n-- Select the exact ALTER or a no-op so marker-loss replay never duplicates committed DDL.\nSET @kfs_0016_compilation_permission_binding_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_permission_binding_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `document_compilation_attempts` ADD CONSTRAINT `document_compilation_attempts_permission_binding_ck` CHECK ((`requested_by_subject_id` IS NULL AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL AND `access_channel` IS NULL) OR (`requested_by_subject_id` IS NOT NULL AND `permission_snapshot_id` IS NOT NULL AND `permission_snapshot_revision` >= 1 AND `access_channel` IN (''interactive'', ''service_api'', ''mcp'', ''agent'')))'\n);\nPREPARE kfs_0016_compilation_permission_binding_stmt\n FROM @kfs_0016_compilation_permission_binding_sql;\nEXECUTE kfs_0016_compilation_permission_binding_stmt;\nDEALLOCATE PREPARE kfs_0016_compilation_permission_binding_stmt;\n\nSET @kfs_0016_compilation_permission_snapshot_fk_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_permission_snapshot_fk'\n ),\n 'DO 0',\n 'ALTER TABLE `document_compilation_attempts` ADD CONSTRAINT `document_compilation_attempts_permission_snapshot_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `permission_snapshot_id`, `requested_by_subject_id`, `access_channel`) REFERENCES `knowledge_space_permission_snapshots` (`tenant_id`, `knowledge_space_id`, `id`, `subject_id`, `access_channel`)'\n);\nPREPARE kfs_0016_compilation_permission_snapshot_fk_stmt\n FROM @kfs_0016_compilation_permission_snapshot_fk_sql;\nEXECUTE kfs_0016_compilation_permission_snapshot_fk_stmt;\nDEALLOCATE PREPARE kfs_0016_compilation_permission_snapshot_fk_stmt;\n", path: "packages/database/migrations/0016_compilation_job_requester_binding.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0017_durable_deletion\n-- Dialect: postgres\n\n-- Resource rows remain present while deletion is in progress. Defaults preserve rolling-upgrade\n-- compatibility for old writers; the application only enables the deletion endpoint after all\n-- writers understand these fences.\nALTER TABLE \"knowledge_spaces\"\n ADD COLUMN IF NOT EXISTS \"revision\" INTEGER NOT NULL DEFAULT 1,\n ADD COLUMN IF NOT EXISTS \"lifecycle_state\" VARCHAR(16) NOT NULL DEFAULT 'active',\n ADD COLUMN IF NOT EXISTS \"deletion_job_id\" UUID,\n ADD COLUMN IF NOT EXISTS \"deleting_at\" TIMESTAMPTZ;\n\nALTER TABLE \"sources\"\n ADD COLUMN IF NOT EXISTS \"deletion_job_id\" UUID,\n ADD COLUMN IF NOT EXISTS \"deleting_at\" TIMESTAMPTZ;\n\nALTER TABLE \"document_assets\"\n ADD COLUMN IF NOT EXISTS \"lifecycle_state\" VARCHAR(16) NOT NULL DEFAULT 'active',\n ADD COLUMN IF NOT EXISTS \"deletion_job_id\" UUID,\n ADD COLUMN IF NOT EXISTS \"deleting_at\" TIMESTAMPTZ,\n ADD COLUMN IF NOT EXISTS \"row_version\" INTEGER NOT NULL DEFAULT 1;\n\nALTER TABLE \"knowledge_space_mutation_leases\"\n ADD COLUMN IF NOT EXISTS \"lease_token\" UUID,\n ADD COLUMN IF NOT EXISTS \"heartbeat_at\" TIMESTAMPTZ,\n ADD COLUMN IF NOT EXISTS \"expires_at\" TIMESTAMPTZ;\n-- Rows created by the pre-0017 ownerless lease protocol are made immediately reclaimable. A live\n-- writer remains fenced by the resource tombstone even if it returns after this compatibility cut.\nUPDATE \"knowledge_space_mutation_leases\"\nSET \"lease_token\" = \"id\",\n \"heartbeat_at\" = \"acquired_at\",\n \"expires_at\" = \"acquired_at\"\nWHERE \"lease_token\" IS NULL OR \"heartbeat_at\" IS NULL OR \"expires_at\" IS NULL;\n\n-- The catalog models manifests as tenant-owned rows. The original schema constrained only the\n-- UUID, so fail closed on historical cross-tenant rows before installing the composite invariant.\n-- Add the stronger FK before removing the legacy one so marker-loss replay never leaves a gap.\nDO $kfs_0017_knowledge_space_manifests_space_fk$\nBEGIN\n IF EXISTS (\n SELECT 1\n FROM \"knowledge_space_manifests\" AS manifest\n LEFT JOIN \"knowledge_spaces\" AS space\n ON space.\"tenant_id\" = manifest.\"tenant_id\"\n AND space.\"id\" = manifest.\"knowledge_space_id\"\n WHERE space.\"id\" IS NULL\n ) THEN\n RAISE EXCEPTION\n 'knowledge_space_manifests contains a tenant/space ownership mismatch';\n END IF;\n\n IF NOT EXISTS (\n SELECT 1 FROM \"pg_constraint\"\n WHERE \"conrelid\" = 'knowledge_space_manifests'::regclass\n AND \"conname\" = 'knowledge_space_manifests_space_fk'\n ) THEN\n ALTER TABLE \"knowledge_space_manifests\"\n ADD CONSTRAINT \"knowledge_space_manifests_space_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE;\n END IF;\n\n ALTER TABLE \"knowledge_space_manifests\"\n DROP CONSTRAINT IF EXISTS \"knowledge_space_manifests_knowledge_space_id_fkey\";\nEND\n$kfs_0017_knowledge_space_manifests_space_fk$;\n\n-- Evidence bundles were originally ownerless. Keep the columns nullable for a rolling writer\n-- upgrade, backfill only when every durable reference agrees on one tenant/space, and quarantine\n-- ambiguous rows by leaving them NULL. Application reads fail closed and rollout readiness\n-- requires the NULL population to be purged before durable deletion is enabled.\nALTER TABLE \"evidence_bundles\"\n ADD COLUMN IF NOT EXISTS \"tenant_id\" VARCHAR(255),\n ADD COLUMN IF NOT EXISTS \"knowledge_space_id\" UUID;\n\nWITH evidence_scope_candidates AS (\n SELECT evidence.\"id\" AS bundle_id,\n space.\"tenant_id\" AS tenant_id,\n trace.\"knowledge_space_id\" AS knowledge_space_id\n FROM \"evidence_bundles\" AS evidence\n INNER JOIN \"answer_traces\" AS trace\n ON trace.\"evidence_bundle_id\" = evidence.\"id\" OR trace.\"id\" = evidence.\"trace_id\"\n INNER JOIN \"knowledge_spaces\" AS space\n ON space.\"id\" = trace.\"knowledge_space_id\"\n UNION ALL\n SELECT evidence.\"id\" AS bundle_id,\n partial.\"tenant_id\" AS tenant_id,\n partial.\"knowledge_space_id\" AS knowledge_space_id\n FROM \"evidence_bundles\" AS evidence\n INNER JOIN \"research_task_partial_results\" AS partial\n ON CASE\n WHEN jsonb_typeof(partial.\"evidence_bundle\") = 'object'\n THEN partial.\"evidence_bundle\" ->> 'id'\n ELSE NULL\n END = CAST(evidence.\"id\" AS TEXT)\n INNER JOIN \"knowledge_spaces\" AS space\n ON space.\"tenant_id\" = partial.\"tenant_id\"\n AND space.\"id\" = partial.\"knowledge_space_id\"\n), distinct_candidate_scopes AS (\n SELECT DISTINCT bundle_id, tenant_id, knowledge_space_id\n FROM evidence_scope_candidates\n), unambiguous_evidence_scopes AS (\n SELECT bundle_id,\n MIN(tenant_id) AS tenant_id,\n MIN(CAST(knowledge_space_id AS TEXT))::UUID AS knowledge_space_id\n FROM distinct_candidate_scopes\n GROUP BY bundle_id\n HAVING COUNT(*) = 1\n)\nUPDATE \"evidence_bundles\" AS evidence\nSET \"tenant_id\" = resolved.\"tenant_id\",\n \"knowledge_space_id\" = resolved.\"knowledge_space_id\"\nFROM unambiguous_evidence_scopes AS resolved\nWHERE evidence.\"id\" = resolved.bundle_id\n AND evidence.\"tenant_id\" IS NULL\n AND evidence.\"knowledge_space_id\" IS NULL;\n\nDO $kfs_0017_evidence_bundles_scope_pair_ck$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM \"pg_constraint\"\n WHERE \"conrelid\" = 'evidence_bundles'::regclass\n AND \"conname\" = 'evidence_bundles_scope_pair_ck'\n ) THEN\n ALTER TABLE \"evidence_bundles\"\n ADD CONSTRAINT \"evidence_bundles_scope_pair_ck\" CHECK (\n (\"tenant_id\" IS NULL AND \"knowledge_space_id\" IS NULL)\n OR (\"tenant_id\" IS NOT NULL AND \"knowledge_space_id\" IS NOT NULL)\n );\n END IF;\nEND\n$kfs_0017_evidence_bundles_scope_pair_ck$;\n\nDO $kfs_0017_evidence_bundles_scope_fk$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM \"pg_constraint\"\n WHERE \"conrelid\" = 'evidence_bundles'::regclass\n AND \"conname\" = 'evidence_bundles_scope_fk'\n ) THEN\n ALTER TABLE \"evidence_bundles\"\n ADD CONSTRAINT \"evidence_bundles_scope_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE;\n END IF;\nEND\n$kfs_0017_evidence_bundles_scope_fk$;\n\nCREATE INDEX IF NOT EXISTS \"evidence_bundles_scope_created_idx\"\n ON \"evidence_bundles\" (\"tenant_id\", \"knowledge_space_id\", \"created_at\", \"id\");\n\nDO $kfs_0017_knowledge_spaces_deletion_lifecycle_ck$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM \"pg_constraint\"\n WHERE \"conrelid\" = 'knowledge_spaces'::regclass\n AND \"conname\" = 'knowledge_spaces_deletion_lifecycle_ck'\n ) THEN\n ALTER TABLE \"knowledge_spaces\"\n ADD CONSTRAINT \"knowledge_spaces_deletion_lifecycle_ck\" CHECK (\n \"revision\" >= 1\n AND (\n (\"lifecycle_state\" = 'active' AND \"deletion_job_id\" IS NULL AND \"deleting_at\" IS NULL)\n OR\n (\"lifecycle_state\" = 'deleting' AND \"deletion_job_id\" IS NOT NULL AND \"deleting_at\" IS NOT NULL)\n )\n );\n END IF;\nEND\n$kfs_0017_knowledge_spaces_deletion_lifecycle_ck$;\n\nDO $kfs_0017_sources_deletion_lifecycle_ck$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM \"pg_constraint\"\n WHERE \"conrelid\" = 'sources'::regclass\n AND \"conname\" = 'sources_deletion_lifecycle_ck'\n ) THEN\n ALTER TABLE \"sources\"\n ADD CONSTRAINT \"sources_deletion_lifecycle_ck\" CHECK (\n (\n \"status\" = 'deleting'\n AND \"deletion_job_id\" IS NOT NULL\n AND \"deleting_at\" IS NOT NULL\n )\n OR (\n \"status\" <> 'deleting'\n AND \"deletion_job_id\" IS NULL\n AND \"deleting_at\" IS NULL\n )\n );\n END IF;\nEND\n$kfs_0017_sources_deletion_lifecycle_ck$;\n\nDO $kfs_0017_document_assets_deletion_lifecycle_ck$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM \"pg_constraint\"\n WHERE \"conrelid\" = 'document_assets'::regclass\n AND \"conname\" = 'document_assets_deletion_lifecycle_ck'\n ) THEN\n ALTER TABLE \"document_assets\"\n ADD CONSTRAINT \"document_assets_deletion_lifecycle_ck\" CHECK (\n \"row_version\" >= 1\n AND (\n (\"lifecycle_state\" = 'active' AND \"deletion_job_id\" IS NULL AND \"deleting_at\" IS NULL)\n OR\n (\"lifecycle_state\" = 'deleting' AND \"deletion_job_id\" IS NOT NULL AND \"deleting_at\" IS NOT NULL)\n )\n );\n END IF;\nEND\n$kfs_0017_document_assets_deletion_lifecycle_ck$;\n\n-- Retrieval execution leases are durable, token-fenced evidence that a request may still be\n-- reading the space. Deletion drains active leases before destructive cleanup and can reclaim\n-- only rows whose heartbeat has expired.\nCREATE TABLE IF NOT EXISTS \"retrieval_execution_leases\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"subject_id\" TEXT NOT NULL,\n \"trace_id\" UUID NOT NULL,\n \"lease_token\" VARCHAR(128) NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"row_version\" INTEGER NOT NULL,\n \"acquired_at\" TIMESTAMPTZ NOT NULL,\n \"heartbeat_at\" TIMESTAMPTZ NOT NULL,\n \"expires_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"retrieval_execution_leases_state_ck\" CHECK (\n \"status\" IN ('active', 'released', 'expired')\n AND \"row_version\" >= 0\n AND \"heartbeat_at\" >= \"acquired_at\"\n AND \"expires_at\" > \"heartbeat_at\"\n AND \"updated_at\" >= \"acquired_at\"\n ),\n CONSTRAINT \"retrieval_execution_leases_space_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS \"retrieval_execution_leases_space_expiry_idx\"\n ON \"retrieval_execution_leases\" (\n \"tenant_id\", \"knowledge_space_id\", \"status\", \"expires_at\", \"id\"\n );\n\n-- Jobs intentionally have no FK to any resource or permission snapshot that they delete. The\n-- copied tenant/scope/requester fields are a durable authorization and audit record.\nCREATE TABLE IF NOT EXISTS \"deletion_jobs\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"target_type\" VARCHAR(32) NOT NULL,\n \"target_id\" UUID NOT NULL,\n \"target_revision\" INTEGER NOT NULL,\n \"delete_mode\" VARCHAR(16) NOT NULL,\n \"requested_by_subject_id\" VARCHAR(255) NOT NULL,\n \"permission_snapshot_id\" UUID NOT NULL,\n \"permission_snapshot_revision\" INTEGER NOT NULL,\n \"access_channel\" VARCHAR(16) NOT NULL,\n \"api_key_id\" UUID,\n \"api_key_revision\" INTEGER,\n \"api_key_expires_at\" TIMESTAMPTZ,\n \"idempotency_key\" VARCHAR(512) NOT NULL,\n \"request_fingerprint\" CHAR(64) NOT NULL,\n \"name_challenge_digest\" CHAR(64),\n \"checkpoint\" VARCHAR(32) NOT NULL,\n \"scan_phase\" VARCHAR(64),\n \"scan_cursor\" VARCHAR(1024),\n \"inventory_complete\" BOOLEAN NOT NULL,\n \"run_state\" VARCHAR(16) NOT NULL,\n \"active_slot\" INTEGER,\n \"execution_attempts\" INTEGER NOT NULL,\n \"max_execution_attempts\" INTEGER NOT NULL,\n \"retry_at\" TIMESTAMPTZ,\n \"worker_id\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"heartbeat_at\" TIMESTAMPTZ,\n \"queue_job_id\" VARCHAR(255),\n \"last_error_code\" VARCHAR(64),\n \"last_error_message\" TEXT,\n \"row_version\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"started_at\" TIMESTAMPTZ,\n \"completed_at\" TIMESTAMPTZ,\n CONSTRAINT \"deletion_jobs_target_ck\" CHECK (\n \"target_type\" IN ('knowledge_space', 'source', 'document_asset')\n AND (\n (\"target_type\" = 'source' AND \"delete_mode\" IN ('keep', 'cascade') AND \"name_challenge_digest\" IS NULL)\n OR\n (\"target_type\" = 'knowledge_space' AND \"delete_mode\" = 'cascade' AND \"name_challenge_digest\" IS NOT NULL)\n OR\n (\"target_type\" = 'document_asset' AND \"delete_mode\" = 'cascade' AND \"name_challenge_digest\" IS NULL)\n )\n ),\n CONSTRAINT \"deletion_jobs_checkpoint_ck\" CHECK (\n \"checkpoint\" IN (\n 'requested', 'quiescing', 'deleting_objects', 'deleting_derived_data',\n 'deleting_primary_data', 'completed'\n )\n ),\n CONSTRAINT \"deletion_jobs_run_state_ck\" CHECK (\n \"run_state\" IN ('dispatch_pending', 'queued', 'running', 'retry_wait', 'succeeded', 'failed', 'canceled')\n ),\n CONSTRAINT \"deletion_jobs_access_channel_ck\" CHECK (\n \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')\n ),\n CONSTRAINT \"deletion_jobs_api_key_binding_ck\" CHECK (\n (\n \"api_key_id\" IS NULL\n AND \"api_key_revision\" IS NULL\n AND \"api_key_expires_at\" IS NULL\n )\n OR (\n \"api_key_id\" IS NOT NULL\n AND \"api_key_revision\" >= 1\n AND \"access_channel\" = 'service_api'\n )\n ),\n CONSTRAINT \"deletion_jobs_positive_ck\" CHECK (\n \"target_revision\" >= 1\n AND \"permission_snapshot_revision\" >= 1\n AND \"row_version\" >= 1\n AND \"execution_attempts\" >= 0\n AND \"max_execution_attempts\" >= 1\n AND \"execution_attempts\" <= \"max_execution_attempts\"\n AND (\"active_slot\" IS NULL OR \"active_slot\" = 1)\n ),\n CONSTRAINT \"deletion_jobs_lifecycle_ck\" CHECK (\n (\n \"run_state\" IN ('dispatch_pending', 'queued', 'running', 'retry_wait', 'failed')\n AND \"active_slot\" = 1\n AND \"completed_at\" IS NULL\n )\n OR (\n \"run_state\" IN ('succeeded', 'canceled')\n AND \"active_slot\" IS NULL\n AND \"completed_at\" IS NOT NULL\n )\n ),\n CONSTRAINT \"deletion_jobs_completion_ck\" CHECK (\n (\"run_state\" = 'succeeded' AND \"checkpoint\" = 'completed')\n OR (\"run_state\" <> 'succeeded' AND \"checkpoint\" <> 'completed')\n ),\n CONSTRAINT \"deletion_jobs_retry_ck\" CHECK (\n (\"run_state\" = 'retry_wait' AND \"retry_at\" IS NOT NULL)\n OR (\"run_state\" <> 'retry_wait' AND \"retry_at\" IS NULL)\n ),\n CONSTRAINT \"deletion_jobs_lease_ck\" CHECK (\n (\n \"run_state\" = 'running'\n AND \"worker_id\" IS NOT NULL\n AND \"lease_token\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL\n AND \"heartbeat_at\" IS NOT NULL\n )\n OR (\n \"run_state\" <> 'running'\n AND \"worker_id\" IS NULL\n AND \"lease_token\" IS NULL\n AND \"lease_expires_at\" IS NULL\n AND \"heartbeat_at\" IS NULL\n )\n )\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"deletion_jobs_idempotency_uq\"\n ON \"deletion_jobs\" (\"tenant_id\", \"idempotency_key\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"deletion_jobs_target_active_uq\"\n ON \"deletion_jobs\" (\n \"tenant_id\", \"knowledge_space_id\", \"target_type\", \"target_id\", \"active_slot\"\n );\nCREATE INDEX IF NOT EXISTS \"deletion_jobs_claim_idx\"\n ON \"deletion_jobs\" (\"run_state\", \"retry_at\", \"lease_expires_at\", \"created_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"deletion_jobs_scope_history_idx\"\n ON \"deletion_jobs\" (\"tenant_id\", \"knowledge_space_id\", \"created_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"deletion_jobs_requester_provenance_idx\"\n ON \"deletion_jobs\" (\n \"tenant_id\", \"knowledge_space_id\", \"requested_by_subject_id\",\n \"api_key_id\", \"api_key_revision\", \"created_at\", \"id\"\n );\n\n-- Tombstones have no FK, including no FK back to the job, so the permanent no-republish fence\n-- survives resource deletion and independent job retention.\nCREATE TABLE IF NOT EXISTS \"deletion_tombstones\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"deletion_job_id\" UUID NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"target_type\" VARCHAR(32) NOT NULL,\n \"target_id\" UUID NOT NULL,\n \"target_revision\" INTEGER NOT NULL,\n \"state\" VARCHAR(16) NOT NULL,\n \"row_version\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"completed_at\" TIMESTAMPTZ,\n CONSTRAINT \"deletion_tombstones_target_ck\" CHECK (\n \"target_type\" IN ('knowledge_space', 'source', 'document_asset')\n ),\n CONSTRAINT \"deletion_tombstones_state_ck\" CHECK (\n (\"state\" = 'active' AND \"completed_at\" IS NULL)\n OR (\"state\" = 'completed' AND \"completed_at\" IS NOT NULL)\n ),\n CONSTRAINT \"deletion_tombstones_positive_ck\" CHECK (\n \"target_revision\" >= 1 AND \"row_version\" >= 1\n )\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"deletion_tombstones_target_uq\"\n ON \"deletion_tombstones\" (\"tenant_id\", \"target_type\", \"target_id\");\nCREATE INDEX IF NOT EXISTS \"deletion_tombstones_space_target_idx\"\n ON \"deletion_tombstones\" (\"tenant_id\", \"knowledge_space_id\", \"target_type\", \"target_id\");\nCREATE INDEX IF NOT EXISTS \"deletion_tombstones_job_idx\"\n ON \"deletion_tombstones\" (\"deletion_job_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"deletion_job_items\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"deletion_job_id\" UUID NOT NULL,\n \"ordinal\" BIGINT NOT NULL,\n \"kind\" VARCHAR(32) NOT NULL,\n \"resource_id\" UUID,\n \"object_key\" TEXT,\n \"credential_ref\" TEXT,\n \"cache_key\" TEXT,\n \"payload_digest\" CHAR(64) NOT NULL,\n \"idempotency_key\" VARCHAR(512) NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"attempts\" INTEGER NOT NULL,\n \"max_attempts\" INTEGER NOT NULL,\n \"next_attempt_at\" TIMESTAMPTZ,\n \"last_error_code\" VARCHAR(64),\n \"last_error_message\" TEXT,\n \"row_version\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"completed_at\" TIMESTAMPTZ,\n \"redacted_at\" TIMESTAMPTZ,\n CONSTRAINT \"deletion_job_items_kind_ck\" CHECK (\n \"kind\" IN ('object', 'secret_ref', 'cache_key', 'document_cascade', 'document_detach')\n ),\n CONSTRAINT \"deletion_job_items_status_ck\" CHECK (\n \"status\" IN ('pending', 'retry_wait', 'completed', 'dead')\n ),\n CONSTRAINT \"deletion_job_items_positive_ck\" CHECK (\n \"ordinal\" >= 0 AND \"attempts\" >= 0 AND \"max_attempts\" >= 1\n AND \"attempts\" <= \"max_attempts\" AND \"row_version\" >= 1\n ),\n CONSTRAINT \"deletion_job_items_retry_ck\" CHECK (\n (\"status\" = 'retry_wait' AND \"next_attempt_at\" IS NOT NULL)\n OR (\"status\" <> 'retry_wait' AND \"next_attempt_at\" IS NULL)\n ),\n CONSTRAINT \"deletion_job_items_terminal_ck\" CHECK (\n (\"status\" IN ('completed', 'dead') AND \"completed_at\" IS NOT NULL)\n OR (\"status\" IN ('pending', 'retry_wait') AND \"completed_at\" IS NULL)\n ),\n CONSTRAINT \"deletion_job_items_payload_ck\" CHECK (\n (\n \"kind\" = 'object' AND \"credential_ref\" IS NULL AND \"cache_key\" IS NULL\n AND (\n (\"status\" = 'completed' AND \"object_key\" IS NULL AND \"redacted_at\" IS NOT NULL)\n OR (\"status\" <> 'completed' AND \"object_key\" IS NOT NULL AND \"redacted_at\" IS NULL)\n )\n )\n OR (\n \"kind\" = 'secret_ref' AND \"object_key\" IS NULL AND \"cache_key\" IS NULL\n AND (\n (\"status\" = 'completed' AND \"credential_ref\" IS NULL AND \"redacted_at\" IS NOT NULL)\n OR (\"status\" <> 'completed' AND \"credential_ref\" IS NOT NULL AND \"redacted_at\" IS NULL)\n )\n )\n OR (\n \"kind\" = 'cache_key' AND \"object_key\" IS NULL AND \"credential_ref\" IS NULL\n AND (\n (\"status\" = 'completed' AND \"cache_key\" IS NULL AND \"redacted_at\" IS NOT NULL)\n OR (\"status\" <> 'completed' AND \"cache_key\" IS NOT NULL AND \"redacted_at\" IS NULL)\n )\n )\n OR (\n \"kind\" IN ('document_cascade', 'document_detach')\n AND \"resource_id\" IS NOT NULL AND \"object_key\" IS NULL\n AND \"credential_ref\" IS NULL AND \"cache_key\" IS NULL AND \"redacted_at\" IS NULL\n )\n ),\n FOREIGN KEY (\"deletion_job_id\") REFERENCES \"deletion_jobs\" (\"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"deletion_job_items_idempotency_uq\"\n ON \"deletion_job_items\" (\"deletion_job_id\", \"idempotency_key\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"deletion_job_items_ordinal_uq\"\n ON \"deletion_job_items\" (\"deletion_job_id\", \"ordinal\");\nCREATE INDEX IF NOT EXISTS \"deletion_job_items_work_idx\"\n ON \"deletion_job_items\" (\n \"deletion_job_id\", \"status\", \"next_attempt_at\", \"ordinal\", \"id\"\n );\nCREATE INDEX IF NOT EXISTS \"deletion_job_items_resource_idx\"\n ON \"deletion_job_items\" (\"deletion_job_id\", \"kind\", \"resource_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"deletion_outbox\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"deletion_job_id\" UUID NOT NULL,\n \"delivery_revision\" INTEGER NOT NULL,\n \"event_type\" VARCHAR(32) NOT NULL,\n \"schema_version\" INTEGER NOT NULL,\n \"idempotency_key\" VARCHAR(512) NOT NULL,\n \"request_idempotency_key\" VARCHAR(512) NOT NULL,\n \"request_fingerprint\" CHAR(64) NOT NULL,\n \"payload\" JSONB NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"available_at\" TIMESTAMPTZ NOT NULL,\n \"dispatch_attempts\" INTEGER NOT NULL,\n \"locked_by\" VARCHAR(255),\n \"locked_until\" TIMESTAMPTZ,\n \"lock_token\" UUID,\n \"queue_job_id\" VARCHAR(255),\n \"last_error\" TEXT,\n \"delivered_at\" TIMESTAMPTZ,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"deletion_outbox_event_ck\" CHECK (\"event_type\" = 'deletion.job'),\n CONSTRAINT \"deletion_outbox_schema_ck\" CHECK (\"schema_version\" = 1),\n CONSTRAINT \"deletion_outbox_status_ck\" CHECK (\n \"status\" IN ('pending', 'dispatching', 'dispatched', 'leased', 'completed', 'canceled', 'dead')\n ),\n CONSTRAINT \"deletion_outbox_positive_ck\" CHECK (\n \"delivery_revision\" >= 1 AND \"dispatch_attempts\" >= 0\n ),\n CONSTRAINT \"deletion_outbox_lock_ck\" CHECK (\n (\"lock_token\" IS NULL AND \"locked_by\" IS NULL AND \"locked_until\" IS NULL)\n OR (\"lock_token\" IS NOT NULL AND \"locked_by\" IS NOT NULL AND \"locked_until\" IS NOT NULL)\n ),\n FOREIGN KEY (\"deletion_job_id\") REFERENCES \"deletion_jobs\" (\"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"deletion_outbox_idempotency_uq\"\n ON \"deletion_outbox\" (\"idempotency_key\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"deletion_outbox_job_delivery_uq\"\n ON \"deletion_outbox\" (\"deletion_job_id\", \"delivery_revision\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"deletion_outbox_job_request_uq\"\n ON \"deletion_outbox\" (\"deletion_job_id\", \"request_idempotency_key\");\nCREATE INDEX IF NOT EXISTS \"deletion_outbox_claim_idx\"\n ON \"deletion_outbox\" (\"status\", \"available_at\", \"locked_until\", \"id\");\n\n-- Every manual retry has immutable actor provenance. This is intentionally separate from\n-- deletion_jobs: an owner rescue must never overwrite the original deletion requester, and there\n-- is deliberately no FK to the permission snapshot or target space because both may be deleted.\nCREATE TABLE IF NOT EXISTS \"deletion_retry_audits\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"deletion_job_id\" UUID NOT NULL,\n \"outbox_id\" UUID NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"retry_authority\" VARCHAR(32) NOT NULL,\n \"actor_subject_id\" VARCHAR(255) NOT NULL,\n \"permission_snapshot_id\" UUID NOT NULL,\n \"permission_snapshot_revision\" INTEGER NOT NULL,\n \"access_channel\" VARCHAR(16) NOT NULL,\n \"api_key_id\" UUID,\n \"api_key_revision\" INTEGER,\n \"api_key_expires_at\" TIMESTAMPTZ,\n \"request_idempotency_key\" VARCHAR(512) NOT NULL,\n \"request_fingerprint\" CHAR(64) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"deletion_retry_audits_authority_ck\" CHECK (\n \"retry_authority\" IN ('original_requester', 'interactive_owner_rescue')\n ),\n CONSTRAINT \"deletion_retry_audits_access_channel_ck\" CHECK (\n \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')\n ),\n CONSTRAINT \"deletion_retry_audits_positive_ck\" CHECK (\n \"permission_snapshot_revision\" >= 1\n ),\n CONSTRAINT \"deletion_retry_audits_api_key_binding_ck\" CHECK (\n (\n \"api_key_id\" IS NULL\n AND \"api_key_revision\" IS NULL\n AND \"api_key_expires_at\" IS NULL\n )\n OR (\n \"api_key_id\" IS NOT NULL\n AND \"api_key_revision\" >= 1\n AND \"access_channel\" = 'service_api'\n )\n ),\n CONSTRAINT \"deletion_retry_audits_owner_rescue_ck\" CHECK (\n \"retry_authority\" <> 'interactive_owner_rescue'\n OR (\n \"access_channel\" = 'interactive'\n AND \"api_key_id\" IS NULL\n AND \"api_key_revision\" IS NULL\n AND \"api_key_expires_at\" IS NULL\n )\n ),\n FOREIGN KEY (\"deletion_job_id\") REFERENCES \"deletion_jobs\" (\"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"deletion_retry_audits_job_request_uq\"\n ON \"deletion_retry_audits\" (\"deletion_job_id\", \"request_idempotency_key\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"deletion_retry_audits_outbox_uq\"\n ON \"deletion_retry_audits\" (\"outbox_id\");\nCREATE INDEX IF NOT EXISTS \"deletion_retry_audits_actor_idx\"\n ON \"deletion_retry_audits\" (\n \"tenant_id\", \"knowledge_space_id\", \"actor_subject_id\", \"created_at\", \"id\"\n );\n\nCREATE INDEX IF NOT EXISTS \"knowledge_spaces_lifecycle_idx\"\n ON \"knowledge_spaces\" (\"tenant_id\", \"lifecycle_state\", \"updated_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"sources_deletion_job_idx\"\n ON \"sources\" (\"knowledge_space_id\", \"deletion_job_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"document_assets_lifecycle_idx\"\n ON \"document_assets\" (\"knowledge_space_id\", \"lifecycle_state\", \"source_id\", \"version\", \"id\");\nCREATE INDEX IF NOT EXISTS \"page_index_manifests_document_idx\"\n ON \"page_index_manifests\" (\"document_asset_id\", \"publication_generation_id\", \"id\");\n\n-- Agent workspace snapshots contain command logs and opaque metadata that cannot be attributed to\n-- one document after the fact. Persist exact creator authorization and support whole-space\n-- invalidation so every API replica observes deletion immediately.\nCREATE TABLE IF NOT EXISTS \"agent_workspace_snapshots\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"subject_id\" VARCHAR(255) NOT NULL,\n \"access_channel\" VARCHAR(16) NOT NULL,\n \"permission_snapshot_id\" UUID NOT NULL,\n \"permission_snapshot_revision\" INTEGER NOT NULL,\n \"permission_scopes\" JSONB NOT NULL,\n \"fingerprint\" VARCHAR(80) NOT NULL,\n \"payload\" JSONB NOT NULL,\n \"invalidated_at\" TIMESTAMPTZ,\n \"invalidation_reason\" VARCHAR(64),\n \"created_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"agent_workspace_snapshots_channel_ck\" CHECK (\n \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')\n ),\n CONSTRAINT \"agent_workspace_snapshots_revision_ck\" CHECK (\n \"permission_snapshot_revision\" >= 1\n ),\n CONSTRAINT \"agent_workspace_snapshots_invalidation_ck\" CHECK (\n (\"invalidated_at\" IS NULL AND \"invalidation_reason\" IS NULL)\n OR (\"invalidated_at\" IS NOT NULL AND \"invalidation_reason\" IS NOT NULL)\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS \"agent_workspace_snapshots_tenant_lookup_idx\"\n ON \"agent_workspace_snapshots\" (\"tenant_id\", \"id\", \"invalidated_at\");\nCREATE INDEX IF NOT EXISTS \"agent_workspace_snapshots_space_cleanup_idx\"\n ON \"agent_workspace_snapshots\" (\n \"tenant_id\", \"knowledge_space_id\", \"invalidated_at\", \"id\"\n );\n", path: "packages/database/migrations/0017_durable_deletion.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0017_durable_deletion\n-- Dialect: tidb\n\nALTER TABLE `knowledge_spaces`\n ADD COLUMN IF NOT EXISTS `revision` INT NOT NULL DEFAULT 1;\nALTER TABLE `knowledge_spaces`\n ADD COLUMN IF NOT EXISTS `lifecycle_state` VARCHAR(16) NOT NULL DEFAULT 'active';\nALTER TABLE `knowledge_spaces`\n ADD COLUMN IF NOT EXISTS `deletion_job_id` CHAR(36);\nALTER TABLE `knowledge_spaces`\n ADD COLUMN IF NOT EXISTS `deleting_at` DATETIME(3);\n\nALTER TABLE `sources`\n ADD COLUMN IF NOT EXISTS `deletion_job_id` CHAR(36);\nALTER TABLE `sources`\n ADD COLUMN IF NOT EXISTS `deleting_at` DATETIME(3);\n\nALTER TABLE `document_assets`\n ADD COLUMN IF NOT EXISTS `lifecycle_state` VARCHAR(16) NOT NULL DEFAULT 'active';\nALTER TABLE `document_assets`\n ADD COLUMN IF NOT EXISTS `deletion_job_id` CHAR(36);\nALTER TABLE `document_assets`\n ADD COLUMN IF NOT EXISTS `deleting_at` DATETIME(3);\nALTER TABLE `document_assets`\n ADD COLUMN IF NOT EXISTS `row_version` INT NOT NULL DEFAULT 1;\n\nALTER TABLE `knowledge_space_mutation_leases`\n ADD COLUMN IF NOT EXISTS `lease_token` CHAR(36);\nALTER TABLE `knowledge_space_mutation_leases`\n ADD COLUMN IF NOT EXISTS `heartbeat_at` DATETIME(3);\nALTER TABLE `knowledge_space_mutation_leases`\n ADD COLUMN IF NOT EXISTS `expires_at` DATETIME(3);\nUPDATE `knowledge_space_mutation_leases`\nSET `lease_token` = `id`, `heartbeat_at` = `acquired_at`, `expires_at` = `acquired_at`\nWHERE `lease_token` IS NULL OR `heartbeat_at` IS NULL OR `expires_at` IS NULL;\n\n-- Fail closed rather than silently rewriting a historical cross-tenant manifest. A temporary\n-- NOT NULL guard is used because TiDB does not support a portable top-level conditional SIGNAL.\nDROP TEMPORARY TABLE IF EXISTS `kfs_0017_manifest_space_guard`;\nCREATE TEMPORARY TABLE `kfs_0017_manifest_space_guard` (`valid` TINYINT NOT NULL);\nINSERT INTO `kfs_0017_manifest_space_guard` (`valid`)\nSELECT NULL\nWHERE EXISTS (\n SELECT 1\n FROM `knowledge_space_manifests` AS manifest\n LEFT JOIN `knowledge_spaces` AS space\n ON space.`tenant_id` = manifest.`tenant_id`\n AND space.`id` = manifest.`knowledge_space_id`\n WHERE space.`id` IS NULL\n);\nDROP TEMPORARY TABLE `kfs_0017_manifest_space_guard`;\n\nSET @kfs_0017_manifest_space_fk_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'knowledge_space_manifests'\n AND constraint_name = 'knowledge_space_manifests_space_fk'\n ),\n 'DO 0',\n 'ALTER TABLE `knowledge_space_manifests` ADD CONSTRAINT `knowledge_space_manifests_space_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE'\n);\nPREPARE kfs_0017_manifest_space_fk_stmt FROM @kfs_0017_manifest_space_fk_sql;\nEXECUTE kfs_0017_manifest_space_fk_stmt;\nDEALLOCATE PREPARE kfs_0017_manifest_space_fk_stmt;\n\n-- TiDB assigned the original anonymous FK a generated name. Resolve that exact one-column\n-- relationship from the catalog instead of assuming the generated name is stable across clusters.\nSET @kfs_0017_manifest_legacy_fk_name = (\n SELECT key_usage.constraint_name\n FROM information_schema.key_column_usage AS key_usage\n INNER JOIN information_schema.referential_constraints AS reference_constraint\n ON reference_constraint.constraint_schema = key_usage.constraint_schema\n AND reference_constraint.table_name = key_usage.table_name\n AND reference_constraint.constraint_name = key_usage.constraint_name\n WHERE key_usage.constraint_schema = DATABASE()\n AND key_usage.table_name = 'knowledge_space_manifests'\n AND key_usage.referenced_table_name = 'knowledge_spaces'\n GROUP BY key_usage.constraint_name\n HAVING COUNT(*) = 1\n AND MIN(key_usage.column_name) = 'knowledge_space_id'\n AND MIN(key_usage.referenced_column_name) = 'id'\n LIMIT 1\n);\nSET @kfs_0017_manifest_legacy_fk_sql = IF(\n @kfs_0017_manifest_legacy_fk_name IS NULL,\n 'DO 0',\n CONCAT(\n 'ALTER TABLE `knowledge_space_manifests` DROP FOREIGN KEY `',\n REPLACE(@kfs_0017_manifest_legacy_fk_name, '`', '``'),\n '`'\n )\n);\nPREPARE kfs_0017_manifest_legacy_fk_stmt FROM @kfs_0017_manifest_legacy_fk_sql;\nEXECUTE kfs_0017_manifest_legacy_fk_stmt;\nDEALLOCATE PREPARE kfs_0017_manifest_legacy_fk_stmt;\n\n-- Rolling evidence-bundle scoping. Ambiguous or unowned legacy rows intentionally stay NULL;\n-- application reads quarantine them and rollout readiness requires their bounded purge.\nALTER TABLE `evidence_bundles`\n ADD COLUMN IF NOT EXISTS `tenant_id` VARCHAR(255);\nALTER TABLE `evidence_bundles`\n ADD COLUMN IF NOT EXISTS `knowledge_space_id` CHAR(36);\n\nUPDATE `evidence_bundles` AS evidence\nINNER JOIN (\n SELECT candidate.bundle_id,\n MIN(candidate.tenant_id) AS tenant_id,\n MIN(candidate.knowledge_space_id) AS knowledge_space_id\n FROM (\n SELECT evidence_from_trace.`id` AS bundle_id,\n space_from_trace.`tenant_id` AS tenant_id,\n trace.`knowledge_space_id` AS knowledge_space_id\n FROM `evidence_bundles` AS evidence_from_trace\n INNER JOIN `answer_traces` AS trace\n ON trace.`evidence_bundle_id` = evidence_from_trace.`id`\n OR trace.`id` = evidence_from_trace.`trace_id`\n INNER JOIN `knowledge_spaces` AS space_from_trace\n ON space_from_trace.`id` = trace.`knowledge_space_id`\n UNION ALL\n SELECT evidence_from_partial.`id` AS bundle_id,\n partial.`tenant_id` AS tenant_id,\n partial.`knowledge_space_id` AS knowledge_space_id\n FROM `evidence_bundles` AS evidence_from_partial\n INNER JOIN `research_task_partial_results` AS partial\n ON CASE\n WHEN JSON_TYPE(partial.`evidence_bundle`) = 'OBJECT'\n THEN JSON_UNQUOTE(JSON_EXTRACT(partial.`evidence_bundle`, '$.id'))\n ELSE NULL\n END = CAST(evidence_from_partial.`id` AS CHAR(36))\n INNER JOIN `knowledge_spaces` AS space_from_partial\n ON space_from_partial.`tenant_id` = partial.`tenant_id`\n AND space_from_partial.`id` = partial.`knowledge_space_id`\n ) AS candidate\n GROUP BY candidate.bundle_id\n HAVING COUNT(DISTINCT CONCAT(candidate.tenant_id, CHAR(0), candidate.knowledge_space_id)) = 1\n) AS resolved ON resolved.bundle_id = evidence.`id`\nSET evidence.`tenant_id` = resolved.tenant_id,\n evidence.`knowledge_space_id` = resolved.knowledge_space_id\nWHERE evidence.`tenant_id` IS NULL\n AND evidence.`knowledge_space_id` IS NULL;\n\nSET @kfs_0017_evidence_scope_pair_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'evidence_bundles'\n AND constraint_name = 'evidence_bundles_scope_pair_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `evidence_bundles` ADD CONSTRAINT `evidence_bundles_scope_pair_ck` CHECK ((`tenant_id` IS NULL AND `knowledge_space_id` IS NULL) OR (`tenant_id` IS NOT NULL AND `knowledge_space_id` IS NOT NULL))'\n);\nPREPARE kfs_0017_evidence_scope_pair_stmt FROM @kfs_0017_evidence_scope_pair_sql;\nEXECUTE kfs_0017_evidence_scope_pair_stmt;\nDEALLOCATE PREPARE kfs_0017_evidence_scope_pair_stmt;\n\nSET @kfs_0017_evidence_scope_fk_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'evidence_bundles'\n AND constraint_name = 'evidence_bundles_scope_fk'\n ),\n 'DO 0',\n 'ALTER TABLE `evidence_bundles` ADD CONSTRAINT `evidence_bundles_scope_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE'\n);\nPREPARE kfs_0017_evidence_scope_fk_stmt FROM @kfs_0017_evidence_scope_fk_sql;\nEXECUTE kfs_0017_evidence_scope_fk_stmt;\nDEALLOCATE PREPARE kfs_0017_evidence_scope_fk_stmt;\n\nCREATE INDEX IF NOT EXISTS `evidence_bundles_scope_created_idx`\n ON `evidence_bundles` (`tenant_id`, `knowledge_space_id`, `created_at`, `id`);\n\n-- TiDB commits DDL independently of the migration marker. Select either the exact ALTER or a\n-- no-op so a marker-loss replay cannot duplicate constraints already committed.\nSET @kfs_0017_space_lifecycle_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'knowledge_spaces'\n AND constraint_name = 'knowledge_spaces_deletion_lifecycle_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `knowledge_spaces` ADD CONSTRAINT `knowledge_spaces_deletion_lifecycle_ck` CHECK (`revision` >= 1 AND ((`lifecycle_state` = ''active'' AND `deletion_job_id` IS NULL AND `deleting_at` IS NULL) OR (`lifecycle_state` = ''deleting'' AND `deletion_job_id` IS NOT NULL AND `deleting_at` IS NOT NULL)))'\n);\nPREPARE kfs_0017_space_lifecycle_stmt FROM @kfs_0017_space_lifecycle_sql;\nEXECUTE kfs_0017_space_lifecycle_stmt;\nDEALLOCATE PREPARE kfs_0017_space_lifecycle_stmt;\n\nSET @kfs_0017_source_lifecycle_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'sources'\n AND constraint_name = 'sources_deletion_lifecycle_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `sources` ADD CONSTRAINT `sources_deletion_lifecycle_ck` CHECK ((`status` = ''deleting'' AND `deletion_job_id` IS NOT NULL AND `deleting_at` IS NOT NULL) OR (`status` <> ''deleting'' AND `deletion_job_id` IS NULL AND `deleting_at` IS NULL))'\n);\nPREPARE kfs_0017_source_lifecycle_stmt FROM @kfs_0017_source_lifecycle_sql;\nEXECUTE kfs_0017_source_lifecycle_stmt;\nDEALLOCATE PREPARE kfs_0017_source_lifecycle_stmt;\n\nSET @kfs_0017_document_lifecycle_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'document_assets'\n AND constraint_name = 'document_assets_deletion_lifecycle_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `document_assets` ADD CONSTRAINT `document_assets_deletion_lifecycle_ck` CHECK (`row_version` >= 1 AND ((`lifecycle_state` = ''active'' AND `deletion_job_id` IS NULL AND `deleting_at` IS NULL) OR (`lifecycle_state` = ''deleting'' AND `deletion_job_id` IS NOT NULL AND `deleting_at` IS NOT NULL)))'\n);\nPREPARE kfs_0017_document_lifecycle_stmt FROM @kfs_0017_document_lifecycle_sql;\nEXECUTE kfs_0017_document_lifecycle_stmt;\nDEALLOCATE PREPARE kfs_0017_document_lifecycle_stmt;\n\n-- Retrieval execution leases let durable deletion drain live reads. Skip the parent-table DDL\n-- entirely on replay so TiDB does not revalidate the already-installed foreign key.\nSET @kfs_0017_retrieval_execution_leases_exists = (\n SELECT COUNT(*) FROM information_schema.tables\n WHERE table_schema = DATABASE() AND table_name = 'retrieval_execution_leases'\n);\nSET @kfs_0017_retrieval_execution_leases_sql = IF(\n @kfs_0017_retrieval_execution_leases_exists = 0,\n 'CREATE TABLE `retrieval_execution_leases` ( `id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `subject_id` TEXT NOT NULL, `trace_id` CHAR(36) NOT NULL, `lease_token` VARCHAR(128) NOT NULL, `status` VARCHAR(16) NOT NULL, `row_version` INT NOT NULL, `acquired_at` DATETIME(3) NOT NULL, `heartbeat_at` DATETIME(3) NOT NULL, `expires_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, CONSTRAINT `retrieval_execution_leases_state_ck` CHECK (`status` IN (''active'', ''released'', ''expired'') AND `row_version` >= 0 AND `heartbeat_at` >= `acquired_at` AND `expires_at` > `heartbeat_at` AND `updated_at` >= `acquired_at`), CONSTRAINT `retrieval_execution_leases_space_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE )',\n 'DO 0'\n);\nPREPARE kfs_0017_retrieval_execution_leases_stmt\n FROM @kfs_0017_retrieval_execution_leases_sql;\nEXECUTE kfs_0017_retrieval_execution_leases_stmt;\nDEALLOCATE PREPARE kfs_0017_retrieval_execution_leases_stmt;\n\nCREATE INDEX IF NOT EXISTS `retrieval_execution_leases_space_expiry_idx`\n ON `retrieval_execution_leases` (\n `tenant_id`, `knowledge_space_id`, `status`, `expires_at`, `id`\n );\n\n-- deletion_jobs gains inbound FKs from items/outbox. TiDB can revalidate those FKs even for\n-- CREATE TABLE IF NOT EXISTS, so skip parent-table DDL entirely once it exists.\nSET @kfs_0017_deletion_jobs_exists = (\n SELECT COUNT(*) FROM information_schema.tables\n WHERE table_schema = DATABASE() AND table_name = 'deletion_jobs'\n);\nSET @kfs_0017_deletion_jobs_sql = IF(\n @kfs_0017_deletion_jobs_exists = 0,\n 'CREATE TABLE `deletion_jobs` ( `id` CHAR(36) PRIMARY KEY NOT NULL, `tenant_id` VARCHAR(255) NOT NULL, `knowledge_space_id` CHAR(36) NOT NULL, `target_type` VARCHAR(32) NOT NULL, `target_id` CHAR(36) NOT NULL, `target_revision` INT NOT NULL, `delete_mode` VARCHAR(16) NOT NULL, `requested_by_subject_id` VARCHAR(255) NOT NULL, `permission_snapshot_id` CHAR(36) NOT NULL, `permission_snapshot_revision` INT NOT NULL, `access_channel` VARCHAR(16) NOT NULL, `api_key_id` CHAR(36), `api_key_revision` INT, `api_key_expires_at` DATETIME(3), `idempotency_key` VARCHAR(512) NOT NULL, `request_fingerprint` CHAR(64) NOT NULL, `name_challenge_digest` CHAR(64), `checkpoint` VARCHAR(32) NOT NULL, `scan_phase` VARCHAR(64), `scan_cursor` VARCHAR(1024), `inventory_complete` BOOLEAN NOT NULL, `run_state` VARCHAR(16) NOT NULL, `active_slot` INT, `execution_attempts` INT NOT NULL, `max_execution_attempts` INT NOT NULL, `retry_at` DATETIME(3), `worker_id` VARCHAR(255), `lease_token` CHAR(36), `lease_expires_at` DATETIME(3), `heartbeat_at` DATETIME(3), `queue_job_id` VARCHAR(255), `last_error_code` VARCHAR(64), `last_error_message` TEXT, `row_version` INT NOT NULL, `created_at` DATETIME(3) NOT NULL, `updated_at` DATETIME(3) NOT NULL, `started_at` DATETIME(3), `completed_at` DATETIME(3), CONSTRAINT `deletion_jobs_target_ck` CHECK (`target_type` IN (''knowledge_space'', ''source'', ''document_asset'') AND ((`target_type` = ''source'' AND `delete_mode` IN (''keep'', ''cascade'') AND `name_challenge_digest` IS NULL) OR (`target_type` = ''knowledge_space'' AND `delete_mode` = ''cascade'' AND `name_challenge_digest` IS NOT NULL) OR (`target_type` = ''document_asset'' AND `delete_mode` = ''cascade'' AND `name_challenge_digest` IS NULL))), CONSTRAINT `deletion_jobs_checkpoint_ck` CHECK (`checkpoint` IN (''requested'', ''quiescing'', ''deleting_objects'', ''deleting_derived_data'', ''deleting_primary_data'', ''completed'')), CONSTRAINT `deletion_jobs_run_state_ck` CHECK (`run_state` IN (''dispatch_pending'', ''queued'', ''running'', ''retry_wait'', ''succeeded'', ''failed'', ''canceled'')), CONSTRAINT `deletion_jobs_access_channel_ck` CHECK (`access_channel` IN (''interactive'', ''service_api'', ''mcp'', ''agent'')), CONSTRAINT `deletion_jobs_api_key_binding_ck` CHECK ((`api_key_id` IS NULL AND `api_key_revision` IS NULL AND `api_key_expires_at` IS NULL) OR (`api_key_id` IS NOT NULL AND `api_key_revision` >= 1 AND `access_channel` = ''service_api'')), CONSTRAINT `deletion_jobs_positive_ck` CHECK (`target_revision` >= 1 AND `permission_snapshot_revision` >= 1 AND `row_version` >= 1 AND `execution_attempts` >= 0 AND `max_execution_attempts` >= 1 AND `execution_attempts` <= `max_execution_attempts` AND (`active_slot` IS NULL OR `active_slot` = 1)), CONSTRAINT `deletion_jobs_lifecycle_ck` CHECK ((`run_state` IN (''dispatch_pending'', ''queued'', ''running'', ''retry_wait'', ''failed'') AND `active_slot` = 1 AND `completed_at` IS NULL) OR (`run_state` IN (''succeeded'', ''canceled'') AND `active_slot` IS NULL AND `completed_at` IS NOT NULL)), CONSTRAINT `deletion_jobs_completion_ck` CHECK ((`run_state` = ''succeeded'' AND `checkpoint` = ''completed'') OR (`run_state` <> ''succeeded'' AND `checkpoint` <> ''completed'')), CONSTRAINT `deletion_jobs_retry_ck` CHECK ((`run_state` = ''retry_wait'' AND `retry_at` IS NOT NULL) OR (`run_state` <> ''retry_wait'' AND `retry_at` IS NULL)), CONSTRAINT `deletion_jobs_lease_ck` CHECK ((`run_state` = ''running'' AND `worker_id` IS NOT NULL AND `lease_token` IS NOT NULL AND `lease_expires_at` IS NOT NULL AND `heartbeat_at` IS NOT NULL) OR (`run_state` <> ''running'' AND `worker_id` IS NULL AND `lease_token` IS NULL AND `lease_expires_at` IS NULL AND `heartbeat_at` IS NULL)))',\n 'DO 0'\n);\nPREPARE kfs_0017_deletion_jobs_stmt FROM @kfs_0017_deletion_jobs_sql;\nEXECUTE kfs_0017_deletion_jobs_stmt;\nDEALLOCATE PREPARE kfs_0017_deletion_jobs_stmt;\n\nCREATE UNIQUE INDEX IF NOT EXISTS `deletion_jobs_idempotency_uq`\n ON `deletion_jobs` (`tenant_id`, `idempotency_key`);\nCREATE UNIQUE INDEX IF NOT EXISTS `deletion_jobs_target_active_uq`\n ON `deletion_jobs` (\n `tenant_id`, `knowledge_space_id`, `target_type`, `target_id`, `active_slot`\n );\nCREATE INDEX IF NOT EXISTS `deletion_jobs_claim_idx`\n ON `deletion_jobs` (`run_state`, `retry_at`, `lease_expires_at`, `created_at`, `id`);\nCREATE INDEX IF NOT EXISTS `deletion_jobs_scope_history_idx`\n ON `deletion_jobs` (`tenant_id`, `knowledge_space_id`, `created_at`, `id`);\nCREATE INDEX IF NOT EXISTS `deletion_jobs_requester_provenance_idx`\n ON `deletion_jobs` (\n `tenant_id`, `knowledge_space_id`, `requested_by_subject_id`,\n `api_key_id`, `api_key_revision`, `created_at`, `id`\n );\n\nCREATE TABLE IF NOT EXISTS `deletion_tombstones` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `deletion_job_id` CHAR(36) NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `target_type` VARCHAR(32) NOT NULL,\n `target_id` CHAR(36) NOT NULL,\n `target_revision` INT NOT NULL,\n `state` VARCHAR(16) NOT NULL,\n `row_version` INT NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `completed_at` DATETIME(3),\n CONSTRAINT `deletion_tombstones_target_ck` CHECK (\n `target_type` IN ('knowledge_space', 'source', 'document_asset')\n ),\n CONSTRAINT `deletion_tombstones_state_ck` CHECK (\n (`state` = 'active' AND `completed_at` IS NULL)\n OR (`state` = 'completed' AND `completed_at` IS NOT NULL)\n ),\n CONSTRAINT `deletion_tombstones_positive_ck` CHECK (\n `target_revision` >= 1 AND `row_version` >= 1\n )\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `deletion_tombstones_target_uq`\n ON `deletion_tombstones` (`tenant_id`, `target_type`, `target_id`);\nCREATE INDEX IF NOT EXISTS `deletion_tombstones_space_target_idx`\n ON `deletion_tombstones` (`tenant_id`, `knowledge_space_id`, `target_type`, `target_id`);\nCREATE INDEX IF NOT EXISTS `deletion_tombstones_job_idx`\n ON `deletion_tombstones` (`deletion_job_id`, `id`);\n\nCREATE TABLE IF NOT EXISTS `deletion_job_items` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `deletion_job_id` CHAR(36) NOT NULL,\n `ordinal` BIGINT NOT NULL,\n `kind` VARCHAR(32) NOT NULL,\n `resource_id` CHAR(36),\n `object_key` TEXT,\n `credential_ref` VARCHAR(255),\n `cache_key` TEXT,\n `payload_digest` CHAR(64) NOT NULL,\n `idempotency_key` VARCHAR(512) NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `attempts` INT NOT NULL,\n `max_attempts` INT NOT NULL,\n `next_attempt_at` DATETIME(3),\n `last_error_code` VARCHAR(64),\n `last_error_message` TEXT,\n `row_version` INT NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `completed_at` DATETIME(3),\n `redacted_at` DATETIME(3),\n CONSTRAINT `deletion_job_items_kind_ck` CHECK (\n `kind` IN ('object', 'secret_ref', 'cache_key', 'document_cascade', 'document_detach')\n ),\n CONSTRAINT `deletion_job_items_status_ck` CHECK (\n `status` IN ('pending', 'retry_wait', 'completed', 'dead')\n ),\n CONSTRAINT `deletion_job_items_positive_ck` CHECK (\n `ordinal` >= 0 AND `attempts` >= 0 AND `max_attempts` >= 1\n AND `attempts` <= `max_attempts` AND `row_version` >= 1\n ),\n CONSTRAINT `deletion_job_items_retry_ck` CHECK (\n (`status` = 'retry_wait' AND `next_attempt_at` IS NOT NULL)\n OR (`status` <> 'retry_wait' AND `next_attempt_at` IS NULL)\n ),\n CONSTRAINT `deletion_job_items_terminal_ck` CHECK (\n (`status` IN ('completed', 'dead') AND `completed_at` IS NOT NULL)\n OR (`status` IN ('pending', 'retry_wait') AND `completed_at` IS NULL)\n ),\n CONSTRAINT `deletion_job_items_payload_ck` CHECK (\n (`kind` = 'object' AND `credential_ref` IS NULL AND `cache_key` IS NULL\n AND ((`status` = 'completed' AND `object_key` IS NULL AND `redacted_at` IS NOT NULL)\n OR (`status` <> 'completed' AND `object_key` IS NOT NULL AND `redacted_at` IS NULL)))\n OR (`kind` = 'secret_ref' AND `object_key` IS NULL AND `cache_key` IS NULL\n AND ((`status` = 'completed' AND `credential_ref` IS NULL AND `redacted_at` IS NOT NULL)\n OR (`status` <> 'completed' AND `credential_ref` IS NOT NULL AND `redacted_at` IS NULL)))\n OR (`kind` = 'cache_key' AND `object_key` IS NULL AND `credential_ref` IS NULL\n AND ((`status` = 'completed' AND `cache_key` IS NULL AND `redacted_at` IS NOT NULL)\n OR (`status` <> 'completed' AND `cache_key` IS NOT NULL AND `redacted_at` IS NULL)))\n OR (`kind` IN ('document_cascade', 'document_detach') AND `resource_id` IS NOT NULL\n AND `object_key` IS NULL AND `credential_ref` IS NULL AND `cache_key` IS NULL\n AND `redacted_at` IS NULL)\n ),\n FOREIGN KEY (`deletion_job_id`) REFERENCES `deletion_jobs` (`id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `deletion_job_items_idempotency_uq`\n ON `deletion_job_items` (`deletion_job_id`, `idempotency_key`);\nCREATE UNIQUE INDEX IF NOT EXISTS `deletion_job_items_ordinal_uq`\n ON `deletion_job_items` (`deletion_job_id`, `ordinal`);\nCREATE INDEX IF NOT EXISTS `deletion_job_items_work_idx`\n ON `deletion_job_items` (\n `deletion_job_id`, `status`, `next_attempt_at`, `ordinal`, `id`\n );\nCREATE INDEX IF NOT EXISTS `deletion_job_items_resource_idx`\n ON `deletion_job_items` (`deletion_job_id`, `kind`, `resource_id`, `id`);\n\nCREATE TABLE IF NOT EXISTS `deletion_outbox` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `deletion_job_id` CHAR(36) NOT NULL,\n `delivery_revision` INT NOT NULL,\n `event_type` VARCHAR(32) NOT NULL,\n `schema_version` INT NOT NULL,\n `idempotency_key` VARCHAR(512) NOT NULL,\n `request_idempotency_key` VARCHAR(512) NOT NULL,\n `request_fingerprint` CHAR(64) NOT NULL,\n `payload` JSON NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `available_at` DATETIME(3) NOT NULL,\n `dispatch_attempts` INT NOT NULL,\n `locked_by` VARCHAR(255),\n `locked_until` DATETIME(3),\n `lock_token` CHAR(36),\n `queue_job_id` VARCHAR(255),\n `last_error` TEXT,\n `delivered_at` DATETIME(3),\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `deletion_outbox_event_ck` CHECK (`event_type` = 'deletion.job'),\n CONSTRAINT `deletion_outbox_schema_ck` CHECK (`schema_version` = 1),\n CONSTRAINT `deletion_outbox_status_ck` CHECK (\n `status` IN ('pending', 'dispatching', 'dispatched', 'leased', 'completed', 'canceled', 'dead')\n ),\n CONSTRAINT `deletion_outbox_positive_ck` CHECK (\n `delivery_revision` >= 1 AND `dispatch_attempts` >= 0\n ),\n CONSTRAINT `deletion_outbox_lock_ck` CHECK (\n (`lock_token` IS NULL AND `locked_by` IS NULL AND `locked_until` IS NULL)\n OR (`lock_token` IS NOT NULL AND `locked_by` IS NOT NULL AND `locked_until` IS NOT NULL)\n ),\n FOREIGN KEY (`deletion_job_id`) REFERENCES `deletion_jobs` (`id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `deletion_outbox_idempotency_uq`\n ON `deletion_outbox` (`idempotency_key`);\nCREATE UNIQUE INDEX IF NOT EXISTS `deletion_outbox_job_delivery_uq`\n ON `deletion_outbox` (`deletion_job_id`, `delivery_revision`);\nCREATE UNIQUE INDEX IF NOT EXISTS `deletion_outbox_job_request_uq`\n ON `deletion_outbox` (`deletion_job_id`, `request_idempotency_key`);\nCREATE INDEX IF NOT EXISTS `deletion_outbox_claim_idx`\n ON `deletion_outbox` (`status`, `available_at`, `locked_until`, `id`);\n\n-- Immutable retry provenance is kept separately so owner rescue never overwrites the original\n-- requester on deletion_jobs. Permission snapshots and the target space may later be removed.\nCREATE TABLE IF NOT EXISTS `deletion_retry_audits` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `deletion_job_id` CHAR(36) NOT NULL,\n `outbox_id` CHAR(36) NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `retry_authority` VARCHAR(32) NOT NULL,\n `actor_subject_id` VARCHAR(255) NOT NULL,\n `permission_snapshot_id` CHAR(36) NOT NULL,\n `permission_snapshot_revision` INT NOT NULL,\n `access_channel` VARCHAR(16) NOT NULL,\n `api_key_id` CHAR(36),\n `api_key_revision` INT,\n `api_key_expires_at` DATETIME(3),\n `request_idempotency_key` VARCHAR(512) NOT NULL,\n `request_fingerprint` CHAR(64) NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n CONSTRAINT `deletion_retry_audits_authority_ck` CHECK (\n `retry_authority` IN ('original_requester', 'interactive_owner_rescue')\n ),\n CONSTRAINT `deletion_retry_audits_access_channel_ck` CHECK (\n `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent')\n ),\n CONSTRAINT `deletion_retry_audits_positive_ck` CHECK (\n `permission_snapshot_revision` >= 1\n ),\n CONSTRAINT `deletion_retry_audits_api_key_binding_ck` CHECK (\n (`api_key_id` IS NULL AND `api_key_revision` IS NULL AND `api_key_expires_at` IS NULL)\n OR (`api_key_id` IS NOT NULL AND `api_key_revision` >= 1 AND `access_channel` = 'service_api')\n ),\n CONSTRAINT `deletion_retry_audits_owner_rescue_ck` CHECK (\n `retry_authority` <> 'interactive_owner_rescue'\n OR (\n `access_channel` = 'interactive'\n AND `api_key_id` IS NULL\n AND `api_key_revision` IS NULL\n AND `api_key_expires_at` IS NULL\n )\n ),\n FOREIGN KEY (`deletion_job_id`) REFERENCES `deletion_jobs` (`id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `deletion_retry_audits_job_request_uq`\n ON `deletion_retry_audits` (`deletion_job_id`, `request_idempotency_key`);\nCREATE UNIQUE INDEX IF NOT EXISTS `deletion_retry_audits_outbox_uq`\n ON `deletion_retry_audits` (`outbox_id`);\nCREATE INDEX IF NOT EXISTS `deletion_retry_audits_actor_idx`\n ON `deletion_retry_audits` (\n `tenant_id`, `knowledge_space_id`, `actor_subject_id`, `created_at`, `id`\n );\n\nCREATE INDEX IF NOT EXISTS `knowledge_spaces_lifecycle_idx`\n ON `knowledge_spaces` (`tenant_id`, `lifecycle_state`, `updated_at`, `id`);\nCREATE INDEX IF NOT EXISTS `sources_deletion_job_idx`\n ON `sources` (`knowledge_space_id`, `deletion_job_id`, `id`);\nCREATE INDEX IF NOT EXISTS `document_assets_lifecycle_idx`\n ON `document_assets` (`knowledge_space_id`, `lifecycle_state`, `source_id`, `version`, `id`);\nCREATE INDEX IF NOT EXISTS `page_index_manifests_document_idx`\n ON `page_index_manifests` (`document_asset_id`, `publication_generation_id`, `id`);\n\n-- Agent workspace command logs and metadata are conservatively invalidated by whole space. The\n-- durable row keeps exact creator authorization so reads remain consistent across API replicas.\nCREATE TABLE IF NOT EXISTS `agent_workspace_snapshots` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `subject_id` VARCHAR(255) NOT NULL,\n `access_channel` VARCHAR(16) NOT NULL,\n `permission_snapshot_id` CHAR(36) NOT NULL,\n `permission_snapshot_revision` INT NOT NULL,\n `permission_scopes` JSON NOT NULL,\n `fingerprint` VARCHAR(80) NOT NULL,\n `payload` JSON NOT NULL,\n `invalidated_at` DATETIME(3),\n `invalidation_reason` VARCHAR(64),\n `created_at` DATETIME(3) NOT NULL,\n CONSTRAINT `agent_workspace_snapshots_channel_ck` CHECK (\n `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent')\n ),\n CONSTRAINT `agent_workspace_snapshots_revision_ck` CHECK (\n `permission_snapshot_revision` >= 1\n ),\n CONSTRAINT `agent_workspace_snapshots_invalidation_ck` CHECK (\n (`invalidated_at` IS NULL AND `invalidation_reason` IS NULL)\n OR (`invalidated_at` IS NOT NULL AND `invalidation_reason` IS NOT NULL)\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS `agent_workspace_snapshots_tenant_lookup_idx`\n ON `agent_workspace_snapshots` (`tenant_id`, `id`, `invalidated_at`);\nCREATE INDEX IF NOT EXISTS `agent_workspace_snapshots_space_cleanup_idx`\n ON `agent_workspace_snapshots` (\n `tenant_id`, `knowledge_space_id`, `invalidated_at`, `id`\n );\n", path: "packages/database/migrations/0017_durable_deletion.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0018_versioned_space_profiles\n-- Dialect: postgres\n\n-- Profile snapshots are append-only. The small mutable state surface records activation outcome;\n-- repository transitions never update snapshot, digest, capability, or model identity columns.\nCREATE TABLE IF NOT EXISTS \"knowledge_space_profile_revisions\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"kind\" VARCHAR(16) NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"state\" VARCHAR(16) NOT NULL,\n \"snapshot\" JSONB NOT NULL,\n \"snapshot_digest\" CHAR(64) NOT NULL,\n \"capability_snapshot\" JSONB NOT NULL,\n \"capability_snapshot_digest\" CHAR(64) NOT NULL,\n \"plugin_id\" VARCHAR(256) NOT NULL,\n \"provider\" VARCHAR(256) NOT NULL,\n \"model\" VARCHAR(256) NOT NULL,\n \"vector_space_id\" VARCHAR(87),\n \"dimension\" INTEGER,\n \"created_by_subject_id\" VARCHAR(255) NOT NULL,\n \"failure_code\" VARCHAR(64),\n \"failure_message\" TEXT,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"activated_at\" TIMESTAMPTZ,\n \"superseded_at\" TIMESTAMPTZ,\n \"failed_at\" TIMESTAMPTZ,\n CONSTRAINT \"knowledge_space_profile_revisions_scope_revision_uq\"\n UNIQUE (\"tenant_id\", \"knowledge_space_id\", \"kind\", \"revision\"),\n CONSTRAINT \"knowledge_space_profile_revisions_head_fk_uq\"\n UNIQUE (\"tenant_id\", \"knowledge_space_id\", \"kind\", \"id\", \"revision\"),\n CONSTRAINT \"knowledge_space_profile_revisions_attempt_fk_uq\"\n UNIQUE (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"id\", \"revision\", \"snapshot_digest\"\n ),\n CONSTRAINT \"knowledge_space_profile_revisions_kind_ck\"\n CHECK (\"kind\" IN ('embedding', 'retrieval')),\n CONSTRAINT \"knowledge_space_profile_revisions_state_ck\"\n CHECK (\"state\" IN ('candidate', 'active', 'superseded', 'failed')),\n CONSTRAINT \"knowledge_space_profile_revisions_positive_ck\"\n CHECK (\"revision\" >= 1 AND (\"dimension\" IS NULL OR \"dimension\" >= 1)),\n CONSTRAINT \"knowledge_space_profile_revisions_vector_shape_ck\"\n CHECK (\n (\n \"kind\" = 'embedding'\n AND \"vector_space_id\" IS NOT NULL\n AND \"dimension\" IS NOT NULL\n AND \"dimension\" >= 1\n )\n OR (\"kind\" = 'retrieval' AND \"vector_space_id\" IS NULL AND \"dimension\" IS NULL)\n ),\n CONSTRAINT \"knowledge_space_profile_revisions_lifecycle_ck\"\n CHECK (\n (\n \"state\" = 'candidate'\n AND \"activated_at\" IS NULL AND \"superseded_at\" IS NULL AND \"failed_at\" IS NULL\n AND \"failure_code\" IS NULL AND \"failure_message\" IS NULL\n )\n OR (\n \"state\" = 'active'\n AND \"activated_at\" IS NOT NULL AND \"superseded_at\" IS NULL AND \"failed_at\" IS NULL\n AND \"failure_code\" IS NULL AND \"failure_message\" IS NULL\n )\n OR (\n \"state\" = 'superseded'\n AND \"activated_at\" IS NOT NULL AND \"superseded_at\" IS NOT NULL AND \"failed_at\" IS NULL\n AND \"failure_code\" IS NULL AND \"failure_message\" IS NULL\n )\n OR (\n \"state\" = 'failed'\n AND \"activated_at\" IS NULL AND \"superseded_at\" IS NULL AND \"failed_at\" IS NOT NULL\n AND \"failure_code\" IS NOT NULL AND \"failure_message\" IS NOT NULL\n )\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS \"knowledge_space_profile_revisions_scope_state_idx\"\n ON \"knowledge_space_profile_revisions\" (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"state\", \"revision\", \"id\"\n );\n\n-- The head is the only active-profile pointer. Activation is a CAS transaction that supersedes the\n-- previous active revision, activates the candidate, and advances this row together.\nCREATE TABLE IF NOT EXISTS \"knowledge_space_profile_heads\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"kind\" VARCHAR(16) NOT NULL,\n \"profile_revision_id\" UUID NOT NULL,\n \"active_revision\" INTEGER NOT NULL,\n \"row_version\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"knowledge_space_profile_heads_kind_ck\"\n CHECK (\"kind\" IN ('embedding', 'retrieval')),\n CONSTRAINT \"knowledge_space_profile_heads_positive_ck\"\n CHECK (\"active_revision\" >= 1 AND \"row_version\" >= 1),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"profile_revision_id\", \"active_revision\"\n ) REFERENCES \"knowledge_space_profile_revisions\" (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"id\", \"revision\"\n ) ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_profile_heads_scope_uq\"\n ON \"knowledge_space_profile_heads\" (\"tenant_id\", \"knowledge_space_id\", \"kind\");\n\n-- Discovery writes immutable source snapshots into this durable ledger. Workers are fenced by both\n-- lease token and row version, then revalidate the locked manifest before installing an initial\n-- head. No unbounded or network-dependent backfill runs inside the schema migration.\nCREATE TABLE IF NOT EXISTS \"knowledge_space_profile_backfills\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"kind\" VARCHAR(16) NOT NULL,\n \"source_manifest_version\" INTEGER NOT NULL,\n \"source_snapshot\" JSONB NOT NULL,\n \"source_snapshot_digest\" CHAR(64) NOT NULL,\n \"run_state\" VARCHAR(16) NOT NULL,\n \"execution_attempts\" INTEGER NOT NULL,\n \"max_execution_attempts\" INTEGER NOT NULL,\n \"worker_id\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"heartbeat_at\" TIMESTAMPTZ,\n \"row_version\" INTEGER NOT NULL,\n \"last_error_code\" VARCHAR(64),\n \"last_error_message\" TEXT,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"completed_at\" TIMESTAMPTZ,\n CONSTRAINT \"knowledge_space_profile_backfills_kind_ck\"\n CHECK (\"kind\" IN ('embedding', 'retrieval')),\n CONSTRAINT \"knowledge_space_profile_backfills_state_ck\"\n CHECK (\"run_state\" IN ('queued', 'running', 'succeeded', 'failed')),\n CONSTRAINT \"knowledge_space_profile_backfills_positive_ck\"\n CHECK (\n \"source_manifest_version\" >= 1\n AND \"execution_attempts\" >= 0\n AND \"max_execution_attempts\" >= 1\n AND \"execution_attempts\" <= \"max_execution_attempts\"\n AND \"row_version\" >= 1\n ),\n CONSTRAINT \"knowledge_space_profile_backfills_lease_ck\"\n CHECK (\n (\n \"run_state\" = 'running'\n AND \"worker_id\" IS NOT NULL AND \"lease_token\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL AND \"heartbeat_at\" IS NOT NULL\n )\n OR (\n \"run_state\" <> 'running'\n AND \"worker_id\" IS NULL AND \"lease_token\" IS NULL\n AND \"lease_expires_at\" IS NULL AND \"heartbeat_at\" IS NULL\n )\n ),\n CONSTRAINT \"knowledge_space_profile_backfills_lifecycle_ck\"\n CHECK (\n (\n \"run_state\" IN ('queued', 'running')\n AND \"completed_at\" IS NULL\n AND \"last_error_code\" IS NULL AND \"last_error_message\" IS NULL\n )\n OR (\n \"run_state\" = 'succeeded'\n AND \"completed_at\" IS NOT NULL\n AND \"last_error_code\" IS NULL AND \"last_error_message\" IS NULL\n )\n OR (\n \"run_state\" = 'failed'\n AND \"completed_at\" IS NOT NULL\n AND \"last_error_code\" IS NOT NULL AND \"last_error_message\" IS NOT NULL\n )\n ),\n CONSTRAINT \"knowledge_space_profile_backfills_lease_token_ck\"\n CHECK (\n \"lease_token\" IS NULL\n OR \"lease_token\" <> '00000000-0000-0000-0000-000000000000'::uuid\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_profile_backfills_source_uq\"\n ON \"knowledge_space_profile_backfills\" (\n \"tenant_id\", \"knowledge_space_id\", \"kind\",\n \"source_manifest_version\", \"source_snapshot_digest\"\n );\nCREATE INDEX IF NOT EXISTS \"knowledge_space_profile_backfills_claim_idx\"\n ON \"knowledge_space_profile_backfills\" (\n \"run_state\", \"lease_expires_at\", \"updated_at\", \"id\"\n );\n", path: "packages/database/migrations/0018_versioned_space_profiles.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0018_versioned_space_profiles\n-- Dialect: tidb\n\n-- Profile snapshots are append-only. Runtime transitions only mutate lifecycle columns.\nCREATE TABLE IF NOT EXISTS `knowledge_space_profile_revisions` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `kind` VARCHAR(16) NOT NULL,\n `revision` INT NOT NULL,\n `state` VARCHAR(16) NOT NULL,\n `snapshot` JSON NOT NULL,\n `snapshot_digest` CHAR(64) NOT NULL,\n `capability_snapshot` JSON NOT NULL,\n `capability_snapshot_digest` CHAR(64) NOT NULL,\n `plugin_id` VARCHAR(256) NOT NULL,\n `provider` VARCHAR(256) NOT NULL,\n `model` VARCHAR(256) NOT NULL,\n `vector_space_id` VARCHAR(87),\n `dimension` INT,\n `created_by_subject_id` VARCHAR(255) NOT NULL,\n `failure_code` VARCHAR(64),\n `failure_message` TEXT,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `activated_at` DATETIME(3),\n `superseded_at` DATETIME(3),\n `failed_at` DATETIME(3),\n CONSTRAINT `knowledge_space_profile_revisions_scope_revision_uq`\n UNIQUE (`tenant_id`, `knowledge_space_id`, `kind`, `revision`),\n CONSTRAINT `knowledge_space_profile_revisions_head_fk_uq`\n UNIQUE (`tenant_id`, `knowledge_space_id`, `kind`, `id`, `revision`),\n CONSTRAINT `knowledge_space_profile_revisions_attempt_fk_uq`\n UNIQUE (\n `tenant_id`, `knowledge_space_id`, `kind`, `id`, `revision`, `snapshot_digest`\n ),\n CONSTRAINT `knowledge_space_profile_revisions_kind_ck`\n CHECK (`kind` IN ('embedding', 'retrieval')),\n CONSTRAINT `knowledge_space_profile_revisions_state_ck`\n CHECK (`state` IN ('candidate', 'active', 'superseded', 'failed')),\n CONSTRAINT `knowledge_space_profile_revisions_positive_ck`\n CHECK (`revision` >= 1 AND (`dimension` IS NULL OR `dimension` >= 1)),\n CONSTRAINT `knowledge_space_profile_revisions_vector_shape_ck`\n CHECK (\n (\n `kind` = 'embedding'\n AND `vector_space_id` IS NOT NULL\n AND `dimension` IS NOT NULL\n AND `dimension` >= 1\n )\n OR (`kind` = 'retrieval' AND `vector_space_id` IS NULL AND `dimension` IS NULL)\n ),\n CONSTRAINT `knowledge_space_profile_revisions_lifecycle_ck`\n CHECK (\n (\n `state` = 'candidate'\n AND `activated_at` IS NULL AND `superseded_at` IS NULL AND `failed_at` IS NULL\n AND `failure_code` IS NULL AND `failure_message` IS NULL\n )\n OR (\n `state` = 'active'\n AND `activated_at` IS NOT NULL AND `superseded_at` IS NULL AND `failed_at` IS NULL\n AND `failure_code` IS NULL AND `failure_message` IS NULL\n )\n OR (\n `state` = 'superseded'\n AND `activated_at` IS NOT NULL AND `superseded_at` IS NOT NULL AND `failed_at` IS NULL\n AND `failure_code` IS NULL AND `failure_message` IS NULL\n )\n OR (\n `state` = 'failed'\n AND `activated_at` IS NULL AND `superseded_at` IS NULL AND `failed_at` IS NOT NULL\n AND `failure_code` IS NOT NULL AND `failure_message` IS NOT NULL\n )\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS `knowledge_space_profile_revisions_scope_state_idx`\n ON `knowledge_space_profile_revisions` (\n `tenant_id`, `knowledge_space_id`, `kind`, `state`, `revision`, `id`\n );\n\nCREATE TABLE IF NOT EXISTS `knowledge_space_profile_heads` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `kind` VARCHAR(16) NOT NULL,\n `profile_revision_id` CHAR(36) NOT NULL,\n `active_revision` INT NOT NULL,\n `row_version` INT NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `knowledge_space_profile_heads_kind_ck`\n CHECK (`kind` IN ('embedding', 'retrieval')),\n CONSTRAINT `knowledge_space_profile_heads_positive_ck`\n CHECK (`active_revision` >= 1 AND `row_version` >= 1),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `kind`, `profile_revision_id`, `active_revision`\n ) REFERENCES `knowledge_space_profile_revisions` (\n `tenant_id`, `knowledge_space_id`, `kind`, `id`, `revision`\n )\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_profile_heads_scope_uq`\n ON `knowledge_space_profile_heads` (`tenant_id`, `knowledge_space_id`, `kind`);\n\n-- The runtime discovers legacy manifest profiles in bounded keyset pages and records the exact\n-- source snapshot here. Lease-token plus row-version fences prevent stale workers from activating\n-- a profile after a manifest or deletion transition.\nCREATE TABLE IF NOT EXISTS `knowledge_space_profile_backfills` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `kind` VARCHAR(16) NOT NULL,\n `source_manifest_version` INT NOT NULL,\n `source_snapshot` JSON NOT NULL,\n `source_snapshot_digest` CHAR(64) NOT NULL,\n `run_state` VARCHAR(16) NOT NULL,\n `execution_attempts` INT NOT NULL,\n `max_execution_attempts` INT NOT NULL,\n `worker_id` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `heartbeat_at` DATETIME(3),\n `row_version` INT NOT NULL,\n `last_error_code` VARCHAR(64),\n `last_error_message` TEXT,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `completed_at` DATETIME(3),\n CONSTRAINT `knowledge_space_profile_backfills_kind_ck`\n CHECK (`kind` IN ('embedding', 'retrieval')),\n CONSTRAINT `knowledge_space_profile_backfills_state_ck`\n CHECK (`run_state` IN ('queued', 'running', 'succeeded', 'failed')),\n CONSTRAINT `knowledge_space_profile_backfills_positive_ck`\n CHECK (\n `source_manifest_version` >= 1\n AND `execution_attempts` >= 0\n AND `max_execution_attempts` >= 1\n AND `execution_attempts` <= `max_execution_attempts`\n AND `row_version` >= 1\n ),\n CONSTRAINT `knowledge_space_profile_backfills_lease_ck`\n CHECK (\n (\n `run_state` = 'running'\n AND `worker_id` IS NOT NULL AND `lease_token` IS NOT NULL\n AND `lease_expires_at` IS NOT NULL AND `heartbeat_at` IS NOT NULL\n )\n OR (\n `run_state` <> 'running'\n AND `worker_id` IS NULL AND `lease_token` IS NULL\n AND `lease_expires_at` IS NULL AND `heartbeat_at` IS NULL\n )\n ),\n CONSTRAINT `knowledge_space_profile_backfills_lifecycle_ck`\n CHECK (\n (\n `run_state` IN ('queued', 'running')\n AND `completed_at` IS NULL\n AND `last_error_code` IS NULL AND `last_error_message` IS NULL\n )\n OR (\n `run_state` = 'succeeded'\n AND `completed_at` IS NOT NULL\n AND `last_error_code` IS NULL AND `last_error_message` IS NULL\n )\n OR (\n `run_state` = 'failed'\n AND `completed_at` IS NOT NULL\n AND `last_error_code` IS NOT NULL AND `last_error_message` IS NOT NULL\n )\n ),\n CONSTRAINT `knowledge_space_profile_backfills_lease_token_ck`\n CHECK (\n `lease_token` IS NULL\n OR (\n `lease_token` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `lease_token` <> '00000000-0000-0000-0000-000000000000'\n )\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_profile_backfills_source_uq`\n ON `knowledge_space_profile_backfills` (\n `tenant_id`, `knowledge_space_id`, `kind`,\n `source_manifest_version`, `source_snapshot_digest`\n );\nCREATE INDEX IF NOT EXISTS `knowledge_space_profile_backfills_claim_idx`\n ON `knowledge_space_profile_backfills` (\n `run_state`, `lease_expires_at`, `updated_at`, `id`\n );\n", path: "packages/database/migrations/0018_versioned_space_profiles.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0019_profile_publication_bindings\n-- Dialect: postgres\n\n-- A model-profile migration builds one immutable projection candidate before activation. This\n-- ledger binds that candidate to the exact immutable profile revision that was used for the build.\n-- Runtime activation revalidates the digest/vector-space and advances both heads in one transaction.\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_profile_revisions_attempt_fk_uq\"\n ON \"knowledge_space_profile_revisions\" (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"id\", \"revision\", \"snapshot_digest\"\n );\n\n-- Nullable pairs keep the migration compatible with in-flight attempts created by legacy writers.\n-- New writers populate both exact snapshots when the attempt is created and revalidate them before\n-- publication. The fixed kind columns let the composite foreign keys prove that an embedding\n-- snapshot cannot be substituted for a retrieval snapshot (or vice versa).\nALTER TABLE \"document_compilation_attempts\"\n ADD COLUMN IF NOT EXISTS \"embedding_profile_kind\" VARCHAR(16),\n ADD COLUMN IF NOT EXISTS \"embedding_profile_revision_id\" UUID,\n ADD COLUMN IF NOT EXISTS \"embedding_profile_revision\" INTEGER,\n ADD COLUMN IF NOT EXISTS \"embedding_profile_snapshot_digest\" CHAR(64),\n ADD COLUMN IF NOT EXISTS \"retrieval_profile_kind\" VARCHAR(16),\n ADD COLUMN IF NOT EXISTS \"retrieval_profile_revision_id\" UUID,\n ADD COLUMN IF NOT EXISTS \"retrieval_profile_revision\" INTEGER,\n ADD COLUMN IF NOT EXISTS \"retrieval_profile_snapshot_digest\" CHAR(64);\n\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM \"pg_constraint\" WHERE \"conname\" = 'document_compilation_attempts_embedding_profile_ck' AND \"conrelid\" = 'document_compilation_attempts'::regclass) THEN\n ALTER TABLE \"document_compilation_attempts\"\n ADD CONSTRAINT \"document_compilation_attempts_embedding_profile_ck\" CHECK (\n (\"embedding_profile_kind\" IS NULL AND \"embedding_profile_revision_id\" IS NULL AND \"embedding_profile_revision\" IS NULL AND \"embedding_profile_snapshot_digest\" IS NULL)\n OR (\"embedding_profile_kind\" = 'embedding' AND \"embedding_profile_revision_id\" IS NOT NULL AND \"embedding_profile_revision\" >= 1 AND \"embedding_profile_snapshot_digest\" IS NOT NULL)\n );\n END IF;\n IF NOT EXISTS (SELECT 1 FROM \"pg_constraint\" WHERE \"conname\" = 'document_compilation_attempts_retrieval_profile_ck' AND \"conrelid\" = 'document_compilation_attempts'::regclass) THEN\n ALTER TABLE \"document_compilation_attempts\"\n ADD CONSTRAINT \"document_compilation_attempts_retrieval_profile_ck\" CHECK (\n (\"retrieval_profile_kind\" IS NULL AND \"retrieval_profile_revision_id\" IS NULL AND \"retrieval_profile_revision\" IS NULL AND \"retrieval_profile_snapshot_digest\" IS NULL)\n OR (\"retrieval_profile_kind\" = 'retrieval' AND \"retrieval_profile_revision_id\" IS NOT NULL AND \"retrieval_profile_revision\" >= 1 AND \"retrieval_profile_snapshot_digest\" IS NOT NULL)\n );\n END IF;\n IF NOT EXISTS (SELECT 1 FROM \"pg_constraint\" WHERE \"conname\" = 'document_compilation_attempts_profile_tuple_ck' AND \"conrelid\" = 'document_compilation_attempts'::regclass) THEN\n ALTER TABLE \"document_compilation_attempts\"\n ADD CONSTRAINT \"document_compilation_attempts_profile_tuple_ck\" CHECK (\n (\n \"embedding_profile_kind\" IS NULL\n AND \"embedding_profile_revision_id\" IS NULL\n AND \"embedding_profile_revision\" IS NULL\n AND \"embedding_profile_snapshot_digest\" IS NULL\n AND \"retrieval_profile_kind\" IS NULL\n AND \"retrieval_profile_revision_id\" IS NULL\n AND \"retrieval_profile_revision\" IS NULL\n AND \"retrieval_profile_snapshot_digest\" IS NULL\n )\n OR (\n \"retrieval_profile_kind\" = 'retrieval'\n AND \"retrieval_profile_revision_id\" IS NOT NULL\n AND \"retrieval_profile_revision\" >= 1\n AND \"retrieval_profile_snapshot_digest\" IS NOT NULL\n AND (\n (\n \"embedding_profile_kind\" IS NULL\n AND \"embedding_profile_revision_id\" IS NULL\n AND \"embedding_profile_revision\" IS NULL\n AND \"embedding_profile_snapshot_digest\" IS NULL\n )\n OR (\n \"embedding_profile_kind\" = 'embedding'\n AND \"embedding_profile_revision_id\" IS NOT NULL\n AND \"embedding_profile_revision\" >= 1\n AND \"embedding_profile_snapshot_digest\" IS NOT NULL\n )\n )\n )\n );\n END IF;\n IF NOT EXISTS (SELECT 1 FROM \"pg_constraint\" WHERE \"conname\" = 'document_compilation_attempts_embedding_profile_fk' AND \"conrelid\" = 'document_compilation_attempts'::regclass) THEN\n ALTER TABLE \"document_compilation_attempts\"\n ADD CONSTRAINT \"document_compilation_attempts_embedding_profile_fk\" FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"embedding_profile_kind\", \"embedding_profile_revision_id\", \"embedding_profile_revision\", \"embedding_profile_snapshot_digest\"\n ) REFERENCES \"knowledge_space_profile_revisions\" (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"id\", \"revision\", \"snapshot_digest\"\n ) ON DELETE RESTRICT;\n END IF;\n IF NOT EXISTS (SELECT 1 FROM \"pg_constraint\" WHERE \"conname\" = 'document_compilation_attempts_retrieval_profile_fk' AND \"conrelid\" = 'document_compilation_attempts'::regclass) THEN\n ALTER TABLE \"document_compilation_attempts\"\n ADD CONSTRAINT \"document_compilation_attempts_retrieval_profile_fk\" FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"retrieval_profile_kind\", \"retrieval_profile_revision_id\", \"retrieval_profile_revision\", \"retrieval_profile_snapshot_digest\"\n ) REFERENCES \"knowledge_space_profile_revisions\" (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"id\", \"revision\", \"snapshot_digest\"\n ) ON DELETE RESTRICT;\n END IF;\nEND $$;\n\nCREATE TABLE IF NOT EXISTS \"knowledge_space_profile_publication_bindings\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"changed_kind\" VARCHAR(16) NOT NULL,\n \"binding_reason\" VARCHAR(24) NOT NULL,\n \"embedding_profile_kind\" VARCHAR(16),\n \"embedding_profile_revision_id\" UUID,\n \"embedding_profile_revision\" INTEGER,\n \"embedding_profile_snapshot_digest\" CHAR(64),\n \"retrieval_profile_kind\" VARCHAR(16) NOT NULL,\n \"retrieval_profile_revision_id\" UUID NOT NULL,\n \"retrieval_profile_revision\" INTEGER NOT NULL,\n \"retrieval_profile_snapshot_digest\" CHAR(64) NOT NULL,\n \"vector_space_id\" VARCHAR(87),\n \"publication_id\" UUID NOT NULL,\n \"publication_fingerprint\" VARCHAR(86) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"activated_at\" TIMESTAMPTZ,\n CONSTRAINT \"knowledge_space_profile_publication_bindings_kind_ck\"\n CHECK (\n \"changed_kind\" IN ('embedding', 'retrieval', 'bootstrap', 'content')\n AND \"retrieval_profile_kind\" = 'retrieval'\n AND (\"embedding_profile_kind\" IS NULL OR \"embedding_profile_kind\" = 'embedding')\n ),\n CONSTRAINT \"knowledge_space_profile_publication_bindings_reason_ck\"\n CHECK (\n (\"binding_reason\" = 'candidate-switch' AND \"changed_kind\" IN ('embedding', 'retrieval'))\n OR (\"binding_reason\" = 'legacy-bootstrap' AND \"changed_kind\" = 'bootstrap')\n OR (\"binding_reason\" = 'content-publication' AND \"changed_kind\" = 'content')\n ),\n CONSTRAINT \"knowledge_space_profile_publication_bindings_shape_ck\"\n CHECK (\n \"retrieval_profile_revision\" >= 1\n AND (\n (\n \"embedding_profile_kind\" IS NULL\n AND \"embedding_profile_revision_id\" IS NULL\n AND \"embedding_profile_revision\" IS NULL\n AND \"embedding_profile_snapshot_digest\" IS NULL\n AND \"vector_space_id\" IS NULL\n AND \"changed_kind\" IN ('retrieval', 'bootstrap', 'content')\n )\n OR (\n \"embedding_profile_kind\" = 'embedding'\n AND \"embedding_profile_revision_id\" IS NOT NULL\n AND \"embedding_profile_revision\" >= 1\n AND \"embedding_profile_snapshot_digest\" IS NOT NULL\n AND \"vector_space_id\" IS NOT NULL\n )\n )\n AND (\n \"binding_reason\" = 'candidate-switch'\n OR (\"binding_reason\" IN ('legacy-bootstrap', 'content-publication') AND \"activated_at\" IS NOT NULL)\n )\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"embedding_profile_kind\",\n \"embedding_profile_revision_id\", \"embedding_profile_revision\",\n \"embedding_profile_snapshot_digest\"\n ) REFERENCES \"knowledge_space_profile_revisions\" (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"id\", \"revision\", \"snapshot_digest\"\n ) ON DELETE RESTRICT,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"retrieval_profile_kind\",\n \"retrieval_profile_revision_id\", \"retrieval_profile_revision\",\n \"retrieval_profile_snapshot_digest\"\n ) REFERENCES \"knowledge_space_profile_revisions\" (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"id\", \"revision\", \"snapshot_digest\"\n ) ON DELETE RESTRICT,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"publication_id\", \"publication_fingerprint\"\n ) REFERENCES \"projection_set_publications\" (\n \"tenant_id\", \"knowledge_space_id\", \"id\", \"fingerprint\"\n ) ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_profile_publication_bindings_publication_uq\"\n ON \"knowledge_space_profile_publication_bindings\" (\n \"tenant_id\", \"knowledge_space_id\", \"publication_id\"\n );\nCREATE INDEX IF NOT EXISTS \"knowledge_space_profile_publication_bindings_activation_idx\"\n ON \"knowledge_space_profile_publication_bindings\" (\n \"tenant_id\", \"knowledge_space_id\", \"activated_at\",\n \"embedding_profile_revision\", \"retrieval_profile_revision\", \"publication_id\"\n );\n", path: "packages/database/migrations/0019_profile_publication_bindings.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0019_profile_publication_bindings\n-- Dialect: tidb\n\n-- Candidate-to-profile identity is persisted before the candidate is built. The activation\n-- transaction revalidates this immutable binding before changing either mutable head.\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_profile_revisions_attempt_fk_uq`\n ON `knowledge_space_profile_revisions` (\n `tenant_id`, `knowledge_space_id`, `kind`, `id`, `revision`, `snapshot_digest`\n );\n\n-- Legacy attempts may leave these columns NULL during the rolling upgrade. New writers persist\n-- both exact profile snapshots at attempt creation. Fixed kind columns participate in the foreign\n-- keys so embedding and retrieval snapshots cannot be swapped accidentally.\nALTER TABLE `document_compilation_attempts`\n ADD COLUMN IF NOT EXISTS `embedding_profile_kind` VARCHAR(16),\n ADD COLUMN IF NOT EXISTS `embedding_profile_revision_id` CHAR(36),\n ADD COLUMN IF NOT EXISTS `embedding_profile_revision` INT,\n ADD COLUMN IF NOT EXISTS `embedding_profile_snapshot_digest` CHAR(64),\n ADD COLUMN IF NOT EXISTS `retrieval_profile_kind` VARCHAR(16),\n ADD COLUMN IF NOT EXISTS `retrieval_profile_revision_id` CHAR(36),\n ADD COLUMN IF NOT EXISTS `retrieval_profile_revision` INT,\n ADD COLUMN IF NOT EXISTS `retrieval_profile_snapshot_digest` CHAR(64);\n\nSET @attempt_embedding_profile_ck_exists = (\n SELECT COUNT(*) FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE() AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_embedding_profile_ck'\n);\nSET @attempt_embedding_profile_ck_ddl = IF(\n @attempt_embedding_profile_ck_exists = 0,\n 'ALTER TABLE `document_compilation_attempts` ADD CONSTRAINT `document_compilation_attempts_embedding_profile_ck` CHECK ((`embedding_profile_kind` IS NULL AND `embedding_profile_revision_id` IS NULL AND `embedding_profile_revision` IS NULL AND `embedding_profile_snapshot_digest` IS NULL) OR (`embedding_profile_kind` = ''embedding'' AND `embedding_profile_revision_id` IS NOT NULL AND `embedding_profile_revision` >= 1 AND `embedding_profile_snapshot_digest` IS NOT NULL))',\n 'DO 0'\n);\nPREPARE attempt_embedding_profile_ck_statement FROM @attempt_embedding_profile_ck_ddl;\nEXECUTE attempt_embedding_profile_ck_statement;\nDEALLOCATE PREPARE attempt_embedding_profile_ck_statement;\n\nSET @attempt_retrieval_profile_ck_exists = (\n SELECT COUNT(*) FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE() AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_retrieval_profile_ck'\n);\nSET @attempt_retrieval_profile_ck_ddl = IF(\n @attempt_retrieval_profile_ck_exists = 0,\n 'ALTER TABLE `document_compilation_attempts` ADD CONSTRAINT `document_compilation_attempts_retrieval_profile_ck` CHECK ((`retrieval_profile_kind` IS NULL AND `retrieval_profile_revision_id` IS NULL AND `retrieval_profile_revision` IS NULL AND `retrieval_profile_snapshot_digest` IS NULL) OR (`retrieval_profile_kind` = ''retrieval'' AND `retrieval_profile_revision_id` IS NOT NULL AND `retrieval_profile_revision` >= 1 AND `retrieval_profile_snapshot_digest` IS NOT NULL))',\n 'DO 0'\n);\nPREPARE attempt_retrieval_profile_ck_statement FROM @attempt_retrieval_profile_ck_ddl;\nEXECUTE attempt_retrieval_profile_ck_statement;\nDEALLOCATE PREPARE attempt_retrieval_profile_ck_statement;\n\nSET @attempt_profile_tuple_ck_exists = (\n SELECT COUNT(*) FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE() AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_profile_tuple_ck'\n);\nSET @attempt_profile_tuple_ck_ddl = IF(\n @attempt_profile_tuple_ck_exists = 0,\n 'ALTER TABLE `document_compilation_attempts` ADD CONSTRAINT `document_compilation_attempts_profile_tuple_ck` CHECK (((`embedding_profile_kind` IS NULL AND `embedding_profile_revision_id` IS NULL AND `embedding_profile_revision` IS NULL AND `embedding_profile_snapshot_digest` IS NULL AND `retrieval_profile_kind` IS NULL AND `retrieval_profile_revision_id` IS NULL AND `retrieval_profile_revision` IS NULL AND `retrieval_profile_snapshot_digest` IS NULL) OR (`retrieval_profile_kind` = ''retrieval'' AND `retrieval_profile_revision_id` IS NOT NULL AND `retrieval_profile_revision` >= 1 AND `retrieval_profile_snapshot_digest` IS NOT NULL AND ((`embedding_profile_kind` IS NULL AND `embedding_profile_revision_id` IS NULL AND `embedding_profile_revision` IS NULL AND `embedding_profile_snapshot_digest` IS NULL) OR (`embedding_profile_kind` = ''embedding'' AND `embedding_profile_revision_id` IS NOT NULL AND `embedding_profile_revision` >= 1 AND `embedding_profile_snapshot_digest` IS NOT NULL)))))',\n 'DO 0'\n);\nPREPARE attempt_profile_tuple_ck_statement FROM @attempt_profile_tuple_ck_ddl;\nEXECUTE attempt_profile_tuple_ck_statement;\nDEALLOCATE PREPARE attempt_profile_tuple_ck_statement;\n\nSET @attempt_embedding_profile_fk_exists = (\n SELECT COUNT(*) FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE() AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_embedding_profile_fk'\n);\nSET @attempt_embedding_profile_fk_ddl = IF(\n @attempt_embedding_profile_fk_exists = 0,\n 'ALTER TABLE `document_compilation_attempts` ADD CONSTRAINT `document_compilation_attempts_embedding_profile_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `embedding_profile_kind`, `embedding_profile_revision_id`, `embedding_profile_revision`, `embedding_profile_snapshot_digest`) REFERENCES `knowledge_space_profile_revisions` (`tenant_id`, `knowledge_space_id`, `kind`, `id`, `revision`, `snapshot_digest`)',\n 'DO 0'\n);\nPREPARE attempt_embedding_profile_fk_statement FROM @attempt_embedding_profile_fk_ddl;\nEXECUTE attempt_embedding_profile_fk_statement;\nDEALLOCATE PREPARE attempt_embedding_profile_fk_statement;\n\nSET @attempt_retrieval_profile_fk_exists = (\n SELECT COUNT(*) FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE() AND table_name = 'document_compilation_attempts'\n AND constraint_name = 'document_compilation_attempts_retrieval_profile_fk'\n);\nSET @attempt_retrieval_profile_fk_ddl = IF(\n @attempt_retrieval_profile_fk_exists = 0,\n 'ALTER TABLE `document_compilation_attempts` ADD CONSTRAINT `document_compilation_attempts_retrieval_profile_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `retrieval_profile_kind`, `retrieval_profile_revision_id`, `retrieval_profile_revision`, `retrieval_profile_snapshot_digest`) REFERENCES `knowledge_space_profile_revisions` (`tenant_id`, `knowledge_space_id`, `kind`, `id`, `revision`, `snapshot_digest`)',\n 'DO 0'\n);\nPREPARE attempt_retrieval_profile_fk_statement FROM @attempt_retrieval_profile_fk_ddl;\nEXECUTE attempt_retrieval_profile_fk_statement;\nDEALLOCATE PREPARE attempt_retrieval_profile_fk_statement;\n\nCREATE TABLE IF NOT EXISTS `knowledge_space_profile_publication_bindings` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `changed_kind` VARCHAR(16) NOT NULL,\n `binding_reason` VARCHAR(24) NOT NULL,\n `embedding_profile_kind` VARCHAR(16),\n `embedding_profile_revision_id` CHAR(36),\n `embedding_profile_revision` INT,\n `embedding_profile_snapshot_digest` CHAR(64),\n `retrieval_profile_kind` VARCHAR(16) NOT NULL,\n `retrieval_profile_revision_id` CHAR(36) NOT NULL,\n `retrieval_profile_revision` INT NOT NULL,\n `retrieval_profile_snapshot_digest` CHAR(64) NOT NULL,\n `vector_space_id` VARCHAR(87),\n `publication_id` CHAR(36) NOT NULL,\n `publication_fingerprint` VARCHAR(86) NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `activated_at` DATETIME(3),\n CONSTRAINT `knowledge_space_profile_publication_bindings_kind_ck`\n CHECK (\n `changed_kind` IN ('embedding', 'retrieval', 'bootstrap', 'content')\n AND `retrieval_profile_kind` = 'retrieval'\n AND (`embedding_profile_kind` IS NULL OR `embedding_profile_kind` = 'embedding')\n ),\n CONSTRAINT `knowledge_space_profile_publication_bindings_reason_ck`\n CHECK (\n (`binding_reason` = 'candidate-switch' AND `changed_kind` IN ('embedding', 'retrieval'))\n OR (`binding_reason` = 'legacy-bootstrap' AND `changed_kind` = 'bootstrap')\n OR (`binding_reason` = 'content-publication' AND `changed_kind` = 'content')\n ),\n CONSTRAINT `knowledge_space_profile_publication_bindings_shape_ck`\n CHECK (\n `retrieval_profile_revision` >= 1\n AND (\n (\n `embedding_profile_kind` IS NULL\n AND `embedding_profile_revision_id` IS NULL\n AND `embedding_profile_revision` IS NULL\n AND `embedding_profile_snapshot_digest` IS NULL\n AND `vector_space_id` IS NULL\n AND `changed_kind` IN ('retrieval', 'bootstrap', 'content')\n )\n OR (\n `embedding_profile_kind` = 'embedding'\n AND `embedding_profile_revision_id` IS NOT NULL\n AND `embedding_profile_revision` >= 1\n AND `embedding_profile_snapshot_digest` IS NOT NULL\n AND `vector_space_id` IS NOT NULL\n )\n )\n AND (\n `binding_reason` = 'candidate-switch'\n OR (`binding_reason` IN ('legacy-bootstrap', 'content-publication') AND `activated_at` IS NOT NULL)\n )\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n -- TiDB's omitted referential action is RESTRICT. It is deliberately omitted because explicit\n -- RESTRICT makes these CHECK-constrained child columns ineligible on supported TiDB versions.\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `embedding_profile_kind`,\n `embedding_profile_revision_id`, `embedding_profile_revision`,\n `embedding_profile_snapshot_digest`\n ) REFERENCES `knowledge_space_profile_revisions` (\n `tenant_id`, `knowledge_space_id`, `kind`, `id`, `revision`, `snapshot_digest`\n ),\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `retrieval_profile_kind`,\n `retrieval_profile_revision_id`, `retrieval_profile_revision`,\n `retrieval_profile_snapshot_digest`\n ) REFERENCES `knowledge_space_profile_revisions` (\n `tenant_id`, `knowledge_space_id`, `kind`, `id`, `revision`, `snapshot_digest`\n ),\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `publication_id`, `publication_fingerprint`\n ) REFERENCES `projection_set_publications` (\n `tenant_id`, `knowledge_space_id`, `id`, `fingerprint`\n )\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_profile_publication_bindings_publication_uq`\n ON `knowledge_space_profile_publication_bindings` (\n `tenant_id`, `knowledge_space_id`, `publication_id`\n );\nCREATE INDEX IF NOT EXISTS `knowledge_space_profile_publication_bindings_activation_idx`\n ON `knowledge_space_profile_publication_bindings` (\n `tenant_id`, `knowledge_space_id`, `activated_at`,\n `embedding_profile_revision`, `retrieval_profile_revision`, `publication_id`\n );\n", path: "packages/database/migrations/0019_profile_publication_bindings.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0020_profile_migration_runs\n-- Dialect: postgres\n\n-- A profile change over a populated space is a durable rebuild. The run freezes the old\n-- publication, both old profile heads, and the immutable candidate revision before any worker\n-- starts. Only the joint profile/publication CAS may make the candidate visible.\nCREATE TABLE IF NOT EXISTS \"knowledge_space_profile_migration_runs\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"changed_kind\" VARCHAR(16) NOT NULL,\n \"rebuild_scope\" VARCHAR(48) NOT NULL,\n \"candidate_profile_kind\" VARCHAR(16) NOT NULL,\n \"candidate_profile_revision_id\" UUID NOT NULL,\n \"candidate_profile_revision\" INTEGER NOT NULL,\n \"candidate_profile_snapshot_digest\" CHAR(64) NOT NULL,\n \"base_embedding_profile_kind\" VARCHAR(16),\n \"base_embedding_profile_revision_id\" UUID,\n \"base_embedding_profile_revision\" INTEGER,\n \"base_embedding_profile_snapshot_digest\" CHAR(64),\n \"base_retrieval_profile_kind\" VARCHAR(16) NOT NULL,\n \"base_retrieval_profile_revision_id\" UUID NOT NULL,\n \"base_retrieval_profile_revision\" INTEGER NOT NULL,\n \"base_retrieval_profile_snapshot_digest\" CHAR(64) NOT NULL,\n \"base_publication_id\" UUID NOT NULL,\n \"base_publication_fingerprint\" VARCHAR(86) NOT NULL,\n \"base_publication_head_revision\" INTEGER NOT NULL,\n \"candidate_publication_id\" UUID,\n \"candidate_publication_fingerprint\" VARCHAR(86),\n \"permission_snapshot_id\" UUID NOT NULL,\n \"permission_snapshot_revision\" INTEGER NOT NULL,\n \"requested_by_subject_id\" VARCHAR(255) NOT NULL,\n \"access_channel\" VARCHAR(16) NOT NULL,\n \"idempotency_key\" VARCHAR(255) NOT NULL,\n \"idempotency_digest\" CHAR(64) NOT NULL,\n \"run_state\" VARCHAR(16) NOT NULL,\n \"active_slot\" INTEGER,\n \"checkpoint\" VARCHAR(32) NOT NULL,\n \"evaluation_summary\" JSONB,\n \"execution_attempts\" INTEGER NOT NULL,\n \"max_execution_attempts\" INTEGER NOT NULL,\n \"worker_id\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"heartbeat_at\" TIMESTAMPTZ,\n \"row_version\" INTEGER NOT NULL,\n \"last_error_code\" VARCHAR(64),\n \"last_error_message\" TEXT,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"completed_at\" TIMESTAMPTZ,\n \"canceled_at\" TIMESTAMPTZ,\n CONSTRAINT \"knowledge_space_profile_migration_runs_kind_ck\"\n CHECK (\n \"changed_kind\" IN ('embedding', 'retrieval')\n AND \"candidate_profile_kind\" = \"changed_kind\"\n AND \"base_retrieval_profile_kind\" = 'retrieval'\n AND (\"base_embedding_profile_kind\" IS NULL OR \"base_embedding_profile_kind\" = 'embedding')\n ),\n CONSTRAINT \"knowledge_space_profile_migration_runs_scope_ck\"\n CHECK (\n (\"changed_kind\" = 'embedding' AND \"rebuild_scope\" = 'full-vector-space')\n OR (\"changed_kind\" = 'retrieval' AND \"rebuild_scope\" IN (\n 'clone-publication', 'full-page-index-summary-outline'\n ))\n ),\n CONSTRAINT \"knowledge_space_profile_migration_runs_state_ck\"\n CHECK (\"run_state\" IN ('queued', 'running', 'succeeded', 'failed', 'canceled')),\n CONSTRAINT \"knowledge_space_profile_migration_runs_checkpoint_ck\"\n CHECK (\"checkpoint\" IN ('queued', 'candidate-built', 'evaluated', 'activated')),\n CONSTRAINT \"knowledge_space_profile_migration_runs_idempotency_digest_ck\"\n CHECK (\"idempotency_digest\" ~ '^[a-f0-9]{64}$'),\n CONSTRAINT \"knowledge_space_profile_migration_runs_positive_ck\"\n CHECK (\n \"candidate_profile_revision\" >= 1\n AND \"base_retrieval_profile_revision\" >= 1\n AND \"base_publication_head_revision\" >= 1\n AND \"permission_snapshot_revision\" >= 1\n AND \"execution_attempts\" >= 0\n AND \"max_execution_attempts\" >= 1\n AND \"execution_attempts\" <= \"max_execution_attempts\"\n AND \"row_version\" >= 1\n AND (\"active_slot\" IS NULL OR \"active_slot\" = 1)\n ),\n CONSTRAINT \"knowledge_space_profile_migration_runs_embedding_ref_ck\"\n CHECK (\n (\"base_embedding_profile_kind\" IS NULL\n AND \"base_embedding_profile_revision_id\" IS NULL\n AND \"base_embedding_profile_revision\" IS NULL\n AND \"base_embedding_profile_snapshot_digest\" IS NULL)\n OR (\"base_embedding_profile_kind\" = 'embedding'\n AND \"base_embedding_profile_revision_id\" IS NOT NULL\n AND \"base_embedding_profile_revision\" >= 1\n AND \"base_embedding_profile_snapshot_digest\" IS NOT NULL)\n ),\n CONSTRAINT \"knowledge_space_profile_migration_runs_candidate_publication_ck\"\n CHECK (\n (\"candidate_publication_id\" IS NULL AND \"candidate_publication_fingerprint\" IS NULL)\n OR (\"candidate_publication_id\" IS NOT NULL AND \"candidate_publication_fingerprint\" IS NOT NULL)\n ),\n CONSTRAINT \"knowledge_space_profile_migration_runs_checkpoint_shape_ck\"\n CHECK (\n (\n \"checkpoint\" = 'queued'\n AND \"candidate_publication_id\" IS NULL\n AND \"candidate_publication_fingerprint\" IS NULL\n AND \"evaluation_summary\" IS NULL\n )\n OR (\n \"checkpoint\" = 'candidate-built'\n AND \"candidate_publication_id\" IS NOT NULL\n AND \"candidate_publication_fingerprint\" IS NOT NULL\n AND \"evaluation_summary\" IS NULL\n )\n OR (\n \"checkpoint\" IN ('evaluated', 'activated')\n AND \"candidate_publication_id\" IS NOT NULL\n AND \"candidate_publication_fingerprint\" IS NOT NULL\n AND \"evaluation_summary\" IS NOT NULL\n AND jsonb_typeof(\"evaluation_summary\") = 'object'\n )\n ),\n CONSTRAINT \"knowledge_space_profile_migration_runs_lease_ck\"\n CHECK (\n (\"run_state\" = 'running' AND \"worker_id\" IS NOT NULL AND \"lease_token\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL AND \"heartbeat_at\" IS NOT NULL)\n OR (\"run_state\" <> 'running' AND \"worker_id\" IS NULL AND \"lease_token\" IS NULL\n AND \"lease_expires_at\" IS NULL AND \"heartbeat_at\" IS NULL)\n ),\n CONSTRAINT \"knowledge_space_profile_migration_runs_lease_token_ck\"\n CHECK (\n \"lease_token\" IS NULL\n OR \"lease_token\" <> '00000000-0000-0000-0000-000000000000'::uuid\n ),\n CONSTRAINT \"knowledge_space_profile_migration_runs_lifecycle_ck\"\n CHECK (\n (\"run_state\" IN ('queued', 'running') AND \"active_slot\" = 1 AND \"completed_at\" IS NULL\n AND \"canceled_at\" IS NULL)\n OR (\"run_state\" = 'succeeded' AND \"checkpoint\" = 'activated'\n AND \"active_slot\" IS NULL AND \"completed_at\" IS NOT NULL AND \"canceled_at\" IS NULL\n AND \"last_error_code\" IS NULL AND \"last_error_message\" IS NULL)\n OR (\"run_state\" = 'failed' AND \"completed_at\" IS NOT NULL\n AND \"active_slot\" IS NULL AND \"canceled_at\" IS NULL AND \"last_error_code\" IS NOT NULL\n AND \"last_error_message\" IS NOT NULL)\n OR (\"run_state\" = 'canceled' AND \"completed_at\" IS NOT NULL\n AND \"active_slot\" IS NULL AND \"canceled_at\" IS NOT NULL)\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"candidate_profile_kind\",\n \"candidate_profile_revision_id\", \"candidate_profile_revision\",\n \"candidate_profile_snapshot_digest\"\n ) REFERENCES \"knowledge_space_profile_revisions\" (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"id\", \"revision\", \"snapshot_digest\"\n ) ON DELETE RESTRICT,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"base_embedding_profile_kind\",\n \"base_embedding_profile_revision_id\", \"base_embedding_profile_revision\",\n \"base_embedding_profile_snapshot_digest\"\n ) REFERENCES \"knowledge_space_profile_revisions\" (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"id\", \"revision\", \"snapshot_digest\"\n ) ON DELETE RESTRICT,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"base_retrieval_profile_kind\",\n \"base_retrieval_profile_revision_id\", \"base_retrieval_profile_revision\",\n \"base_retrieval_profile_snapshot_digest\"\n ) REFERENCES \"knowledge_space_profile_revisions\" (\n \"tenant_id\", \"knowledge_space_id\", \"kind\", \"id\", \"revision\", \"snapshot_digest\"\n ) ON DELETE RESTRICT,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"base_publication_id\", \"base_publication_fingerprint\"\n ) REFERENCES \"projection_set_publications\" (\n \"tenant_id\", \"knowledge_space_id\", \"id\", \"fingerprint\"\n ) ON DELETE RESTRICT,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"candidate_publication_id\",\n \"candidate_publication_fingerprint\"\n ) REFERENCES \"projection_set_publications\" (\n \"tenant_id\", \"knowledge_space_id\", \"id\", \"fingerprint\"\n ) ON DELETE RESTRICT,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"permission_snapshot_id\",\n \"requested_by_subject_id\", \"access_channel\"\n )\n REFERENCES \"knowledge_space_permission_snapshots\" (\n \"tenant_id\", \"knowledge_space_id\", \"id\", \"subject_id\", \"access_channel\"\n ) ON DELETE RESTRICT\n);\n\n-- Recover a table left by an earlier attempt that failed while creating the oversized composite\n-- key. The original tuple remains stored and is compared after digest lookup for collision safety.\nALTER TABLE \"knowledge_space_profile_migration_runs\"\n ADD COLUMN IF NOT EXISTS \"idempotency_digest\" CHAR(64);\nUPDATE \"knowledge_space_profile_migration_runs\"\nSET \"idempotency_digest\" = encode(sha256(convert_to(\n 'v1|'\n || octet_length(\"tenant_id\")::text || ':' || \"tenant_id\" || '|'\n || octet_length(\"knowledge_space_id\"::text)::text || ':' || \"knowledge_space_id\"::text || '|'\n || octet_length(\"requested_by_subject_id\")::text || ':' || \"requested_by_subject_id\" || '|'\n || octet_length(\"idempotency_key\")::text || ':' || \"idempotency_key\" || '|',\n 'UTF8'\n)), 'hex')\nWHERE \"idempotency_digest\" IS NULL;\nALTER TABLE \"knowledge_space_profile_migration_runs\"\n ALTER COLUMN \"idempotency_digest\" SET NOT NULL;\n\nDROP INDEX IF EXISTS \"knowledge_space_profile_migration_runs_idempotency_uq\";\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_profile_migration_runs_idempotency_digest_uq\"\n ON \"knowledge_space_profile_migration_runs\" (\"idempotency_digest\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_profile_migration_runs_active_uq\"\n ON \"knowledge_space_profile_migration_runs\" (\n \"tenant_id\", \"knowledge_space_id\", \"active_slot\"\n );\nCREATE INDEX IF NOT EXISTS \"knowledge_space_profile_migration_runs_claim_idx\"\n ON \"knowledge_space_profile_migration_runs\" (\n \"run_state\", \"lease_expires_at\", \"updated_at\", \"id\"\n );\nCREATE INDEX IF NOT EXISTS \"knowledge_space_profile_migration_runs_space_idx\"\n ON \"knowledge_space_profile_migration_runs\" (\n \"tenant_id\", \"knowledge_space_id\", \"created_at\", \"id\"\n );\n\nCREATE TABLE IF NOT EXISTS \"knowledge_space_profile_migration_outbox\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"run_id\" UUID NOT NULL,\n \"delivery_revision\" INTEGER NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"available_at\" TIMESTAMPTZ NOT NULL,\n \"locked_by\" VARCHAR(255),\n \"lock_token\" UUID,\n \"locked_until\" TIMESTAMPTZ,\n \"last_error\" TEXT,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"delivered_at\" TIMESTAMPTZ,\n CONSTRAINT \"knowledge_space_profile_migration_outbox_state_ck\"\n CHECK (\"status\" IN ('pending', 'leased', 'completed', 'canceled')),\n CONSTRAINT \"knowledge_space_profile_migration_outbox_positive_ck\"\n CHECK (\"delivery_revision\" >= 1),\n CONSTRAINT \"knowledge_space_profile_migration_outbox_lock_ck\"\n CHECK (\n (\"status\" = 'leased' AND \"locked_by\" IS NOT NULL AND \"lock_token\" IS NOT NULL\n AND \"locked_until\" IS NOT NULL)\n OR (\"status\" <> 'leased' AND \"locked_by\" IS NULL AND \"lock_token\" IS NULL\n AND \"locked_until\" IS NULL)\n ),\n CONSTRAINT \"knowledge_space_profile_migration_outbox_lock_token_ck\"\n CHECK (\n \"lock_token\" IS NULL\n OR \"lock_token\" <> '00000000-0000-0000-0000-000000000000'::uuid\n ),\n FOREIGN KEY (\"run_id\")\n REFERENCES \"knowledge_space_profile_migration_runs\" (\"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_profile_migration_outbox_delivery_uq\"\n ON \"knowledge_space_profile_migration_outbox\" (\"run_id\", \"delivery_revision\");\nCREATE INDEX IF NOT EXISTS \"knowledge_space_profile_migration_outbox_claim_idx\"\n ON \"knowledge_space_profile_migration_outbox\" (\n \"status\", \"available_at\", \"locked_until\", \"id\"\n );\n", path: "packages/database/migrations/0020_profile_migration_runs.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0020_profile_migration_runs\n-- Dialect: tidb\n\nCREATE TABLE IF NOT EXISTS `knowledge_space_profile_migration_runs` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `changed_kind` VARCHAR(16) NOT NULL,\n `rebuild_scope` VARCHAR(48) NOT NULL,\n `candidate_profile_kind` VARCHAR(16) NOT NULL,\n `candidate_profile_revision_id` CHAR(36) NOT NULL,\n `candidate_profile_revision` INT NOT NULL,\n `candidate_profile_snapshot_digest` CHAR(64) NOT NULL,\n `base_embedding_profile_kind` VARCHAR(16),\n `base_embedding_profile_revision_id` CHAR(36),\n `base_embedding_profile_revision` INT,\n `base_embedding_profile_snapshot_digest` CHAR(64),\n `base_retrieval_profile_kind` VARCHAR(16) NOT NULL,\n `base_retrieval_profile_revision_id` CHAR(36) NOT NULL,\n `base_retrieval_profile_revision` INT NOT NULL,\n `base_retrieval_profile_snapshot_digest` CHAR(64) NOT NULL,\n `base_publication_id` CHAR(36) NOT NULL,\n `base_publication_fingerprint` VARCHAR(86) NOT NULL,\n `base_publication_head_revision` INT NOT NULL,\n `candidate_publication_id` CHAR(36),\n `candidate_publication_fingerprint` VARCHAR(86),\n `permission_snapshot_id` CHAR(36) NOT NULL,\n `permission_snapshot_revision` INT NOT NULL,\n `requested_by_subject_id` VARCHAR(255) NOT NULL,\n `access_channel` VARCHAR(16) NOT NULL,\n `idempotency_key` VARCHAR(255) NOT NULL,\n `idempotency_digest` CHAR(64) NOT NULL,\n `run_state` VARCHAR(16) NOT NULL,\n `active_slot` INT,\n `checkpoint` VARCHAR(32) NOT NULL,\n `evaluation_summary` JSON,\n `execution_attempts` INT NOT NULL,\n `max_execution_attempts` INT NOT NULL,\n `worker_id` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `heartbeat_at` DATETIME(3),\n `row_version` INT NOT NULL,\n `last_error_code` VARCHAR(64),\n `last_error_message` TEXT,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `completed_at` DATETIME(3),\n `canceled_at` DATETIME(3),\n CONSTRAINT `knowledge_space_profile_migration_runs_kind_ck`\n CHECK (\n `changed_kind` IN ('embedding', 'retrieval')\n AND `candidate_profile_kind` = `changed_kind`\n AND `base_retrieval_profile_kind` = 'retrieval'\n AND (`base_embedding_profile_kind` IS NULL OR `base_embedding_profile_kind` = 'embedding')\n ),\n CONSTRAINT `knowledge_space_profile_migration_runs_scope_ck`\n CHECK (\n (`changed_kind` = 'embedding' AND `rebuild_scope` = 'full-vector-space')\n OR (`changed_kind` = 'retrieval' AND `rebuild_scope` IN (\n 'clone-publication', 'full-page-index-summary-outline'\n ))\n ),\n CONSTRAINT `knowledge_space_profile_migration_runs_state_ck`\n CHECK (`run_state` IN ('queued', 'running', 'succeeded', 'failed', 'canceled')),\n CONSTRAINT `knowledge_space_profile_migration_runs_checkpoint_ck`\n CHECK (`checkpoint` IN ('queued', 'candidate-built', 'evaluated', 'activated')),\n CONSTRAINT `knowledge_space_profile_migration_runs_idempotency_digest_ck`\n CHECK (`idempotency_digest` REGEXP '^[a-f0-9]{64}$'),\n CONSTRAINT `knowledge_space_profile_migration_runs_positive_ck`\n CHECK (\n `candidate_profile_revision` >= 1\n AND `base_retrieval_profile_revision` >= 1\n AND `base_publication_head_revision` >= 1\n AND `permission_snapshot_revision` >= 1\n AND `execution_attempts` >= 0\n AND `max_execution_attempts` >= 1\n AND `execution_attempts` <= `max_execution_attempts`\n AND `row_version` >= 1\n AND (`active_slot` IS NULL OR `active_slot` = 1)\n ),\n CONSTRAINT `knowledge_space_profile_migration_runs_embedding_ref_ck`\n CHECK (\n (`base_embedding_profile_kind` IS NULL\n AND `base_embedding_profile_revision_id` IS NULL\n AND `base_embedding_profile_revision` IS NULL\n AND `base_embedding_profile_snapshot_digest` IS NULL)\n OR (`base_embedding_profile_kind` = 'embedding'\n AND `base_embedding_profile_revision_id` IS NOT NULL\n AND `base_embedding_profile_revision` >= 1\n AND `base_embedding_profile_snapshot_digest` IS NOT NULL)\n ),\n CONSTRAINT `knowledge_space_profile_migration_runs_candidate_publication_ck`\n CHECK (\n (`candidate_publication_id` IS NULL AND `candidate_publication_fingerprint` IS NULL)\n OR (`candidate_publication_id` IS NOT NULL AND `candidate_publication_fingerprint` IS NOT NULL)\n ),\n CONSTRAINT `knowledge_space_profile_migration_runs_checkpoint_shape_ck`\n CHECK (\n (\n `checkpoint` = 'queued'\n AND `candidate_publication_id` IS NULL\n AND `candidate_publication_fingerprint` IS NULL\n AND `evaluation_summary` IS NULL\n )\n OR (\n `checkpoint` = 'candidate-built'\n AND `candidate_publication_id` IS NOT NULL\n AND `candidate_publication_fingerprint` IS NOT NULL\n AND `evaluation_summary` IS NULL\n )\n OR (\n `checkpoint` IN ('evaluated', 'activated')\n AND `candidate_publication_id` IS NOT NULL\n AND `candidate_publication_fingerprint` IS NOT NULL\n AND `evaluation_summary` IS NOT NULL\n AND JSON_TYPE(`evaluation_summary`) = 'OBJECT'\n )\n ),\n CONSTRAINT `knowledge_space_profile_migration_runs_lease_ck`\n CHECK (\n (`run_state` = 'running' AND `worker_id` IS NOT NULL AND `lease_token` IS NOT NULL\n AND `lease_expires_at` IS NOT NULL AND `heartbeat_at` IS NOT NULL)\n OR (`run_state` <> 'running' AND `worker_id` IS NULL AND `lease_token` IS NULL\n AND `lease_expires_at` IS NULL AND `heartbeat_at` IS NULL)\n ),\n CONSTRAINT `knowledge_space_profile_migration_runs_lease_token_ck`\n CHECK (\n `lease_token` IS NULL OR (\n `lease_token` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `lease_token` <> '00000000-0000-0000-0000-000000000000'\n )\n ),\n CONSTRAINT `knowledge_space_profile_migration_runs_lifecycle_ck`\n CHECK (\n (`run_state` IN ('queued', 'running') AND `active_slot` = 1 AND `completed_at` IS NULL\n AND `canceled_at` IS NULL)\n OR (`run_state` = 'succeeded' AND `checkpoint` = 'activated'\n AND `active_slot` IS NULL AND `completed_at` IS NOT NULL AND `canceled_at` IS NULL\n AND `last_error_code` IS NULL AND `last_error_message` IS NULL)\n OR (`run_state` = 'failed' AND `completed_at` IS NOT NULL\n AND `active_slot` IS NULL AND `canceled_at` IS NULL AND `last_error_code` IS NOT NULL\n AND `last_error_message` IS NOT NULL)\n OR (`run_state` = 'canceled' AND `completed_at` IS NOT NULL\n AND `active_slot` IS NULL AND `canceled_at` IS NOT NULL)\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `candidate_profile_kind`,\n `candidate_profile_revision_id`, `candidate_profile_revision`,\n `candidate_profile_snapshot_digest`\n ) REFERENCES `knowledge_space_profile_revisions` (\n `tenant_id`, `knowledge_space_id`, `kind`, `id`, `revision`, `snapshot_digest`\n ),\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `base_embedding_profile_kind`,\n `base_embedding_profile_revision_id`, `base_embedding_profile_revision`,\n `base_embedding_profile_snapshot_digest`\n ) REFERENCES `knowledge_space_profile_revisions` (\n `tenant_id`, `knowledge_space_id`, `kind`, `id`, `revision`, `snapshot_digest`\n ),\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `base_retrieval_profile_kind`,\n `base_retrieval_profile_revision_id`, `base_retrieval_profile_revision`,\n `base_retrieval_profile_snapshot_digest`\n ) REFERENCES `knowledge_space_profile_revisions` (\n `tenant_id`, `knowledge_space_id`, `kind`, `id`, `revision`, `snapshot_digest`\n ),\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `base_publication_id`, `base_publication_fingerprint`\n ) REFERENCES `projection_set_publications` (\n `tenant_id`, `knowledge_space_id`, `id`, `fingerprint`\n ),\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `candidate_publication_id`,\n `candidate_publication_fingerprint`\n ) REFERENCES `projection_set_publications` (\n `tenant_id`, `knowledge_space_id`, `id`, `fingerprint`\n ),\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `permission_snapshot_id`,\n `requested_by_subject_id`, `access_channel`\n )\n REFERENCES `knowledge_space_permission_snapshots` (\n `tenant_id`, `knowledge_space_id`, `id`, `subject_id`, `access_channel`\n )\n);\n\n-- Recover a table left by the former overlong composite index. Keep the original tuple so a\n-- digest collision is detected by the repository rather than treated as a replay.\nALTER TABLE `knowledge_space_profile_migration_runs`\n ADD COLUMN IF NOT EXISTS `idempotency_digest` CHAR(64);\nUPDATE `knowledge_space_profile_migration_runs`\nSET `idempotency_digest` = SHA2(CONCAT(\n 'v1|', OCTET_LENGTH(`tenant_id`), ':', `tenant_id`, '|',\n OCTET_LENGTH(`knowledge_space_id`), ':', `knowledge_space_id`, '|',\n OCTET_LENGTH(`requested_by_subject_id`), ':', `requested_by_subject_id`, '|',\n OCTET_LENGTH(`idempotency_key`), ':', `idempotency_key`, '|'\n), 256)\nWHERE `idempotency_digest` IS NULL;\nALTER TABLE `knowledge_space_profile_migration_runs`\n MODIFY COLUMN `idempotency_digest` CHAR(64) NOT NULL;\n\nDROP INDEX IF EXISTS `knowledge_space_profile_migration_runs_idempotency_uq`\n ON `knowledge_space_profile_migration_runs`;\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_profile_migration_runs_idempotency_digest_uq`\n ON `knowledge_space_profile_migration_runs` (`idempotency_digest`);\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_profile_migration_runs_active_uq`\n ON `knowledge_space_profile_migration_runs` (\n `tenant_id`, `knowledge_space_id`, `active_slot`\n );\nCREATE INDEX IF NOT EXISTS `knowledge_space_profile_migration_runs_claim_idx`\n ON `knowledge_space_profile_migration_runs` (\n `run_state`, `lease_expires_at`, `updated_at`, `id`\n );\nCREATE INDEX IF NOT EXISTS `knowledge_space_profile_migration_runs_space_idx`\n ON `knowledge_space_profile_migration_runs` (\n `tenant_id`, `knowledge_space_id`, `created_at`, `id`\n );\n\nCREATE TABLE IF NOT EXISTS `knowledge_space_profile_migration_outbox` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `run_id` CHAR(36) NOT NULL,\n `delivery_revision` INT NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `available_at` DATETIME(3) NOT NULL,\n `locked_by` VARCHAR(255),\n `lock_token` CHAR(36),\n `locked_until` DATETIME(3),\n `last_error` TEXT,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `delivered_at` DATETIME(3),\n CONSTRAINT `knowledge_space_profile_migration_outbox_state_ck`\n CHECK (`status` IN ('pending', 'leased', 'completed', 'canceled')),\n CONSTRAINT `knowledge_space_profile_migration_outbox_positive_ck`\n CHECK (`delivery_revision` >= 1),\n CONSTRAINT `knowledge_space_profile_migration_outbox_lock_ck`\n CHECK (\n (`status` = 'leased' AND `locked_by` IS NOT NULL AND `lock_token` IS NOT NULL\n AND `locked_until` IS NOT NULL)\n OR (`status` <> 'leased' AND `locked_by` IS NULL AND `lock_token` IS NULL\n AND `locked_until` IS NULL)\n ),\n CONSTRAINT `knowledge_space_profile_migration_outbox_lock_token_ck`\n CHECK (\n `lock_token` IS NULL OR (\n `lock_token` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `lock_token` <> '00000000-0000-0000-0000-000000000000'\n )\n ),\n FOREIGN KEY (`run_id`)\n REFERENCES `knowledge_space_profile_migration_runs` (`id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_profile_migration_outbox_delivery_uq`\n ON `knowledge_space_profile_migration_outbox` (`run_id`, `delivery_revision`);\nCREATE INDEX IF NOT EXISTS `knowledge_space_profile_migration_outbox_claim_idx`\n ON `knowledge_space_profile_migration_outbox` (\n `status`, `available_at`, `locked_until`, `id`\n );\n", path: "packages/database/migrations/0020_profile_migration_runs.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0021_source_product_workflows\n-- Dialect: postgres\n\nCREATE TABLE IF NOT EXISTS \"source_connections\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"provider_id\" VARCHAR(128) NOT NULL,\n \"name\" VARCHAR(160) NOT NULL,\n \"auth_kind\" VARCHAR(16) NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"configuration\" JSONB NOT NULL,\n \"credential_ref\" VARCHAR(255),\n \"scopes\" JSONB NOT NULL,\n \"expires_at\" TIMESTAMPTZ,\n \"last_error_code\" VARCHAR(64),\n \"version\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"source_connections_auth_kind_ck\"\n CHECK (\"auth_kind\" IN ('api-key', 'endpoint', 'oauth2')),\n CONSTRAINT \"source_connections_status_ck\"\n CHECK (\"status\" IN ('provisioning', 'active', 'expired', 'error', 'revoked')),\n CONSTRAINT \"source_connections_version_ck\" CHECK (\"version\" >= 1),\n CONSTRAINT \"source_connections_secret_ck\" CHECK (\n (\"status\" = 'revoked' AND \"credential_ref\" IS NULL)\n OR \"status\" <> 'revoked'\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_connections_scope_id_uq\"\n ON \"source_connections\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_connections_space_id_uq\"\n ON \"source_connections\" (\"knowledge_space_id\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_connections_credential_ref_uq\"\n ON \"source_connections\" (\"credential_ref\") WHERE \"credential_ref\" IS NOT NULL;\nCREATE INDEX IF NOT EXISTS \"source_connections_scope_status_idx\"\n ON \"source_connections\" (\n \"tenant_id\", \"knowledge_space_id\", \"status\", \"created_at\", \"id\"\n );\n\nCREATE TABLE IF NOT EXISTS \"source_oauth_transactions\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"connection_id\" UUID NOT NULL,\n \"requested_by_subject_id\" VARCHAR(255) NOT NULL,\n \"access_channel\" VARCHAR(16) NOT NULL,\n \"permission_snapshot_id\" UUID NOT NULL,\n \"permission_snapshot_revision\" INTEGER NOT NULL,\n \"api_key_id\" UUID,\n \"state_hash\" CHAR(64) NOT NULL,\n \"verifier_ref\" VARCHAR(255) NOT NULL,\n \"redirect_uri\" VARCHAR(2048) NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"expires_at\" TIMESTAMPTZ NOT NULL,\n \"consumed_at\" TIMESTAMPTZ,\n \"completed_at\" TIMESTAMPTZ,\n CONSTRAINT \"source_oauth_transactions_state_hash_ck\"\n CHECK (\"state_hash\" ~ '^[a-f0-9]{64}$'),\n CONSTRAINT \"source_oauth_transactions_status_ck\"\n CHECK (\"status\" IN ('pending', 'exchanging', 'completed', 'failed')),\n CONSTRAINT \"source_oauth_transactions_channel_ck\"\n CHECK (\"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')),\n CONSTRAINT \"source_oauth_transactions_permission_ck\"\n CHECK (\"permission_snapshot_revision\" >= 1),\n CONSTRAINT \"source_oauth_transactions_lifecycle_ck\" CHECK (\n (\"status\" = 'pending' AND \"consumed_at\" IS NULL AND \"completed_at\" IS NULL)\n OR (\"status\" IN ('exchanging', 'failed') AND \"consumed_at\" IS NOT NULL)\n OR (\"status\" = 'completed' AND \"consumed_at\" IS NOT NULL AND \"completed_at\" IS NOT NULL)\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"connection_id\")\n REFERENCES \"source_connections\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE CASCADE,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"permission_snapshot_id\",\n \"requested_by_subject_id\", \"access_channel\"\n ) REFERENCES \"knowledge_space_permission_snapshots\" (\n \"tenant_id\", \"knowledge_space_id\", \"id\", \"subject_id\", \"access_channel\"\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"api_key_id\")\n REFERENCES \"knowledge_space_api_keys\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_oauth_transactions_state_hash_uq\"\n ON \"source_oauth_transactions\" (\"state_hash\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_oauth_transactions_verifier_ref_uq\"\n ON \"source_oauth_transactions\" (\"verifier_ref\");\nCREATE INDEX IF NOT EXISTS \"source_oauth_transactions_expiry_idx\"\n ON \"source_oauth_transactions\" (\"status\", \"expires_at\", \"id\");\n\n-- Independent cleanup ledger: it intentionally survives connection/space deletion until the\n-- encrypted object and optional remote OAuth grant have both been revoked.\nCREATE TABLE IF NOT EXISTS \"source_connection_secret_refs\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"connection_id\" UUID NOT NULL,\n \"provider_id\" VARCHAR(128) NOT NULL,\n \"credential_ref\" VARCHAR(255) NOT NULL,\n \"purpose\" VARCHAR(32) NOT NULL,\n \"state\" VARCHAR(16) NOT NULL,\n \"remote_revoke_required\" BOOLEAN NOT NULL,\n \"recover_after\" TIMESTAMPTZ NOT NULL,\n \"next_attempt_at\" TIMESTAMPTZ,\n \"worker_id\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"row_version\" INTEGER NOT NULL,\n \"last_error_code\" VARCHAR(64),\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"deleted_at\" TIMESTAMPTZ,\n CONSTRAINT \"source_connection_secret_refs_purpose_ck\"\n CHECK (\"purpose\" IN ('connection-credential', 'oauth-pkce')),\n CONSTRAINT \"source_connection_secret_refs_state_ck\"\n CHECK (\"state\" IN ('staged', 'active', 'retired', 'deleting', 'deleted')),\n CONSTRAINT \"source_connection_secret_refs_version_ck\" CHECK (\"row_version\" >= 1),\n CONSTRAINT \"source_connection_secret_refs_lease_ck\" CHECK (\n (\"state\" = 'deleting' AND \"worker_id\" IS NOT NULL\n AND \"lease_token\" IS NOT NULL AND \"lease_expires_at\" IS NOT NULL)\n OR (\"state\" <> 'deleting' AND \"worker_id\" IS NULL\n AND \"lease_token\" IS NULL AND \"lease_expires_at\" IS NULL)\n ),\n CONSTRAINT \"source_connection_secret_refs_terminal_ck\" CHECK (\n (\"state\" = 'deleted' AND \"deleted_at\" IS NOT NULL)\n OR (\"state\" <> 'deleted' AND \"deleted_at\" IS NULL)\n )\n);\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_connection_secret_refs_ref_uq\"\n ON \"source_connection_secret_refs\" (\"credential_ref\");\nCREATE INDEX IF NOT EXISTS \"source_connection_secret_refs_claim_idx\"\n ON \"source_connection_secret_refs\" (\n \"state\", \"next_attempt_at\", \"recover_after\", \"lease_expires_at\", \"id\"\n );\nCREATE INDEX IF NOT EXISTS \"source_connection_secret_refs_scope_idx\"\n ON \"source_connection_secret_refs\" (\n \"tenant_id\", \"knowledge_space_id\", \"connection_id\", \"state\", \"id\"\n );\n\nALTER TABLE \"sources\" ADD COLUMN IF NOT EXISTS \"connection_id\" UUID;\nDO $kfs_source_connection_fk$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'sources_connection_fk' AND conrelid = 'sources'::regclass\n ) THEN\n ALTER TABLE \"sources\" ADD CONSTRAINT \"sources_connection_fk\"\n FOREIGN KEY (\"knowledge_space_id\", \"connection_id\")\n REFERENCES \"source_connections\" (\"knowledge_space_id\", \"id\") ON DELETE RESTRICT;\n END IF;\nEND\n$kfs_source_connection_fk$;\nCREATE INDEX IF NOT EXISTS \"sources_connection_idx\"\n ON \"sources\" (\"knowledge_space_id\", \"connection_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"source_sync_policies\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"source_id\" UUID NOT NULL,\n \"requested_by_subject_id\" VARCHAR(255) NOT NULL,\n \"access_channel\" VARCHAR(16) NOT NULL,\n \"permission_snapshot_id\" UUID NOT NULL,\n \"permission_snapshot_revision\" INTEGER NOT NULL,\n \"required_permission_scope\" JSONB NOT NULL DEFAULT '[]'::jsonb,\n \"mode\" VARCHAR(16) NOT NULL,\n \"enabled\" BOOLEAN NOT NULL,\n \"custom_interval_seconds\" INTEGER,\n \"next_run_at\" TIMESTAMPTZ,\n \"expected_source_version\" INTEGER NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"source_sync_policies_mode_ck\"\n CHECK (\"mode\" IN ('provider', 'manual', 'interval', 'custom')),\n CONSTRAINT \"source_sync_policies_channel_ck\"\n CHECK (\"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')),\n CONSTRAINT \"source_sync_policies_interval_ck\" CHECK (\n (\"mode\" = 'custom' AND \"custom_interval_seconds\" BETWEEN 3600 AND 2592000)\n OR (\"mode\" <> 'custom' AND \"custom_interval_seconds\" IS NULL)\n ),\n CONSTRAINT \"source_sync_policies_revision_ck\"\n CHECK (\n \"revision\" >= 1 AND \"expected_source_version\" >= 1\n AND \"permission_snapshot_revision\" >= 1\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\"knowledge_space_id\", \"source_id\")\n REFERENCES \"sources\" (\"knowledge_space_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"permission_snapshot_id\",\n \"requested_by_subject_id\", \"access_channel\"\n ) REFERENCES \"knowledge_space_permission_snapshots\" (\n \"tenant_id\", \"knowledge_space_id\", \"id\", \"subject_id\", \"access_channel\"\n )\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_sync_policies_source_uq\"\n ON \"source_sync_policies\" (\"tenant_id\", \"knowledge_space_id\", \"source_id\");\nCREATE INDEX IF NOT EXISTS \"source_sync_policies_due_idx\"\n ON \"source_sync_policies\" (\"enabled\", \"next_run_at\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"source_workflow_runs\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"source_id\" UUID,\n \"source_scope\" VARCHAR(128) NOT NULL,\n \"kind\" VARCHAR(32) NOT NULL,\n \"run_state\" VARCHAR(24) NOT NULL,\n \"checkpoint\" VARCHAR(32) NOT NULL,\n \"payload\" JSONB NOT NULL,\n \"cursor\" VARCHAR(4096),\n \"progress_total\" INTEGER,\n \"progress_completed\" INTEGER NOT NULL,\n \"progress_skipped\" INTEGER NOT NULL,\n \"progress_failed\" INTEGER NOT NULL,\n \"permission_snapshot_id\" UUID NOT NULL,\n \"permission_snapshot_revision\" INTEGER NOT NULL,\n \"requested_by_subject_id\" VARCHAR(255) NOT NULL,\n \"required_permission_scope\" JSONB NOT NULL DEFAULT '[]'::jsonb,\n \"access_channel\" VARCHAR(16) NOT NULL,\n \"idempotency_key\" VARCHAR(255) NOT NULL,\n \"idempotency_digest\" CHAR(64) NOT NULL,\n \"execution_attempts\" INTEGER NOT NULL,\n \"max_execution_attempts\" INTEGER NOT NULL,\n \"worker_id\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"row_version\" INTEGER NOT NULL,\n \"active_slot\" INTEGER,\n \"last_error_code\" VARCHAR(64),\n \"last_error_message\" VARCHAR(1000),\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"completed_at\" TIMESTAMPTZ,\n \"canceled_at\" TIMESTAMPTZ,\n CONSTRAINT \"source_workflow_runs_idempotency_digest_ck\"\n CHECK (\"idempotency_digest\" ~ '^[a-f0-9]{64}$'),\n CONSTRAINT \"source_workflow_runs_kind_ck\" CHECK (\n \"kind\" IN (\n 'crawl-preview', 'crawl-import', 'online-document-import',\n 'online-drive-import', 'sync', 'bulk'\n )\n ),\n CONSTRAINT \"source_workflow_runs_state_ck\" CHECK (\n \"run_state\" IN (\n 'queued', 'running', 'crawling', 'preview_ready', 'importing', 'syncing',\n 'completed', 'zero_results', 'failed', 'canceled'\n )\n ),\n CONSTRAINT \"source_workflow_runs_checkpoint_ck\" CHECK (\n \"checkpoint\" IN (\n 'queued', 'provider-read', 'preview-staged', 'selection-frozen',\n 'materialized', 'cleanup-staging', 'source-committed'\n )\n ),\n CONSTRAINT \"source_workflow_runs_counts_ck\" CHECK (\n \"progress_total\" IS NULL OR (\n \"progress_total\" >= 0\n AND \"progress_completed\" + \"progress_skipped\" + \"progress_failed\" <= \"progress_total\"\n )\n ),\n CONSTRAINT \"source_workflow_runs_nonnegative_ck\" CHECK (\n \"progress_completed\" >= 0 AND \"progress_skipped\" >= 0 AND \"progress_failed\" >= 0\n AND \"execution_attempts\" >= 0 AND \"max_execution_attempts\" >= 1\n AND \"execution_attempts\" <= \"max_execution_attempts\"\n AND \"permission_snapshot_revision\" >= 1 AND \"row_version\" >= 1\n AND (\"active_slot\" IS NULL OR \"active_slot\" = 1)\n ),\n CONSTRAINT \"source_workflow_runs_lease_ck\" CHECK (\n (\"run_state\" IN ('running', 'crawling', 'importing', 'syncing')\n AND \"worker_id\" IS NOT NULL AND \"lease_token\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL)\n OR (\"run_state\" NOT IN ('running', 'crawling', 'importing', 'syncing')\n AND \"worker_id\" IS NULL AND \"lease_token\" IS NULL AND \"lease_expires_at\" IS NULL)\n ),\n CONSTRAINT \"source_workflow_runs_terminal_ck\" CHECK (\n (\"run_state\" IN ('queued', 'running', 'crawling', 'preview_ready', 'importing', 'syncing')\n AND \"active_slot\" = 1 AND \"completed_at\" IS NULL)\n OR (\"run_state\" IN ('completed', 'zero_results', 'failed')\n AND \"active_slot\" IS NULL AND \"completed_at\" IS NOT NULL AND \"canceled_at\" IS NULL)\n OR (\"run_state\" = 'canceled' AND \"active_slot\" IS NULL\n AND \"completed_at\" IS NOT NULL AND \"canceled_at\" IS NOT NULL)\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\"knowledge_space_id\", \"source_id\")\n REFERENCES \"sources\" (\"knowledge_space_id\", \"id\") ON DELETE RESTRICT,\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"permission_snapshot_id\",\n \"requested_by_subject_id\", \"access_channel\"\n ) REFERENCES \"knowledge_space_permission_snapshots\" (\n \"tenant_id\", \"knowledge_space_id\", \"id\", \"subject_id\", \"access_channel\"\n ) ON DELETE RESTRICT\n);\n\n-- Recover a table left behind by an earlier 0021 attempt that failed while creating the\n-- oversized TiDB composite idempotency index. The original key remains collision evidence.\nALTER TABLE \"source_workflow_runs\"\n ADD COLUMN IF NOT EXISTS \"idempotency_digest\" CHAR(64);\nUPDATE \"source_workflow_runs\"\nSET \"idempotency_digest\" = encode(sha256(convert_to(\n 'v1|'\n || octet_length(\"tenant_id\")::text || ':' || \"tenant_id\" || '|'\n || octet_length(\"knowledge_space_id\"::text)::text || ':' || \"knowledge_space_id\"::text || '|'\n || octet_length(\"requested_by_subject_id\")::text || ':' || \"requested_by_subject_id\" || '|'\n || octet_length(\"idempotency_key\")::text || ':' || \"idempotency_key\" || '|',\n 'UTF8'\n)), 'hex')\nWHERE \"idempotency_digest\" IS NULL;\nALTER TABLE \"source_workflow_runs\"\n ALTER COLUMN \"idempotency_digest\" SET NOT NULL;\n\nDROP INDEX IF EXISTS \"source_workflow_runs_idempotency_uq\";\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_workflow_runs_idempotency_digest_uq\"\n ON \"source_workflow_runs\" (\"idempotency_digest\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_workflow_runs_scope_id_uq\"\n ON \"source_workflow_runs\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_workflow_runs_active_uq\"\n ON \"source_workflow_runs\" (\n \"tenant_id\", \"knowledge_space_id\", \"source_scope\", \"active_slot\"\n );\nCREATE INDEX IF NOT EXISTS \"source_workflow_runs_claim_idx\"\n ON \"source_workflow_runs\" (\"run_state\", \"lease_expires_at\", \"updated_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"source_workflow_runs_history_idx\"\n ON \"source_workflow_runs\" (\n \"tenant_id\", \"knowledge_space_id\", \"source_id\", \"created_at\", \"id\"\n );\n\nCREATE TABLE IF NOT EXISTS \"source_workflow_outbox\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"run_id\" UUID NOT NULL,\n \"delivery_revision\" INTEGER NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"available_at\" TIMESTAMPTZ NOT NULL,\n \"locked_by\" VARCHAR(255),\n \"lock_token\" UUID,\n \"locked_until\" TIMESTAMPTZ,\n \"last_error\" VARCHAR(1000),\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"delivered_at\" TIMESTAMPTZ,\n CONSTRAINT \"source_workflow_outbox_status_ck\"\n CHECK (\"status\" IN ('pending', 'leased', 'completed', 'canceled')),\n CONSTRAINT \"source_workflow_outbox_revision_ck\" CHECK (\"delivery_revision\" >= 1),\n CONSTRAINT \"source_workflow_outbox_lease_ck\" CHECK (\n (\"status\" = 'leased' AND \"locked_by\" IS NOT NULL\n AND \"lock_token\" IS NOT NULL AND \"locked_until\" IS NOT NULL)\n OR (\"status\" <> 'leased' AND \"locked_by\" IS NULL\n AND \"lock_token\" IS NULL AND \"locked_until\" IS NULL)\n ),\n FOREIGN KEY (\"run_id\") REFERENCES \"source_workflow_runs\" (\"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_workflow_outbox_delivery_uq\"\n ON \"source_workflow_outbox\" (\"run_id\", \"delivery_revision\");\nCREATE INDEX IF NOT EXISTS \"source_workflow_outbox_claim_idx\"\n ON \"source_workflow_outbox\" (\"status\", \"available_at\", \"locked_until\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"source_crawl_preview_pages\" (\n \"id\" CHAR(64) NOT NULL,\n \"run_id\" UUID NOT NULL,\n \"page_id\" CHAR(64) NOT NULL,\n \"source_url\" VARCHAR(4096) NOT NULL,\n \"title\" VARCHAR(500),\n \"description\" VARCHAR(2000),\n \"etag\" VARCHAR(1024),\n \"content_hash\" CHAR(64) NOT NULL,\n \"content_object_key\" VARCHAR(2048) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n PRIMARY KEY (\"run_id\", \"id\"),\n CONSTRAINT \"source_crawl_preview_pages_hash_ck\" CHECK (\n \"id\" ~ '^[a-f0-9]{64}$' AND \"page_id\" ~ '^[a-f0-9]{64}$'\n AND \"content_hash\" ~ '^[a-f0-9]{64}$'\n ),\n FOREIGN KEY (\"run_id\") REFERENCES \"source_workflow_runs\" (\"id\") ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_crawl_preview_pages_page_uq\"\n ON \"source_crawl_preview_pages\" (\"run_id\", \"page_id\");\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"deletion_jobs_scope_id_uq\"\n ON \"deletion_jobs\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"source_bulk_workflow_items\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"run_id\" UUID NOT NULL,\n \"source_id\" UUID NOT NULL,\n \"child_run_id\" UUID,\n \"deletion_job_id\" UUID,\n \"action\" VARCHAR(16) NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"reason\" VARCHAR(1000),\n \"error_code\" VARCHAR(64),\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"source_bulk_workflow_items_action_ck\"\n CHECK (\"action\" IN ('sync', 'disable', 'remove')),\n CONSTRAINT \"source_bulk_workflow_items_status_ck\"\n CHECK (\"status\" IN ('eligible', 'running', 'skipped', 'failed', 'completed')),\n CONSTRAINT \"source_bulk_workflow_items_child_ck\" CHECK (\n (\"child_run_id\" IS NULL AND \"deletion_job_id\" IS NULL\n AND \"status\" IN ('eligible', 'skipped', 'failed'))\n OR (\"child_run_id\" IS NULL AND \"deletion_job_id\" IS NULL\n AND \"action\" = 'disable' AND \"status\" = 'completed')\n OR (\"child_run_id\" IS NOT NULL AND \"deletion_job_id\" IS NULL\n AND \"action\" = 'sync' AND \"status\" IN ('running', 'failed', 'completed'))\n OR (\"child_run_id\" IS NULL AND \"deletion_job_id\" IS NOT NULL\n AND \"action\" = 'remove' AND \"status\" IN ('running', 'failed', 'completed'))\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"run_id\")\n REFERENCES \"source_workflow_runs\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE CASCADE,\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"child_run_id\")\n REFERENCES \"source_workflow_runs\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE RESTRICT,\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"deletion_job_id\")\n REFERENCES \"deletion_jobs\" (\"tenant_id\", \"knowledge_space_id\", \"id\") ON DELETE RESTRICT\n);\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_bulk_workflow_items_source_uq\"\n ON \"source_bulk_workflow_items\" (\"run_id\", \"source_id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_bulk_workflow_items_child_uq\"\n ON \"source_bulk_workflow_items\" (\"child_run_id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"source_bulk_workflow_items_deletion_job_uq\"\n ON \"source_bulk_workflow_items\" (\"deletion_job_id\");\nCREATE INDEX IF NOT EXISTS \"source_bulk_workflow_items_list_idx\"\n ON \"source_bulk_workflow_items\" (\"tenant_id\", \"knowledge_space_id\", \"run_id\", \"id\");\n", path: "packages/database/migrations/0021_source_product_workflows.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0021_source_product_workflows\n-- Dialect: tidb\n\nCREATE TABLE IF NOT EXISTS `source_connections` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `provider_id` VARCHAR(128) NOT NULL,\n `name` VARCHAR(160) NOT NULL,\n `auth_kind` VARCHAR(16) NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `configuration` JSON NOT NULL,\n `credential_ref` VARCHAR(255),\n `scopes` JSON NOT NULL,\n `expires_at` DATETIME(3),\n `last_error_code` VARCHAR(64),\n `version` INT NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `source_connections_auth_kind_ck`\n CHECK (`auth_kind` IN ('api-key', 'endpoint', 'oauth2')),\n CONSTRAINT `source_connections_status_ck`\n CHECK (`status` IN ('provisioning', 'active', 'expired', 'error', 'revoked')),\n CONSTRAINT `source_connections_version_ck` CHECK (`version` >= 1),\n CONSTRAINT `source_connections_secret_ck` CHECK (\n (`status` = 'revoked' AND `credential_ref` IS NULL) OR `status` <> 'revoked'\n ),\n UNIQUE KEY `source_connections_scope_id_uq` (`tenant_id`, `knowledge_space_id`, `id`),\n UNIQUE KEY `source_connections_space_id_uq` (`knowledge_space_id`, `id`),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_connections_scope_id_uq`\n ON `source_connections` (`tenant_id`, `knowledge_space_id`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_connections_space_id_uq`\n ON `source_connections` (`knowledge_space_id`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_connections_credential_ref_uq`\n ON `source_connections` (`credential_ref`);\nCREATE INDEX IF NOT EXISTS `source_connections_scope_status_idx`\n ON `source_connections` (`tenant_id`, `knowledge_space_id`, `status`, `created_at`, `id`);\n\nCREATE TABLE IF NOT EXISTS `source_oauth_transactions` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `connection_id` CHAR(36) NOT NULL,\n `requested_by_subject_id` VARCHAR(255) NOT NULL,\n `access_channel` VARCHAR(16) NOT NULL,\n `permission_snapshot_id` CHAR(36) NOT NULL,\n `permission_snapshot_revision` INT NOT NULL,\n `api_key_id` CHAR(36),\n `state_hash` CHAR(64) NOT NULL,\n `verifier_ref` VARCHAR(255) NOT NULL,\n `redirect_uri` VARCHAR(2048) NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `expires_at` DATETIME(3) NOT NULL,\n `consumed_at` DATETIME(3),\n `completed_at` DATETIME(3),\n CONSTRAINT `source_oauth_transactions_state_hash_ck`\n CHECK (`state_hash` REGEXP '^[a-f0-9]{64}$'),\n CONSTRAINT `source_oauth_transactions_status_ck`\n CHECK (`status` IN ('pending', 'exchanging', 'completed', 'failed')),\n CONSTRAINT `source_oauth_transactions_channel_ck`\n CHECK (`access_channel` IN ('interactive', 'service_api', 'mcp', 'agent')),\n CONSTRAINT `source_oauth_transactions_permission_ck`\n CHECK (`permission_snapshot_revision` >= 1),\n CONSTRAINT `source_oauth_transactions_lifecycle_ck` CHECK (\n (`status` = 'pending' AND `consumed_at` IS NULL AND `completed_at` IS NULL)\n OR (`status` IN ('exchanging', 'failed') AND `consumed_at` IS NOT NULL)\n OR (`status` = 'completed' AND `consumed_at` IS NOT NULL AND `completed_at` IS NOT NULL)\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `connection_id`)\n REFERENCES `source_connections` (`tenant_id`, `knowledge_space_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `permission_snapshot_id`,\n `requested_by_subject_id`, `access_channel`\n ) REFERENCES `knowledge_space_permission_snapshots` (\n `tenant_id`, `knowledge_space_id`, `id`, `subject_id`, `access_channel`\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `api_key_id`)\n REFERENCES `knowledge_space_api_keys` (`tenant_id`, `knowledge_space_id`, `id`)\n);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_oauth_transactions_state_hash_uq`\n ON `source_oauth_transactions` (`state_hash`);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_oauth_transactions_verifier_ref_uq`\n ON `source_oauth_transactions` (`verifier_ref`);\nCREATE INDEX IF NOT EXISTS `source_oauth_transactions_expiry_idx`\n ON `source_oauth_transactions` (`status`, `expires_at`, `id`);\n\nCREATE TABLE IF NOT EXISTS `source_connection_secret_refs` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `connection_id` CHAR(36) NOT NULL,\n `provider_id` VARCHAR(128) NOT NULL,\n `credential_ref` VARCHAR(255) NOT NULL,\n `purpose` VARCHAR(32) NOT NULL,\n `state` VARCHAR(16) NOT NULL,\n `remote_revoke_required` BOOLEAN NOT NULL,\n `recover_after` DATETIME(3) NOT NULL,\n `next_attempt_at` DATETIME(3),\n `worker_id` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `row_version` INT NOT NULL,\n `last_error_code` VARCHAR(64),\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `deleted_at` DATETIME(3),\n CONSTRAINT `source_connection_secret_refs_purpose_ck`\n CHECK (`purpose` IN ('connection-credential', 'oauth-pkce')),\n CONSTRAINT `source_connection_secret_refs_state_ck`\n CHECK (`state` IN ('staged', 'active', 'retired', 'deleting', 'deleted')),\n CONSTRAINT `source_connection_secret_refs_version_ck` CHECK (`row_version` >= 1),\n CONSTRAINT `source_connection_secret_refs_lease_ck` CHECK (\n (`state` = 'deleting' AND `worker_id` IS NOT NULL\n AND `lease_token` IS NOT NULL AND `lease_expires_at` IS NOT NULL)\n OR (`state` <> 'deleting' AND `worker_id` IS NULL\n AND `lease_token` IS NULL AND `lease_expires_at` IS NULL)\n ),\n CONSTRAINT `source_connection_secret_refs_terminal_ck` CHECK (\n (`state` = 'deleted' AND `deleted_at` IS NOT NULL)\n OR (`state` <> 'deleted' AND `deleted_at` IS NULL)\n )\n);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_connection_secret_refs_ref_uq`\n ON `source_connection_secret_refs` (`credential_ref`);\nCREATE INDEX IF NOT EXISTS `source_connection_secret_refs_claim_idx`\n ON `source_connection_secret_refs` (`state`, `next_attempt_at`, `recover_after`, `lease_expires_at`, `id`);\nCREATE INDEX IF NOT EXISTS `source_connection_secret_refs_scope_idx`\n ON `source_connection_secret_refs` (`tenant_id`, `knowledge_space_id`, `connection_id`, `state`, `id`);\n\nALTER TABLE `sources` ADD COLUMN IF NOT EXISTS `connection_id` CHAR(36);\nSET @source_connection_fk_exists = (\n SELECT COUNT(*) FROM information_schema.table_constraints\n WHERE constraint_schema = DATABASE() AND table_name = 'sources'\n AND constraint_name = 'sources_connection_fk'\n);\nSET @source_connection_fk_ddl = IF(\n @source_connection_fk_exists = 0,\n 'ALTER TABLE `sources` ADD CONSTRAINT `sources_connection_fk` FOREIGN KEY (`knowledge_space_id`, `connection_id`) REFERENCES `source_connections` (`knowledge_space_id`, `id`) ON DELETE RESTRICT',\n 'DO 0'\n);\nPREPARE source_connection_fk_statement FROM @source_connection_fk_ddl;\nEXECUTE source_connection_fk_statement;\nDEALLOCATE PREPARE source_connection_fk_statement;\nCREATE INDEX IF NOT EXISTS `sources_connection_idx`\n ON `sources` (`knowledge_space_id`, `connection_id`, `id`);\n\nCREATE TABLE IF NOT EXISTS `source_sync_policies` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `source_id` CHAR(36) NOT NULL,\n `requested_by_subject_id` VARCHAR(255) NOT NULL,\n `access_channel` VARCHAR(16) NOT NULL,\n `permission_snapshot_id` CHAR(36) NOT NULL,\n `permission_snapshot_revision` INT NOT NULL,\n `required_permission_scope` JSON NOT NULL,\n `mode` VARCHAR(16) NOT NULL,\n `enabled` BOOLEAN NOT NULL,\n `custom_interval_seconds` INT,\n `next_run_at` DATETIME(3),\n `expected_source_version` INT NOT NULL,\n `revision` INT NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `source_sync_policies_mode_ck`\n CHECK (`mode` IN ('provider', 'manual', 'interval', 'custom')),\n CONSTRAINT `source_sync_policies_channel_ck`\n CHECK (`access_channel` IN ('interactive', 'service_api', 'mcp', 'agent')),\n CONSTRAINT `source_sync_policies_interval_ck` CHECK (\n (`mode` = 'custom' AND `custom_interval_seconds` BETWEEN 3600 AND 2592000)\n OR (`mode` <> 'custom' AND `custom_interval_seconds` IS NULL)\n ),\n CONSTRAINT `source_sync_policies_revision_ck`\n CHECK (\n `revision` >= 1 AND `expected_source_version` >= 1\n AND `permission_snapshot_revision` >= 1\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (`knowledge_space_id`, `source_id`)\n REFERENCES `sources` (`knowledge_space_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `permission_snapshot_id`,\n `requested_by_subject_id`, `access_channel`\n ) REFERENCES `knowledge_space_permission_snapshots` (\n `tenant_id`, `knowledge_space_id`, `id`, `subject_id`, `access_channel`\n )\n);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_sync_policies_source_uq`\n ON `source_sync_policies` (`tenant_id`, `knowledge_space_id`, `source_id`);\nCREATE INDEX IF NOT EXISTS `source_sync_policies_due_idx`\n ON `source_sync_policies` (`enabled`, `next_run_at`, `id`);\n\nCREATE TABLE IF NOT EXISTS `source_workflow_runs` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `source_id` CHAR(36),\n `source_scope` VARCHAR(128) NOT NULL,\n `kind` VARCHAR(32) NOT NULL,\n `run_state` VARCHAR(24) NOT NULL,\n `checkpoint` VARCHAR(32) NOT NULL,\n `payload` JSON NOT NULL,\n `cursor` VARCHAR(4096),\n `progress_total` INT,\n `progress_completed` INT NOT NULL,\n `progress_skipped` INT NOT NULL,\n `progress_failed` INT NOT NULL,\n `permission_snapshot_id` CHAR(36) NOT NULL,\n `permission_snapshot_revision` INT NOT NULL,\n `requested_by_subject_id` VARCHAR(255) NOT NULL,\n `required_permission_scope` JSON NOT NULL,\n `access_channel` VARCHAR(16) NOT NULL,\n `idempotency_key` VARCHAR(255) NOT NULL,\n `idempotency_digest` CHAR(64) NOT NULL,\n `execution_attempts` INT NOT NULL,\n `max_execution_attempts` INT NOT NULL,\n `worker_id` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `row_version` INT NOT NULL,\n `active_slot` INT,\n `last_error_code` VARCHAR(64),\n `last_error_message` VARCHAR(1000),\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `completed_at` DATETIME(3),\n `canceled_at` DATETIME(3),\n CONSTRAINT `source_workflow_runs_idempotency_digest_ck`\n CHECK (`idempotency_digest` REGEXP '^[a-f0-9]{64}$'),\n CONSTRAINT `source_workflow_runs_kind_ck` CHECK (\n `kind` IN ('crawl-preview', 'crawl-import', 'online-document-import', 'online-drive-import', 'sync', 'bulk')\n ),\n CONSTRAINT `source_workflow_runs_state_ck` CHECK (\n `run_state` IN ('queued', 'running', 'crawling', 'preview_ready', 'importing', 'syncing', 'completed', 'zero_results', 'failed', 'canceled')\n ),\n CONSTRAINT `source_workflow_runs_checkpoint_ck` CHECK (\n `checkpoint` IN ('queued', 'provider-read', 'preview-staged', 'selection-frozen', 'materialized', 'cleanup-staging', 'source-committed')\n ),\n CONSTRAINT `source_workflow_runs_nonnegative_ck` CHECK (\n (`progress_total` IS NULL OR `progress_total` >= 0)\n AND (`progress_total` IS NULL OR\n `progress_completed` + `progress_skipped` + `progress_failed` <= `progress_total`)\n AND `progress_completed` >= 0 AND `progress_skipped` >= 0 AND `progress_failed` >= 0\n AND `execution_attempts` >= 0 AND `max_execution_attempts` >= 1\n AND `execution_attempts` <= `max_execution_attempts`\n AND `permission_snapshot_revision` >= 1 AND `row_version` >= 1\n AND (`active_slot` IS NULL OR `active_slot` = 1)\n ),\n CONSTRAINT `source_workflow_runs_lease_ck` CHECK (\n (`run_state` IN ('running', 'crawling', 'importing', 'syncing')\n AND `worker_id` IS NOT NULL AND `lease_token` IS NOT NULL AND `lease_expires_at` IS NOT NULL)\n OR (`run_state` NOT IN ('running', 'crawling', 'importing', 'syncing')\n AND `worker_id` IS NULL AND `lease_token` IS NULL AND `lease_expires_at` IS NULL)\n ),\n CONSTRAINT `source_workflow_runs_terminal_ck` CHECK (\n (`run_state` IN ('queued', 'running', 'crawling', 'preview_ready', 'importing', 'syncing')\n AND `active_slot` = 1 AND `completed_at` IS NULL)\n OR (`run_state` IN ('completed', 'zero_results', 'failed')\n AND `active_slot` IS NULL AND `completed_at` IS NOT NULL AND `canceled_at` IS NULL)\n OR (`run_state` = 'canceled' AND `active_slot` IS NULL\n AND `completed_at` IS NOT NULL AND `canceled_at` IS NOT NULL)\n ),\n UNIQUE KEY `source_workflow_runs_scope_id_uq` (`tenant_id`, `knowledge_space_id`, `id`),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (`knowledge_space_id`, `source_id`)\n REFERENCES `sources` (`knowledge_space_id`, `id`) ON DELETE RESTRICT,\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `permission_snapshot_id`, `requested_by_subject_id`, `access_channel`)\n REFERENCES `knowledge_space_permission_snapshots` (`tenant_id`, `knowledge_space_id`, `id`, `subject_id`, `access_channel`)\n ON DELETE RESTRICT\n);\n\n-- Recover a table left behind when the former 3204-byte composite index exceeded TiDB's\n-- 3072-byte limit. Retain idempotency_key and verify it after digest lookup for collision safety.\nALTER TABLE `source_workflow_runs`\n ADD COLUMN IF NOT EXISTS `idempotency_digest` CHAR(64);\nUPDATE `source_workflow_runs`\nSET `idempotency_digest` = SHA2(CONCAT(\n 'v1|', OCTET_LENGTH(`tenant_id`), ':', `tenant_id`, '|',\n OCTET_LENGTH(`knowledge_space_id`), ':', `knowledge_space_id`, '|',\n OCTET_LENGTH(`requested_by_subject_id`), ':', `requested_by_subject_id`, '|',\n OCTET_LENGTH(`idempotency_key`), ':', `idempotency_key`, '|'\n), 256)\nWHERE `idempotency_digest` IS NULL;\nALTER TABLE `source_workflow_runs`\n MODIFY COLUMN `idempotency_digest` CHAR(64) NOT NULL;\n\nDROP INDEX IF EXISTS `source_workflow_runs_idempotency_uq` ON `source_workflow_runs`;\nCREATE UNIQUE INDEX IF NOT EXISTS `source_workflow_runs_idempotency_digest_uq`\n ON `source_workflow_runs` (`idempotency_digest`);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_workflow_runs_scope_id_uq`\n ON `source_workflow_runs` (`tenant_id`, `knowledge_space_id`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_workflow_runs_active_uq`\n ON `source_workflow_runs` (`tenant_id`, `knowledge_space_id`, `source_scope`, `active_slot`);\nCREATE INDEX IF NOT EXISTS `source_workflow_runs_claim_idx`\n ON `source_workflow_runs` (`run_state`, `lease_expires_at`, `updated_at`, `id`);\nCREATE INDEX IF NOT EXISTS `source_workflow_runs_history_idx`\n ON `source_workflow_runs` (`tenant_id`, `knowledge_space_id`, `source_id`, `created_at`, `id`);\n\nCREATE TABLE IF NOT EXISTS `source_workflow_outbox` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `run_id` CHAR(36) NOT NULL,\n `delivery_revision` INT NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `available_at` DATETIME(3) NOT NULL,\n `locked_by` VARCHAR(255),\n `lock_token` CHAR(36),\n `locked_until` DATETIME(3),\n `last_error` VARCHAR(1000),\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `delivered_at` DATETIME(3),\n CONSTRAINT `source_workflow_outbox_status_ck`\n CHECK (`status` IN ('pending', 'leased', 'completed', 'canceled')),\n CONSTRAINT `source_workflow_outbox_revision_ck` CHECK (`delivery_revision` >= 1),\n CONSTRAINT `source_workflow_outbox_lease_ck` CHECK (\n (`status` = 'leased' AND `locked_by` IS NOT NULL AND `lock_token` IS NOT NULL AND `locked_until` IS NOT NULL)\n OR (`status` <> 'leased' AND `locked_by` IS NULL AND `lock_token` IS NULL AND `locked_until` IS NULL)\n ),\n FOREIGN KEY (`run_id`) REFERENCES `source_workflow_runs` (`id`) ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_workflow_outbox_delivery_uq`\n ON `source_workflow_outbox` (`run_id`, `delivery_revision`);\nCREATE INDEX IF NOT EXISTS `source_workflow_outbox_claim_idx`\n ON `source_workflow_outbox` (`status`, `available_at`, `locked_until`, `id`);\n\nCREATE TABLE IF NOT EXISTS `source_crawl_preview_pages` (\n `id` CHAR(64) NOT NULL,\n `run_id` CHAR(36) NOT NULL,\n `page_id` CHAR(64) NOT NULL,\n `source_url` VARCHAR(4096) NOT NULL,\n `title` VARCHAR(500),\n `description` VARCHAR(2000),\n `etag` VARCHAR(1024),\n `content_hash` CHAR(64) NOT NULL,\n `content_object_key` VARCHAR(2048) NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n PRIMARY KEY (`run_id`, `id`),\n CONSTRAINT `source_crawl_preview_pages_hash_ck` CHECK (\n `id` REGEXP '^[a-f0-9]{64}$' AND `page_id` REGEXP '^[a-f0-9]{64}$'\n AND `content_hash` REGEXP '^[a-f0-9]{64}$'\n ),\n FOREIGN KEY (`run_id`) REFERENCES `source_workflow_runs` (`id`) ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_crawl_preview_pages_page_uq`\n ON `source_crawl_preview_pages` (`run_id`, `page_id`);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `deletion_jobs_scope_id_uq`\n ON `deletion_jobs` (`tenant_id`, `knowledge_space_id`, `id`);\n\nCREATE TABLE IF NOT EXISTS `source_bulk_workflow_items` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `run_id` CHAR(36) NOT NULL,\n `source_id` CHAR(36) NOT NULL,\n `child_run_id` CHAR(36),\n `deletion_job_id` CHAR(36),\n `action` VARCHAR(16) NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `reason` VARCHAR(1000),\n `error_code` VARCHAR(64),\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `source_bulk_workflow_items_action_ck`\n CHECK (`action` IN ('sync', 'disable', 'remove')),\n CONSTRAINT `source_bulk_workflow_items_status_ck`\n CHECK (`status` IN ('eligible', 'running', 'skipped', 'failed', 'completed')),\n CONSTRAINT `source_bulk_workflow_items_child_ck` CHECK (\n (`child_run_id` IS NULL AND `deletion_job_id` IS NULL\n AND `status` IN ('eligible', 'skipped', 'failed'))\n OR (`child_run_id` IS NULL AND `deletion_job_id` IS NULL\n AND `action` = 'disable' AND `status` = 'completed')\n OR (`child_run_id` IS NOT NULL AND `deletion_job_id` IS NULL\n AND `action` = 'sync' AND `status` IN ('running', 'failed', 'completed'))\n OR (`child_run_id` IS NULL AND `deletion_job_id` IS NOT NULL\n AND `action` = 'remove' AND `status` IN ('running', 'failed', 'completed'))\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `run_id`)\n REFERENCES `source_workflow_runs` (`tenant_id`, `knowledge_space_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `child_run_id`)\n REFERENCES `source_workflow_runs` (`tenant_id`, `knowledge_space_id`, `id`),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `deletion_job_id`)\n REFERENCES `deletion_jobs` (`tenant_id`, `knowledge_space_id`, `id`)\n);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_bulk_workflow_items_source_uq`\n ON `source_bulk_workflow_items` (`run_id`, `source_id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_bulk_workflow_items_child_uq`\n ON `source_bulk_workflow_items` (`child_run_id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `source_bulk_workflow_items_deletion_job_uq`\n ON `source_bulk_workflow_items` (`deletion_job_id`);\nCREATE INDEX IF NOT EXISTS `source_bulk_workflow_items_list_idx`\n ON `source_bulk_workflow_items` (`tenant_id`, `knowledge_space_id`, `run_id`, `id`);\n", path: "packages/database/migrations/0021_source_product_workflows.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0022_logical_document_revisions\n-- Dialect: postgres\n\nALTER TABLE \"deletion_jobs\" DROP CONSTRAINT IF EXISTS \"deletion_jobs_target_ck\";\nALTER TABLE \"deletion_jobs\" ADD CONSTRAINT \"deletion_jobs_target_ck\" CHECK (\n \"target_type\" IN ('knowledge_space', 'source', 'document_asset', 'logical_document')\n AND (\n (\"target_type\" = 'source' AND \"delete_mode\" IN ('keep', 'cascade') AND \"name_challenge_digest\" IS NULL)\n OR (\"target_type\" = 'knowledge_space' AND \"delete_mode\" = 'cascade' AND \"name_challenge_digest\" IS NOT NULL)\n OR (\"target_type\" IN ('document_asset', 'logical_document') AND \"delete_mode\" = 'cascade' AND \"name_challenge_digest\" IS NULL)\n )\n);\nALTER TABLE \"deletion_tombstones\" DROP CONSTRAINT IF EXISTS \"deletion_tombstones_target_ck\";\nALTER TABLE \"deletion_tombstones\" ADD CONSTRAINT \"deletion_tombstones_target_ck\"\n CHECK (\"target_type\" IN ('knowledge_space', 'source', 'document_asset', 'logical_document'));\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"sources_space_id_uq\"\n ON \"sources\" (\"knowledge_space_id\", \"id\");\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_compilation_attempts_scope_id_uq\"\n ON \"document_compilation_attempts\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"logical_documents\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"source_id\" UUID,\n \"provider_item_id\" VARCHAR(1024),\n \"provider_item_digest\" CHAR(64),\n \"title\" TEXT NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"deletion_job_id\" UUID,\n \"deleting_at\" TIMESTAMPTZ,\n \"active_revision\" INTEGER,\n \"row_version\" INTEGER NOT NULL,\n \"system_metadata\" JSONB NOT NULL,\n \"user_metadata\" JSONB NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"logical_documents_status_ck\"\n CHECK (\"status\" IN ('pending', 'ready', 'failed', 'deleting')),\n CONSTRAINT \"logical_documents_deletion_lifecycle_ck\"\n CHECK (\n (\"status\" = 'deleting' AND \"deletion_job_id\" IS NOT NULL AND \"deleting_at\" IS NOT NULL)\n OR (\"status\" <> 'deleting' AND \"deletion_job_id\" IS NULL AND \"deleting_at\" IS NULL)\n ),\n CONSTRAINT \"logical_documents_active_revision_ck\"\n CHECK (\"active_revision\" IS NULL OR \"active_revision\" > 0),\n CONSTRAINT \"logical_documents_row_version_ck\"\n CHECK (\"row_version\" >= 0),\n CONSTRAINT \"logical_documents_provider_identity_ck\"\n CHECK (\n (\n \"source_id\" IS NULL\n AND \"provider_item_id\" IS NULL\n AND \"provider_item_digest\" IS NULL\n )\n OR (\n \"source_id\" IS NOT NULL\n AND \"provider_item_id\" IS NOT NULL\n AND \"provider_item_digest\" ~ '^[a-f0-9]{64}$'\n )\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\")\n ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"logical_documents_scope_id_uq\"\n ON \"logical_documents\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"logical_documents_provider_item_uq\"\n ON \"logical_documents\" (\"provider_item_digest\")\n WHERE \"provider_item_digest\" IS NOT NULL;\nCREATE INDEX IF NOT EXISTS \"logical_documents_space_cursor_idx\"\n ON \"logical_documents\" (\"tenant_id\", \"knowledge_space_id\", \"created_at\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"document_revisions\" (\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_id\" UUID NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"document_asset_version\" INTEGER NOT NULL,\n \"compilation_attempt_id\" UUID,\n \"expected_active_revision\" INTEGER,\n \"expected_document_row_version\" INTEGER NOT NULL,\n \"content_hash\" VARCHAR(64) NOT NULL,\n \"mime_type\" VARCHAR(255) NOT NULL,\n \"size_bytes\" BIGINT NOT NULL,\n \"state\" VARCHAR(16) NOT NULL,\n \"system_metadata\" JSONB NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"activated_at\" TIMESTAMPTZ,\n PRIMARY KEY (\"tenant_id\", \"knowledge_space_id\", \"document_id\", \"revision\"),\n CONSTRAINT \"document_revisions_revision_ck\" CHECK (\"revision\" > 0),\n CONSTRAINT \"document_revisions_asset_version_ck\" CHECK (\"document_asset_version\" > 0),\n CONSTRAINT \"document_revisions_expected_active_ck\"\n CHECK (\"expected_active_revision\" IS NULL OR \"expected_active_revision\" > 0),\n CONSTRAINT \"document_revisions_expected_row_version_ck\"\n CHECK (\"expected_document_row_version\" >= 0),\n CONSTRAINT \"document_revisions_size_ck\" CHECK (\"size_bytes\" >= 0),\n CONSTRAINT \"document_revisions_hash_ck\" CHECK (\"content_hash\" ~ '^[0-9a-f]{64}$'),\n CONSTRAINT \"document_revisions_state_ck\"\n CHECK (\"state\" IN ('candidate', 'active', 'superseded', 'failed')),\n CONSTRAINT \"document_revisions_activation_ck\"\n CHECK (\n (\"state\" IN ('active', 'superseded') AND \"activated_at\" IS NOT NULL)\n OR (\"state\" IN ('candidate', 'failed') AND \"activated_at\" IS NULL)\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"document_id\")\n REFERENCES \"logical_documents\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE CASCADE,\n FOREIGN KEY (\"knowledge_space_id\", \"document_asset_id\", \"document_asset_version\")\n REFERENCES \"document_assets\" (\"knowledge_space_id\", \"id\", \"version\")\n ON DELETE RESTRICT\n);\n\nCREATE INDEX IF NOT EXISTS \"document_revisions_asset_idx\"\n ON \"document_revisions\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_asset_id\",\n \"document_asset_version\"\n );\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_revisions_compilation_attempt_uq\"\n ON \"document_revisions\" (\"tenant_id\", \"knowledge_space_id\", \"compilation_attempt_id\")\n WHERE \"compilation_attempt_id\" IS NOT NULL;\nCREATE INDEX IF NOT EXISTS \"document_revisions_history_idx\"\n ON \"document_revisions\" (\"tenant_id\", \"knowledge_space_id\", \"document_id\", \"revision\" DESC);\n\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1\n FROM pg_constraint\n WHERE conname = 'logical_documents_active_revision_fk'\n AND conrelid = 'logical_documents'::regclass\n ) THEN\n ALTER TABLE \"logical_documents\"\n ADD CONSTRAINT \"logical_documents_active_revision_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"id\", \"active_revision\")\n REFERENCES \"document_revisions\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"revision\"\n )\n DEFERRABLE INITIALLY DEFERRED;\n END IF;\nEND\n$$;\n\nCREATE TABLE IF NOT EXISTS \"document_revision_chunks\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_id\" UUID NOT NULL,\n \"document_revision\" INTEGER NOT NULL,\n \"parent_chunk_id\" UUID,\n \"ordinal\" INTEGER NOT NULL,\n \"token_count\" INTEGER NOT NULL,\n \"text\" TEXT NOT NULL,\n \"system_metadata\" JSONB NOT NULL,\n \"user_metadata\" JSONB NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n UNIQUE (\"tenant_id\", \"knowledge_space_id\", \"document_id\", \"document_revision\", \"id\"),\n CONSTRAINT \"document_revision_chunks_ordinal_ck\" CHECK (\"ordinal\" >= 0),\n CONSTRAINT \"document_revision_chunks_tokens_ck\" CHECK (\"token_count\" >= 0),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"document_id\", \"document_revision\")\n REFERENCES \"document_revisions\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"revision\"\n )\n ON DELETE CASCADE,\n FOREIGN KEY (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"document_revision\",\n \"parent_chunk_id\"\n ) REFERENCES \"document_revision_chunks\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"document_revision\",\n \"id\"\n ) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_revision_chunks_ordinal_uq\"\n ON \"document_revision_chunks\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"document_revision\",\n \"ordinal\"\n );\nCREATE INDEX IF NOT EXISTS \"document_revision_chunks_cursor_idx\"\n ON \"document_revision_chunks\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"document_revision\",\n \"id\"\n );\n\nCREATE TABLE IF NOT EXISTS \"document_chunk_state_changes\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_id\" UUID NOT NULL,\n \"document_revision\" INTEGER NOT NULL,\n \"chunk_id\" UUID NOT NULL,\n \"enabled\" BOOLEAN NOT NULL,\n \"state\" VARCHAR(16) NOT NULL,\n \"compilation_attempt_id\" UUID NOT NULL,\n \"candidate_publication_id\" UUID,\n \"candidate_fingerprint\" VARCHAR(86),\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"activated_at\" TIMESTAMPTZ,\n CONSTRAINT \"document_chunk_state_changes_state_ck\"\n CHECK (\"state\" IN ('candidate', 'active', 'superseded', 'failed')),\n CONSTRAINT \"document_chunk_state_changes_activation_ck\"\n CHECK (\n (\"state\" IN ('active', 'superseded') AND \"activated_at\" IS NOT NULL)\n OR (\"state\" IN ('candidate', 'failed') AND \"activated_at\" IS NULL)\n ),\n CONSTRAINT \"document_chunk_state_changes_candidate_pair_ck\"\n CHECK (\n (\"candidate_publication_id\" IS NULL AND \"candidate_fingerprint\" IS NULL)\n OR (\"candidate_publication_id\" IS NOT NULL AND \"candidate_fingerprint\" IS NOT NULL)\n ),\n FOREIGN KEY (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"document_revision\",\n \"chunk_id\"\n ) REFERENCES \"document_revision_chunks\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"document_revision\",\n \"id\"\n ) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_chunk_state_changes_candidate_uq\"\n ON \"document_chunk_state_changes\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"document_revision\",\n \"chunk_id\",\n \"candidate_publication_id\"\n );\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_chunk_state_changes_attempt_uq\"\n ON \"document_chunk_state_changes\" (\n \"tenant_id\", \"knowledge_space_id\", \"compilation_attempt_id\"\n );\nCREATE INDEX IF NOT EXISTS \"document_chunk_state_changes_active_idx\"\n ON \"document_chunk_state_changes\" (\"chunk_id\", \"state\", \"activated_at\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"document_settings_revisions\" (\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_id\" UUID NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"settings\" JSONB NOT NULL,\n \"state\" VARCHAR(16) NOT NULL,\n \"created_by_subject_id\" VARCHAR(255) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"activated_at\" TIMESTAMPTZ,\n PRIMARY KEY (\"tenant_id\", \"knowledge_space_id\", \"document_id\", \"revision\"),\n CONSTRAINT \"document_settings_revisions_revision_ck\" CHECK (\"revision\" > 0),\n CONSTRAINT \"document_settings_revisions_state_ck\"\n CHECK (\"state\" IN ('candidate', 'active', 'superseded', 'failed')),\n CONSTRAINT \"document_settings_revisions_activation_ck\"\n CHECK (\n (\"state\" IN ('active', 'superseded') AND \"activated_at\" IS NOT NULL)\n OR (\"state\" IN ('candidate', 'failed') AND \"activated_at\" IS NULL)\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"document_id\")\n REFERENCES \"logical_documents\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS \"document_settings_heads\" (\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_id\" UUID NOT NULL,\n \"active_revision\" INTEGER NOT NULL,\n \"row_version\" INTEGER NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n PRIMARY KEY (\"tenant_id\", \"knowledge_space_id\", \"document_id\"),\n CONSTRAINT \"document_settings_heads_revision_ck\" CHECK (\"active_revision\" > 0),\n CONSTRAINT \"document_settings_heads_row_version_ck\" CHECK (\"row_version\" >= 0),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"document_id\", \"active_revision\")\n REFERENCES \"document_settings_revisions\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"revision\"\n )\n ON DELETE RESTRICT\n DEFERRABLE INITIALLY DEFERRED\n);\n\nCREATE TABLE IF NOT EXISTS \"document_reindex_attempts\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_id\" UUID NOT NULL,\n \"document_revision\" INTEGER NOT NULL,\n \"settings_revision\" INTEGER NOT NULL,\n \"expected_settings_head_revision\" INTEGER NOT NULL,\n \"state\" VARCHAR(16) NOT NULL,\n \"active_slot\" INTEGER,\n \"compilation_attempt_id\" UUID NOT NULL,\n \"candidate_publication_id\" UUID,\n \"candidate_fingerprint\" VARCHAR(86),\n \"row_version\" INTEGER NOT NULL,\n \"error_code\" VARCHAR(64),\n \"error_message\" TEXT,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"completed_at\" TIMESTAMPTZ,\n CONSTRAINT \"document_reindex_attempts_state_ck\"\n CHECK (\"state\" IN ('queued', 'running', 'succeeded', 'failed', 'canceled')),\n CONSTRAINT \"document_reindex_attempts_active_slot_ck\"\n CHECK (\"active_slot\" IS NULL OR \"active_slot\" = 1),\n CONSTRAINT \"document_reindex_attempts_row_version_ck\" CHECK (\"row_version\" >= 0),\n CONSTRAINT \"document_reindex_attempts_expected_settings_head_revision_ck\"\n CHECK (\"expected_settings_head_revision\" > 0),\n CONSTRAINT \"document_reindex_attempts_lifecycle_ck\"\n CHECK (\n (\"state\" IN ('queued', 'running') AND \"active_slot\" = 1 AND \"completed_at\" IS NULL)\n OR\n (\"state\" IN ('succeeded', 'failed', 'canceled') AND \"active_slot\" IS NULL AND \"completed_at\" IS NOT NULL)\n ),\n CONSTRAINT \"document_reindex_attempts_candidate_pair_ck\"\n CHECK (\n (\"candidate_publication_id\" IS NULL AND \"candidate_fingerprint\" IS NULL)\n OR\n (\"candidate_publication_id\" IS NOT NULL AND \"candidate_fingerprint\" IS NOT NULL)\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"document_id\", \"document_revision\")\n REFERENCES \"document_revisions\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"revision\"\n ) ON DELETE RESTRICT,\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"document_id\", \"settings_revision\")\n REFERENCES \"document_settings_revisions\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"revision\"\n ) ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"document_reindex_attempts_active_uq\"\n ON \"document_reindex_attempts\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"active_slot\"\n );\nCREATE INDEX IF NOT EXISTS \"document_reindex_attempts_cursor_idx\"\n ON \"document_reindex_attempts\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"created_at\",\n \"id\"\n );\n\nCREATE INDEX IF NOT EXISTS \"document_compilation_attempts_space_cursor_idx\"\n ON \"document_compilation_attempts\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"created_at\",\n \"id\"\n );\n\n-- Compatibility bridge: every pre-existing DocumentAsset remains addressable as a logical\n-- document with one immutable active revision. New provider imports may instead attach additional\n-- assets as later revisions of a stable logical document.\nINSERT INTO \"logical_documents\" (\n \"id\",\n \"tenant_id\",\n \"knowledge_space_id\",\n \"source_id\",\n \"provider_item_id\",\n \"title\",\n \"status\",\n \"active_revision\",\n \"row_version\",\n \"system_metadata\",\n \"user_metadata\",\n \"created_at\",\n \"updated_at\"\n)\nSELECT\n asset.\"id\",\n space.\"tenant_id\",\n asset.\"knowledge_space_id\",\n NULL,\n NULL,\n asset.\"filename\",\n CASE\n WHEN asset.\"parser_status\" = 'parsed' THEN 'ready'\n WHEN asset.\"parser_status\" = 'failed' THEN 'failed'\n ELSE 'pending'\n END,\n NULL,\n 0,\n jsonb_build_object(\n 'legacyDocumentAssetId', asset.\"id\"::text,\n 'provenance', asset.\"metadata\"\n ),\n '{}'::jsonb,\n asset.\"created_at\",\n COALESCE(asset.\"updated_at\", asset.\"created_at\")\nFROM \"document_assets\" asset\nJOIN \"knowledge_spaces\" space ON space.\"id\" = asset.\"knowledge_space_id\"\nON CONFLICT (\"id\") DO NOTHING;\n\nINSERT INTO \"document_revisions\" (\n \"tenant_id\",\n \"knowledge_space_id\",\n \"document_id\",\n \"revision\",\n \"document_asset_id\",\n \"document_asset_version\",\n \"compilation_attempt_id\",\n \"expected_active_revision\",\n \"expected_document_row_version\",\n \"content_hash\",\n \"mime_type\",\n \"size_bytes\",\n \"state\",\n \"system_metadata\",\n \"created_at\",\n \"activated_at\"\n)\nSELECT\n space.\"tenant_id\",\n asset.\"knowledge_space_id\",\n asset.\"id\",\n asset.\"version\",\n asset.\"id\",\n asset.\"version\",\n NULL,\n NULL,\n 0,\n asset.\"sha256\",\n asset.\"mime_type\",\n asset.\"size_bytes\",\n 'active',\n jsonb_build_object('provenance', asset.\"metadata\"),\n asset.\"created_at\",\n COALESCE(asset.\"updated_at\", asset.\"created_at\")\nFROM \"document_assets\" asset\nJOIN \"knowledge_spaces\" space ON space.\"id\" = asset.\"knowledge_space_id\"\nON CONFLICT (\"tenant_id\", \"knowledge_space_id\", \"document_id\", \"revision\") DO NOTHING;\n\nUPDATE \"logical_documents\" document\nSET\n \"active_revision\" = asset.\"version\",\n \"row_version\" = CASE WHEN document.\"active_revision\" IS NULL THEN document.\"row_version\" + 1 ELSE document.\"row_version\" END\nFROM \"document_assets\" asset\nWHERE document.\"id\" = asset.\"id\"\n AND document.\"knowledge_space_id\" = asset.\"knowledge_space_id\"\n AND document.\"active_revision\" IS NULL;\n", path: "packages/database/migrations/0022_logical_document_revisions.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0022_logical_document_revisions\n-- Dialect: tidb\n-- TiDB requires v8.5+ with CHECK and foreign-key enforcement enabled.\n\n-- TiDB auto-commits each DDL statement. Only drop the legacy definition, and only add the new\n-- definition when it is absent, so a crash between DROP and ADD remains replayable.\nSET @kfs_0022_deletion_jobs_target_drop_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_jobs'\n AND constraint_name = 'deletion_jobs_target_ck'\n AND check_clause NOT LIKE '%logical_document%'\n ),\n 'ALTER TABLE `deletion_jobs` DROP CHECK `deletion_jobs_target_ck`',\n 'DO 0'\n);\nPREPARE kfs_0022_deletion_jobs_target_drop_stmt\n FROM @kfs_0022_deletion_jobs_target_drop_sql;\nEXECUTE kfs_0022_deletion_jobs_target_drop_stmt;\nDEALLOCATE PREPARE kfs_0022_deletion_jobs_target_drop_stmt;\n\nSET @kfs_0022_deletion_jobs_target_add_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_jobs'\n AND constraint_name = 'deletion_jobs_target_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `deletion_jobs` ADD CONSTRAINT `deletion_jobs_target_ck` CHECK (`target_type` IN (''knowledge_space'', ''source'', ''document_asset'', ''logical_document'') AND ((`target_type` = ''source'' AND `delete_mode` IN (''keep'', ''cascade'') AND `name_challenge_digest` IS NULL) OR (`target_type` = ''knowledge_space'' AND `delete_mode` = ''cascade'' AND `name_challenge_digest` IS NOT NULL) OR (`target_type` IN (''document_asset'', ''logical_document'') AND `delete_mode` = ''cascade'' AND `name_challenge_digest` IS NULL)))'\n);\nPREPARE kfs_0022_deletion_jobs_target_add_stmt\n FROM @kfs_0022_deletion_jobs_target_add_sql;\nEXECUTE kfs_0022_deletion_jobs_target_add_stmt;\nDEALLOCATE PREPARE kfs_0022_deletion_jobs_target_add_stmt;\n\nSET @kfs_0022_deletion_tombstones_target_drop_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_tombstones'\n AND constraint_name = 'deletion_tombstones_target_ck'\n AND check_clause NOT LIKE '%logical_document%'\n ),\n 'ALTER TABLE `deletion_tombstones` DROP CHECK `deletion_tombstones_target_ck`',\n 'DO 0'\n);\nPREPARE kfs_0022_deletion_tombstones_target_drop_stmt\n FROM @kfs_0022_deletion_tombstones_target_drop_sql;\nEXECUTE kfs_0022_deletion_tombstones_target_drop_stmt;\nDEALLOCATE PREPARE kfs_0022_deletion_tombstones_target_drop_stmt;\n\nSET @kfs_0022_deletion_tombstones_target_add_sql = IF(\n EXISTS(\n SELECT 1 FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_tombstones'\n AND constraint_name = 'deletion_tombstones_target_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `deletion_tombstones` ADD CONSTRAINT `deletion_tombstones_target_ck` CHECK (`target_type` IN (''knowledge_space'', ''source'', ''document_asset'', ''logical_document''))'\n);\nPREPARE kfs_0022_deletion_tombstones_target_add_stmt\n FROM @kfs_0022_deletion_tombstones_target_add_sql;\nEXECUTE kfs_0022_deletion_tombstones_target_add_stmt;\nDEALLOCATE PREPARE kfs_0022_deletion_tombstones_target_add_stmt;\n\nCREATE UNIQUE INDEX IF NOT EXISTS `sources_space_id_uq`\n ON `sources` (`knowledge_space_id`, `id`);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `document_compilation_attempts_scope_id_uq`\n ON `document_compilation_attempts` (`tenant_id`, `knowledge_space_id`, `id`);\n\nCREATE TABLE IF NOT EXISTS `logical_documents` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `source_id` CHAR(36),\n `provider_item_id` VARCHAR(1024),\n `provider_item_digest` CHAR(64),\n `title` TEXT NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `deletion_job_id` CHAR(36),\n `deleting_at` DATETIME(3),\n `active_revision` INT,\n `row_version` INT NOT NULL,\n `system_metadata` JSON NOT NULL,\n `user_metadata` JSON NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n UNIQUE KEY `logical_documents_scope_id_uq` (`tenant_id`, `knowledge_space_id`, `id`),\n CONSTRAINT `logical_documents_status_ck`\n CHECK (`status` IN ('pending', 'ready', 'failed', 'deleting')),\n CONSTRAINT `logical_documents_deletion_lifecycle_ck`\n CHECK (\n (`status` = 'deleting' AND `deletion_job_id` IS NOT NULL AND `deleting_at` IS NOT NULL)\n OR (`status` <> 'deleting' AND `deletion_job_id` IS NULL AND `deleting_at` IS NULL)\n ),\n CONSTRAINT `logical_documents_active_revision_ck`\n CHECK (`active_revision` IS NULL OR `active_revision` > 0),\n CONSTRAINT `logical_documents_row_version_ck` CHECK (`row_version` >= 0),\n CONSTRAINT `logical_documents_provider_identity_ck`\n CHECK (\n (\n `source_id` IS NULL\n AND `provider_item_id` IS NULL\n AND `provider_item_digest` IS NULL\n )\n OR (\n `source_id` IS NOT NULL\n AND `provider_item_id` IS NOT NULL\n AND `provider_item_digest` REGEXP '^[a-f0-9]{64}$'\n )\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `logical_documents_provider_item_uq`\n ON `logical_documents` (`provider_item_digest`);\nCREATE INDEX IF NOT EXISTS `logical_documents_space_cursor_idx`\n ON `logical_documents` (`tenant_id`, `knowledge_space_id`, `created_at`, `id`);\n\nCREATE TABLE IF NOT EXISTS `document_revisions` (\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_id` CHAR(36) NOT NULL,\n `revision` INT NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `document_asset_version` INT NOT NULL,\n `compilation_attempt_id` CHAR(36),\n `expected_active_revision` INT,\n `expected_document_row_version` INT NOT NULL,\n `content_hash` CHAR(64) NOT NULL,\n `mime_type` VARCHAR(255) NOT NULL,\n `size_bytes` BIGINT NOT NULL,\n `state` VARCHAR(16) NOT NULL,\n `system_metadata` JSON NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `activated_at` DATETIME(3),\n PRIMARY KEY (`tenant_id`, `knowledge_space_id`, `document_id`, `revision`),\n CONSTRAINT `document_revisions_revision_ck` CHECK (`revision` > 0),\n CONSTRAINT `document_revisions_asset_version_ck` CHECK (`document_asset_version` > 0),\n CONSTRAINT `document_revisions_expected_active_ck`\n CHECK (`expected_active_revision` IS NULL OR `expected_active_revision` > 0),\n CONSTRAINT `document_revisions_expected_row_version_ck`\n CHECK (`expected_document_row_version` >= 0),\n CONSTRAINT `document_revisions_size_ck` CHECK (`size_bytes` >= 0),\n CONSTRAINT `document_revisions_hash_ck` CHECK (`content_hash` REGEXP '^[0-9a-f]{64}$'),\n CONSTRAINT `document_revisions_state_ck`\n CHECK (`state` IN ('candidate', 'active', 'superseded', 'failed')),\n CONSTRAINT `document_revisions_activation_ck`\n CHECK (\n (`state` IN ('active', 'superseded') AND `activated_at` IS NOT NULL)\n OR (`state` IN ('candidate', 'failed') AND `activated_at` IS NULL)\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `document_id`)\n REFERENCES `logical_documents` (`tenant_id`, `knowledge_space_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (`knowledge_space_id`, `document_asset_id`, `document_asset_version`)\n REFERENCES `document_assets` (`knowledge_space_id`, `id`, `version`)\n);\n\nCREATE INDEX IF NOT EXISTS `document_revisions_asset_idx`\n ON `document_revisions` (\n `tenant_id`,\n `knowledge_space_id`,\n `document_asset_id`,\n `document_asset_version`\n );\nCREATE UNIQUE INDEX IF NOT EXISTS `document_revisions_compilation_attempt_uq`\n ON `document_revisions` (`tenant_id`, `knowledge_space_id`, `compilation_attempt_id`);\nCREATE INDEX IF NOT EXISTS `document_revisions_history_idx`\n ON `document_revisions` (`tenant_id`, `knowledge_space_id`, `document_id`, `revision`);\n\nSET @logical_documents_active_revision_fk_exists = (\n SELECT COUNT(*)\n FROM information_schema.TABLE_CONSTRAINTS\n WHERE CONSTRAINT_SCHEMA = DATABASE()\n AND TABLE_NAME = 'logical_documents'\n AND CONSTRAINT_NAME = 'logical_documents_active_revision_fk'\n);\nSET @logical_documents_active_revision_fk_sql = IF(\n @logical_documents_active_revision_fk_exists = 0,\n 'ALTER TABLE `logical_documents` ADD CONSTRAINT `logical_documents_active_revision_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `id`, `active_revision`) REFERENCES `document_revisions` (`tenant_id`, `knowledge_space_id`, `document_id`, `revision`)',\n 'SELECT 1'\n);\nPREPARE logical_documents_active_revision_fk_stmt FROM @logical_documents_active_revision_fk_sql;\nEXECUTE logical_documents_active_revision_fk_stmt;\nDEALLOCATE PREPARE logical_documents_active_revision_fk_stmt;\n\nCREATE TABLE IF NOT EXISTS `document_revision_chunks` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_id` CHAR(36) NOT NULL,\n `document_revision` INT NOT NULL,\n `parent_chunk_id` CHAR(36),\n `ordinal` INT NOT NULL,\n `token_count` INT NOT NULL,\n `text` TEXT NOT NULL,\n `system_metadata` JSON NOT NULL,\n `user_metadata` JSON NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n UNIQUE (`tenant_id`, `knowledge_space_id`, `document_id`, `document_revision`, `id`),\n CONSTRAINT `document_revision_chunks_ordinal_ck` CHECK (`ordinal` >= 0),\n CONSTRAINT `document_revision_chunks_tokens_ck` CHECK (`token_count` >= 0),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `document_id`, `document_revision`)\n REFERENCES `document_revisions` (\n `tenant_id`, `knowledge_space_id`, `document_id`, `revision`\n ) ON DELETE CASCADE,\n FOREIGN KEY (\n `tenant_id`,\n `knowledge_space_id`,\n `document_id`,\n `document_revision`,\n `parent_chunk_id`\n ) REFERENCES `document_revision_chunks` (\n `tenant_id`,\n `knowledge_space_id`,\n `document_id`,\n `document_revision`,\n `id`\n ) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `document_revision_chunks_ordinal_uq`\n ON `document_revision_chunks` (\n `tenant_id`, `knowledge_space_id`, `document_id`, `document_revision`, `ordinal`\n );\nCREATE INDEX IF NOT EXISTS `document_revision_chunks_cursor_idx`\n ON `document_revision_chunks` (\n `tenant_id`, `knowledge_space_id`, `document_id`, `document_revision`, `id`\n );\n\nCREATE TABLE IF NOT EXISTS `document_chunk_state_changes` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_id` CHAR(36) NOT NULL,\n `document_revision` INT NOT NULL,\n `chunk_id` CHAR(36) NOT NULL,\n `enabled` BOOLEAN NOT NULL,\n `state` VARCHAR(16) NOT NULL,\n `compilation_attempt_id` CHAR(36) NOT NULL,\n `candidate_publication_id` CHAR(36),\n `candidate_fingerprint` VARCHAR(86),\n `created_at` DATETIME(3) NOT NULL,\n `activated_at` DATETIME(3),\n CONSTRAINT `document_chunk_state_changes_state_ck`\n CHECK (`state` IN ('candidate', 'active', 'superseded', 'failed')),\n CONSTRAINT `document_chunk_state_changes_activation_ck`\n CHECK (\n (`state` IN ('active', 'superseded') AND `activated_at` IS NOT NULL)\n OR (`state` IN ('candidate', 'failed') AND `activated_at` IS NULL)\n ),\n CONSTRAINT `document_chunk_state_changes_candidate_pair_ck`\n CHECK (\n (`candidate_publication_id` IS NULL AND `candidate_fingerprint` IS NULL)\n OR (`candidate_publication_id` IS NOT NULL AND `candidate_fingerprint` IS NOT NULL)\n ),\n FOREIGN KEY (\n `tenant_id`,\n `knowledge_space_id`,\n `document_id`,\n `document_revision`,\n `chunk_id`\n ) REFERENCES `document_revision_chunks` (\n `tenant_id`,\n `knowledge_space_id`,\n `document_id`,\n `document_revision`,\n `id`\n ) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `document_chunk_state_changes_candidate_uq`\n ON `document_chunk_state_changes` (\n `tenant_id`, `knowledge_space_id`, `document_id`, `document_revision`, `chunk_id`, `candidate_publication_id`\n );\nCREATE UNIQUE INDEX IF NOT EXISTS `document_chunk_state_changes_attempt_uq`\n ON `document_chunk_state_changes` (\n `tenant_id`, `knowledge_space_id`, `compilation_attempt_id`\n );\nCREATE INDEX IF NOT EXISTS `document_chunk_state_changes_active_idx`\n ON `document_chunk_state_changes` (`chunk_id`, `state`, `activated_at`, `id`);\n\nCREATE TABLE IF NOT EXISTS `document_settings_revisions` (\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_id` CHAR(36) NOT NULL,\n `revision` INT NOT NULL,\n `settings` JSON NOT NULL,\n `state` VARCHAR(16) NOT NULL,\n `created_by_subject_id` VARCHAR(255) NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `activated_at` DATETIME(3),\n PRIMARY KEY (`tenant_id`, `knowledge_space_id`, `document_id`, `revision`),\n CONSTRAINT `document_settings_revisions_revision_ck` CHECK (`revision` > 0),\n CONSTRAINT `document_settings_revisions_state_ck`\n CHECK (`state` IN ('candidate', 'active', 'superseded', 'failed')),\n CONSTRAINT `document_settings_revisions_activation_ck`\n CHECK (\n (`state` IN ('active', 'superseded') AND `activated_at` IS NOT NULL)\n OR (`state` IN ('candidate', 'failed') AND `activated_at` IS NULL)\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `document_id`)\n REFERENCES `logical_documents` (`tenant_id`, `knowledge_space_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS `document_settings_heads` (\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_id` CHAR(36) NOT NULL,\n `active_revision` INT NOT NULL,\n `row_version` INT NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n PRIMARY KEY (`tenant_id`, `knowledge_space_id`, `document_id`),\n CONSTRAINT `document_settings_heads_revision_ck` CHECK (`active_revision` > 0),\n CONSTRAINT `document_settings_heads_row_version_ck` CHECK (`row_version` >= 0),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `document_id`, `active_revision`)\n REFERENCES `document_settings_revisions` (\n `tenant_id`, `knowledge_space_id`, `document_id`, `revision`\n )\n);\n\nCREATE TABLE IF NOT EXISTS `document_reindex_attempts` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_id` CHAR(36) NOT NULL,\n `document_revision` INT NOT NULL,\n `settings_revision` INT NOT NULL,\n `expected_settings_head_revision` INT NOT NULL,\n `state` VARCHAR(16) NOT NULL,\n `active_slot` INT,\n `compilation_attempt_id` CHAR(36) NOT NULL,\n `candidate_publication_id` CHAR(36),\n `candidate_fingerprint` VARCHAR(86),\n `row_version` INT NOT NULL,\n `error_code` VARCHAR(64),\n `error_message` TEXT,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `completed_at` DATETIME(3),\n CONSTRAINT `document_reindex_attempts_state_ck`\n CHECK (`state` IN ('queued', 'running', 'succeeded', 'failed', 'canceled')),\n CONSTRAINT `document_reindex_attempts_active_slot_ck`\n CHECK (`active_slot` IS NULL OR `active_slot` = 1),\n CONSTRAINT `document_reindex_attempts_row_version_ck` CHECK (`row_version` >= 0),\n CONSTRAINT `document_reindex_attempts_expected_settings_head_revision_ck`\n CHECK (`expected_settings_head_revision` > 0),\n CONSTRAINT `document_reindex_attempts_lifecycle_ck`\n CHECK (\n (`state` IN ('queued', 'running') AND `active_slot` = 1 AND `completed_at` IS NULL)\n OR\n (`state` IN ('succeeded', 'failed', 'canceled') AND `active_slot` IS NULL AND `completed_at` IS NOT NULL)\n ),\n CONSTRAINT `document_reindex_attempts_candidate_pair_ck`\n CHECK (\n (`candidate_publication_id` IS NULL AND `candidate_fingerprint` IS NULL)\n OR\n (`candidate_publication_id` IS NOT NULL AND `candidate_fingerprint` IS NOT NULL)\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `document_id`, `document_revision`)\n REFERENCES `document_revisions` (\n `tenant_id`, `knowledge_space_id`, `document_id`, `revision`\n ) ON DELETE RESTRICT,\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `document_id`, `settings_revision`)\n REFERENCES `document_settings_revisions` (\n `tenant_id`, `knowledge_space_id`, `document_id`, `revision`\n ) ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `document_reindex_attempts_active_uq`\n ON `document_reindex_attempts` (`tenant_id`, `knowledge_space_id`, `document_id`, `active_slot`);\nCREATE INDEX IF NOT EXISTS `document_reindex_attempts_cursor_idx`\n ON `document_reindex_attempts` (\n `tenant_id`, `knowledge_space_id`, `document_id`, `created_at`, `id`\n );\nCREATE INDEX IF NOT EXISTS `document_compilation_attempts_space_cursor_idx`\n ON `document_compilation_attempts` (\n `tenant_id`, `knowledge_space_id`, `created_at`, `id`\n );\n\nINSERT INTO `logical_documents` (\n `id`,\n `tenant_id`,\n `knowledge_space_id`,\n `source_id`,\n `provider_item_id`,\n `provider_item_digest`,\n `title`,\n `status`,\n `active_revision`,\n `row_version`,\n `system_metadata`,\n `user_metadata`,\n `created_at`,\n `updated_at`\n)\nSELECT\n asset.`id`,\n space.`tenant_id`,\n asset.`knowledge_space_id`,\n NULL,\n NULL,\n NULL,\n asset.`filename`,\n CASE\n WHEN asset.`parser_status` = 'parsed' THEN 'ready'\n WHEN asset.`parser_status` = 'failed' THEN 'failed'\n ELSE 'pending'\n END,\n NULL,\n 0,\n JSON_OBJECT(\n 'legacyDocumentAssetId', asset.`id`,\n 'provenance', asset.`metadata`\n ),\n JSON_OBJECT(),\n asset.`created_at`,\n COALESCE(asset.`updated_at`, asset.`created_at`)\nFROM `document_assets` asset\nJOIN `knowledge_spaces` space ON space.`id` = asset.`knowledge_space_id`\nON DUPLICATE KEY UPDATE `id` = VALUES(`id`);\n\nINSERT INTO `document_revisions` (\n `tenant_id`,\n `knowledge_space_id`,\n `document_id`,\n `revision`,\n `document_asset_id`,\n `document_asset_version`,\n `compilation_attempt_id`,\n `expected_active_revision`,\n `expected_document_row_version`,\n `content_hash`,\n `mime_type`,\n `size_bytes`,\n `state`,\n `system_metadata`,\n `created_at`,\n `activated_at`\n)\nSELECT\n space.`tenant_id`,\n asset.`knowledge_space_id`,\n asset.`id`,\n asset.`version`,\n asset.`id`,\n asset.`version`,\n NULL,\n NULL,\n 0,\n asset.`sha256`,\n asset.`mime_type`,\n asset.`size_bytes`,\n 'active',\n JSON_OBJECT('provenance', asset.`metadata`),\n asset.`created_at`,\n COALESCE(asset.`updated_at`, asset.`created_at`)\nFROM `document_assets` asset\nJOIN `knowledge_spaces` space ON space.`id` = asset.`knowledge_space_id`\nON DUPLICATE KEY UPDATE `revision` = VALUES(`revision`);\n\nUPDATE `logical_documents` document\nJOIN `document_assets` asset\n ON document.`id` = asset.`id`\n AND document.`knowledge_space_id` = asset.`knowledge_space_id`\nSET\n document.`active_revision` = asset.`version`,\n document.`row_version` = document.`row_version` + 1\nWHERE document.`active_revision` IS NULL;\n", path: "packages/database/migrations/0022_logical_document_revisions.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0023_knowledge_space_overview\n-- Dialect: postgres\n\nALTER TABLE \"knowledge_spaces\" ADD COLUMN IF NOT EXISTS \"icon_ref\" VARCHAR(72);\nDO $kfs_space_icon_check$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'knowledge_spaces_icon_ref_ck'\n AND conrelid = 'knowledge_spaces'::regclass\n ) THEN\n ALTER TABLE \"knowledge_spaces\" ADD CONSTRAINT \"knowledge_spaces_icon_ref_ck\"\n CHECK (\n \"icon_ref\" IS NULL\n OR \"icon_ref\" ~ '^builtin:[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$'\n );\n END IF;\nEND\n$kfs_space_icon_check$;\n\nCREATE TABLE IF NOT EXISTS \"knowledge_space_activity_events\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"actor_type\" VARCHAR(16) NOT NULL,\n \"actor_subject_id\" VARCHAR(255),\n \"action\" VARCHAR(64) NOT NULL,\n \"resource_type\" VARCHAR(32) NOT NULL,\n \"resource_id\" VARCHAR(255),\n \"result\" VARCHAR(16) NOT NULL,\n \"required_permission_scope\" JSONB NOT NULL,\n \"details\" JSONB NOT NULL,\n \"occurred_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"knowledge_space_activity_actor_ck\" CHECK (\n (\"actor_type\" = 'member' AND \"actor_subject_id\" IS NOT NULL)\n OR (\"actor_type\" = 'system' AND \"actor_subject_id\" IS NULL)\n ),\n CONSTRAINT \"knowledge_space_activity_action_ck\" CHECK (\n \"action\" IN (\n 'query.requested', 'query.completed', 'query.failed',\n 'document.published', 'document.failed',\n 'source.synced', 'source.failed',\n 'settings.updated', 'permission.updated', 'profile.published', 'worker.failed'\n )\n ),\n CONSTRAINT \"knowledge_space_activity_resource_ck\" CHECK (\n \"resource_type\" IN (\n 'knowledge-space', 'query', 'document', 'source', 'permission',\n 'profile', 'publication', 'worker'\n )\n ),\n CONSTRAINT \"knowledge_space_activity_result_ck\"\n CHECK (\"result\" IN ('pending', 'success', 'failure', 'canceled')),\n CONSTRAINT \"knowledge_space_activity_scope_json_ck\"\n CHECK (jsonb_typeof(\"required_permission_scope\") = 'array'),\n CONSTRAINT \"knowledge_space_activity_details_json_ck\"\n CHECK (jsonb_typeof(\"details\") = 'object'),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_activity_scope_id_uq\"\n ON \"knowledge_space_activity_events\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_space_activity_feed_idx\"\n ON \"knowledge_space_activity_events\" (\n \"tenant_id\", \"knowledge_space_id\", \"occurred_at\" DESC, \"id\" DESC\n );\nCREATE INDEX IF NOT EXISTS \"knowledge_space_activity_stats_idx\"\n ON \"knowledge_space_activity_events\" (\n \"tenant_id\", \"knowledge_space_id\", \"action\", \"occurred_at\"\n );\nCREATE INDEX IF NOT EXISTS \"knowledge_space_activity_scope_gin_idx\"\n ON \"knowledge_space_activity_events\" USING GIN (\"required_permission_scope\");\n\nCREATE TABLE IF NOT EXISTS \"knowledge_space_attention_states\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"issue_key\" VARCHAR(255) NOT NULL,\n \"rule_id\" VARCHAR(64) NOT NULL,\n \"resource_type\" VARCHAR(32) NOT NULL,\n \"resource_id\" VARCHAR(255) NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"dismissed_until\" TIMESTAMPTZ,\n \"revision\" INTEGER NOT NULL,\n \"updated_by_subject_id\" VARCHAR(255),\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"knowledge_space_attention_rule_ck\" CHECK (\n \"rule_id\" IN (\n 'stale-source', 'failed-document', 'low-quality-query',\n 'permission-readiness', 'model-readiness'\n )\n ),\n CONSTRAINT \"knowledge_space_attention_resource_ck\" CHECK (\n \"resource_type\" IN ('knowledge-space', 'document', 'source', 'failed-query')\n ),\n CONSTRAINT \"knowledge_space_attention_status_ck\"\n CHECK (\"status\" IN ('active', 'dismissed', 'resolved')),\n CONSTRAINT \"knowledge_space_attention_dismiss_ck\" CHECK (\n (\"status\" = 'dismissed' AND \"dismissed_until\" IS NOT NULL)\n OR (\"status\" <> 'dismissed' AND \"dismissed_until\" IS NULL)\n ),\n CONSTRAINT \"knowledge_space_attention_revision_ck\" CHECK (\"revision\" >= 1),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"knowledge_space_attention_issue_uq\"\n ON \"knowledge_space_attention_states\" (\n \"tenant_id\", \"knowledge_space_id\", \"issue_key\"\n );\nCREATE INDEX IF NOT EXISTS \"knowledge_space_attention_list_idx\"\n ON \"knowledge_space_attention_states\" (\n \"tenant_id\", \"knowledge_space_id\", \"status\", \"updated_at\" DESC, \"id\"\n );\n", path: "packages/database/migrations/0023_knowledge_space_overview.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0023_knowledge_space_overview\n-- Dialect: tidb\n\nALTER TABLE `knowledge_spaces` ADD COLUMN IF NOT EXISTS `icon_ref` VARCHAR(72);\n-- TiDB does not support ADD CONSTRAINT IF NOT EXISTS. Keep this artifact replay-safe for the\n-- crash window between executing the DDL and committing the migration-ledger row.\nSET @knowledge_spaces_icon_ref_ck_exists = (\n SELECT COUNT(*)\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'knowledge_spaces'\n AND constraint_name = 'knowledge_spaces_icon_ref_ck'\n);\nSET @knowledge_spaces_icon_ref_ck_ddl = IF(\n @knowledge_spaces_icon_ref_ck_exists = 0,\n 'ALTER TABLE `knowledge_spaces` ADD CONSTRAINT `knowledge_spaces_icon_ref_ck` CHECK (`icon_ref` IS NULL OR `icon_ref` REGEXP ''^builtin:[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$'')',\n 'DO 0'\n);\nPREPARE knowledge_spaces_icon_ref_ck_statement FROM @knowledge_spaces_icon_ref_ck_ddl;\nEXECUTE knowledge_spaces_icon_ref_ck_statement;\nDEALLOCATE PREPARE knowledge_spaces_icon_ref_ck_statement;\n\nCREATE TABLE IF NOT EXISTS `knowledge_space_activity_events` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `actor_type` VARCHAR(16) NOT NULL,\n `actor_subject_id` VARCHAR(255),\n `action` VARCHAR(64) NOT NULL,\n `resource_type` VARCHAR(32) NOT NULL,\n `resource_id` VARCHAR(255),\n `result` VARCHAR(16) NOT NULL,\n `required_permission_scope` JSON NOT NULL,\n `details` JSON NOT NULL,\n `occurred_at` DATETIME(3) NOT NULL,\n CONSTRAINT `knowledge_space_activity_actor_ck` CHECK (\n (`actor_type` = 'member' AND `actor_subject_id` IS NOT NULL)\n OR (`actor_type` = 'system' AND `actor_subject_id` IS NULL)\n ),\n CONSTRAINT `knowledge_space_activity_action_ck` CHECK (\n `action` IN (\n 'query.requested', 'query.completed', 'query.failed',\n 'document.published', 'document.failed',\n 'source.synced', 'source.failed',\n 'settings.updated', 'permission.updated', 'profile.published', 'worker.failed'\n )\n ),\n CONSTRAINT `knowledge_space_activity_resource_ck` CHECK (\n `resource_type` IN (\n 'knowledge-space', 'query', 'document', 'source', 'permission',\n 'profile', 'publication', 'worker'\n )\n ),\n CONSTRAINT `knowledge_space_activity_result_ck`\n CHECK (`result` IN ('pending', 'success', 'failure', 'canceled')),\n CONSTRAINT `knowledge_space_activity_scope_json_ck`\n CHECK (JSON_TYPE(`required_permission_scope`) = 'ARRAY'),\n CONSTRAINT `knowledge_space_activity_details_json_ck`\n CHECK (JSON_TYPE(`details`) = 'OBJECT'),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_activity_scope_id_uq`\n ON `knowledge_space_activity_events` (`tenant_id`, `knowledge_space_id`, `id`);\nCREATE INDEX IF NOT EXISTS `knowledge_space_activity_feed_idx`\n ON `knowledge_space_activity_events` (\n `tenant_id`, `knowledge_space_id`, `occurred_at` DESC, `id` DESC\n );\nCREATE INDEX IF NOT EXISTS `knowledge_space_activity_stats_idx`\n ON `knowledge_space_activity_events` (\n `tenant_id`, `knowledge_space_id`, `action`, `occurred_at`\n );\n\nCREATE TABLE IF NOT EXISTS `knowledge_space_attention_states` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `issue_key` VARCHAR(255) NOT NULL,\n `rule_id` VARCHAR(64) NOT NULL,\n `resource_type` VARCHAR(32) NOT NULL,\n `resource_id` VARCHAR(255) NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `dismissed_until` DATETIME(3),\n `revision` INT NOT NULL,\n `updated_by_subject_id` VARCHAR(255),\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `knowledge_space_attention_rule_ck` CHECK (\n `rule_id` IN (\n 'stale-source', 'failed-document', 'low-quality-query',\n 'permission-readiness', 'model-readiness'\n )\n ),\n CONSTRAINT `knowledge_space_attention_resource_ck` CHECK (\n `resource_type` IN ('knowledge-space', 'document', 'source', 'failed-query')\n ),\n CONSTRAINT `knowledge_space_attention_status_ck`\n CHECK (`status` IN ('active', 'dismissed', 'resolved')),\n CONSTRAINT `knowledge_space_attention_dismiss_ck` CHECK (\n (`status` = 'dismissed' AND `dismissed_until` IS NOT NULL)\n OR (`status` <> 'dismissed' AND `dismissed_until` IS NULL)\n ),\n CONSTRAINT `knowledge_space_attention_revision_ck` CHECK (`revision` >= 1),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `knowledge_space_attention_issue_uq`\n ON `knowledge_space_attention_states` (\n `tenant_id`, `knowledge_space_id`, `issue_key`\n );\nCREATE INDEX IF NOT EXISTS `knowledge_space_attention_list_idx`\n ON `knowledge_space_attention_states` (\n `tenant_id`, `knowledge_space_id`, `status`, `updated_at` DESC, `id`\n );\n", path: "packages/database/migrations/0023_knowledge_space_overview.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0024_quality_control\n-- Dialect: postgres\n-- New-table-only DDL plus IF NOT EXISTS indexes keeps marker-loss replay safe.\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"answer_traces_space_id_uq\"\n ON \"answer_traces\" (\"knowledge_space_id\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"quality_replay_runs\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"idempotency_key\" VARCHAR(255) NOT NULL,\n \"request_fingerprint\" VARCHAR(71) NOT NULL,\n \"mode\" VARCHAR(16) NOT NULL,\n \"state\" VARCHAR(16) NOT NULL,\n \"requested_by_subject_id\" VARCHAR(255) NOT NULL,\n \"access_channel\" VARCHAR(16) NOT NULL,\n \"permission_snapshot_id\" UUID NOT NULL,\n \"permission_snapshot_revision\" INTEGER NOT NULL,\n \"required_permission_scope\" JSONB NOT NULL,\n \"frozen_snapshot\" JSONB NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"attempt\" INTEGER NOT NULL,\n \"lease_owner\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"error_message\" TEXT,\n \"started_at\" TIMESTAMPTZ,\n \"completed_at\" TIMESTAMPTZ,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"quality_replay_runs_state_ck\" CHECK (\n \"mode\" IN ('fast', 'research', 'deep')\n AND \"state\" IN ('queued', 'running', 'passed', 'failed', 'canceled')\n ),\n CONSTRAINT \"quality_replay_runs_lease_ck\" CHECK (\n (\"state\" = 'running' AND \"lease_owner\" IS NOT NULL AND \"lease_token\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL AND \"completed_at\" IS NULL)\n OR (\"state\" <> 'running' AND \"lease_owner\" IS NULL AND \"lease_token\" IS NULL\n AND \"lease_expires_at\" IS NULL)\n ),\n CONSTRAINT \"quality_replay_runs_terminal_ck\" CHECK (\n (\"state\" IN ('passed', 'failed', 'canceled') AND \"completed_at\" IS NOT NULL)\n OR (\"state\" IN ('queued', 'running') AND \"completed_at\" IS NULL)\n ),\n CONSTRAINT \"quality_replay_runs_revision_ck\" CHECK (\n \"revision\" >= 1 AND \"attempt\" >= 0 AND \"permission_snapshot_revision\" >= 1\n AND \"request_fingerprint\" ~ '^sha256:[a-f0-9]{64}$'\n ),\n CONSTRAINT \"quality_replay_runs_scope_json_ck\"\n CHECK (jsonb_typeof(\"required_permission_scope\") = 'array'),\n CONSTRAINT \"quality_replay_runs_snapshot_json_ck\"\n CHECK (jsonb_typeof(\"frozen_snapshot\") = 'object'),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\n \"knowledge_space_id\", \"permission_snapshot_id\", \"requested_by_subject_id\", \"access_channel\"\n ) REFERENCES \"knowledge_space_permission_snapshots\" (\n \"knowledge_space_id\", \"id\", \"subject_id\", \"access_channel\"\n ) ON DELETE RESTRICT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"quality_replay_runs_scope_id_uq\"\n ON \"quality_replay_runs\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"quality_replay_runs_idempotency_uq\"\n ON \"quality_replay_runs\" (\"tenant_id\", \"knowledge_space_id\", \"idempotency_key\");\nCREATE INDEX IF NOT EXISTS \"quality_replay_runs_scope_created_idx\"\n ON \"quality_replay_runs\" (\"tenant_id\", \"knowledge_space_id\", \"created_at\" DESC, \"id\" DESC);\nCREATE INDEX IF NOT EXISTS \"quality_replay_runs_claim_idx\"\n ON \"quality_replay_runs\" (\"state\", \"lease_expires_at\", \"created_at\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"quality_replay_items\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"run_id\" UUID NOT NULL,\n \"golden_question_id\" UUID NOT NULL,\n \"ordinal\" INTEGER NOT NULL,\n \"question\" TEXT NOT NULL,\n \"expected_evidence_ids\" JSONB NOT NULL,\n \"state\" VARCHAR(16) NOT NULL,\n \"result\" JSONB,\n \"trace_id\" UUID,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"quality_replay_items_state_ck\" CHECK (\n \"ordinal\" >= 1 AND \"state\" IN ('queued', 'running', 'passed', 'failed', 'canceled')\n ),\n CONSTRAINT \"quality_replay_items_expected_json_ck\"\n CHECK (jsonb_typeof(\"expected_evidence_ids\") = 'array'),\n CONSTRAINT \"quality_replay_items_result_json_ck\"\n CHECK (\"result\" IS NULL OR jsonb_typeof(\"result\") = 'object'),\n FOREIGN KEY (\"run_id\") REFERENCES \"quality_replay_runs\" (\"id\") ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS \"quality_replay_items_run_ordinal_uq\"\n ON \"quality_replay_items\" (\"run_id\", \"ordinal\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"quality_replay_items_run_golden_uq\"\n ON \"quality_replay_items\" (\"run_id\", \"golden_question_id\");\n\nCREATE TABLE IF NOT EXISTS \"quality_replay_outbox\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"run_id\" UUID NOT NULL,\n \"delivery_revision\" INTEGER NOT NULL,\n \"event_type\" VARCHAR(64) NOT NULL,\n \"delivery_state\" VARCHAR(16) NOT NULL,\n \"attempt\" INTEGER NOT NULL,\n \"lease_owner\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"delivered_at\" TIMESTAMPTZ,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"quality_replay_outbox_state_ck\" CHECK (\n \"delivery_revision\" >= 1\n AND \"delivery_state\" IN ('pending', 'claimed', 'delivered') AND \"attempt\" >= 0\n AND ((\"delivery_state\" = 'claimed' AND \"lease_owner\" IS NOT NULL\n AND \"lease_token\" IS NOT NULL AND \"lease_expires_at\" IS NOT NULL\n AND \"delivered_at\" IS NULL)\n OR (\"delivery_state\" <> 'claimed' AND \"lease_owner\" IS NULL\n AND \"lease_token\" IS NULL AND \"lease_expires_at\" IS NULL))\n AND ((\"delivery_state\" = 'delivered' AND \"delivered_at\" IS NOT NULL)\n OR (\"delivery_state\" <> 'delivered' AND \"delivered_at\" IS NULL))\n ),\n FOREIGN KEY (\"run_id\") REFERENCES \"quality_replay_runs\" (\"id\") ON DELETE CASCADE\n);\nALTER TABLE \"quality_replay_outbox\"\n ADD COLUMN IF NOT EXISTS \"delivery_revision\" INTEGER;\nWITH ranked_delivery AS (\n SELECT \"id\", ROW_NUMBER() OVER (\n PARTITION BY \"run_id\" ORDER BY \"created_at\", \"id\"\n ) AS \"delivery_revision\"\n FROM \"quality_replay_outbox\"\n)\nUPDATE \"quality_replay_outbox\" AS outbox\nSET \"delivery_revision\" = ranked_delivery.\"delivery_revision\"\nFROM ranked_delivery\nWHERE outbox.\"id\" = ranked_delivery.\"id\" AND outbox.\"delivery_revision\" IS NULL;\nALTER TABLE \"quality_replay_outbox\"\n ALTER COLUMN \"delivery_revision\" SET NOT NULL;\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'quality_replay_outbox_delivery_revision_ck'\n AND conrelid = 'quality_replay_outbox'::regclass\n ) THEN\n ALTER TABLE \"quality_replay_outbox\"\n ADD CONSTRAINT \"quality_replay_outbox_delivery_revision_ck\"\n CHECK (\"delivery_revision\" >= 1);\n END IF;\nEND $$;\nCREATE INDEX IF NOT EXISTS \"quality_replay_outbox_claim_idx\"\n ON \"quality_replay_outbox\" (\"delivery_state\", \"lease_expires_at\", \"created_at\", \"id\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"quality_replay_outbox_run_delivery_uq\"\n ON \"quality_replay_outbox\" (\"run_id\", \"delivery_revision\");\nCREATE INDEX IF NOT EXISTS \"quality_replay_outbox_run_idx\"\n ON \"quality_replay_outbox\" (\"run_id\", \"delivery_state\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"quality_bad_cases\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"trace_id\" UUID NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"reason\" TEXT NOT NULL,\n \"tags\" JSONB NOT NULL,\n \"replay_run_id\" UUID,\n \"actor_subject_id\" VARCHAR(255) NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"required_permission_scope\" JSONB NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"quality_bad_cases_state_ck\" CHECK (\n \"status\" IN ('open', 'replaying', 'fixed', 'dismissed') AND \"revision\" >= 1\n AND (\"status\" <> 'replaying' OR \"replay_run_id\" IS NOT NULL)\n ),\n CONSTRAINT \"quality_bad_cases_tags_json_ck\" CHECK (jsonb_typeof(\"tags\") = 'array'),\n CONSTRAINT \"quality_bad_cases_scope_json_ck\"\n CHECK (jsonb_typeof(\"required_permission_scope\") = 'array'),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"quality_bad_cases_trace_scope_fk\"\n FOREIGN KEY (\"knowledge_space_id\", \"trace_id\")\n REFERENCES \"answer_traces\" (\"knowledge_space_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"replay_run_id\")\n REFERENCES \"quality_replay_runs\" (\"tenant_id\", \"knowledge_space_id\", \"id\") ON DELETE RESTRICT\n);\nCREATE INDEX IF NOT EXISTS \"quality_bad_cases_scope_status_idx\"\n ON \"quality_bad_cases\" (\"tenant_id\", \"knowledge_space_id\", \"status\", \"created_at\" DESC, \"id\" DESC);\n\nCREATE TABLE IF NOT EXISTS \"quality_missing_evidence_reviews\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"trace_id\" UUID NOT NULL,\n \"item_key\" VARCHAR(71) NOT NULL,\n \"status\" VARCHAR(16) NOT NULL,\n \"reason\" TEXT,\n \"actor_subject_id\" VARCHAR(255) NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"required_permission_scope\" JSONB NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"quality_missing_evidence_reviews_state_ck\" CHECK (\n \"status\" IN ('active', 'dismissed') AND \"revision\" >= 1\n AND \"item_key\" ~ '^sha256:[a-f0-9]{64}$'\n ),\n CONSTRAINT \"quality_missing_evidence_reviews_scope_json_ck\"\n CHECK (jsonb_typeof(\"required_permission_scope\") = 'array'),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"quality_missing_evidence_reviews_trace_scope_fk\"\n FOREIGN KEY (\"knowledge_space_id\", \"trace_id\")\n REFERENCES \"answer_traces\" (\"knowledge_space_id\", \"id\") ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS \"quality_missing_reviews_item_uq\"\n ON \"quality_missing_evidence_reviews\" (\n \"tenant_id\", \"knowledge_space_id\", \"trace_id\", \"item_key\"\n );\n\nCREATE TABLE IF NOT EXISTS \"quality_resource_history\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"aggregate_type\" VARCHAR(32) NOT NULL,\n \"aggregate_id\" UUID NOT NULL,\n \"action\" VARCHAR(32) NOT NULL,\n \"actor_subject_id\" VARCHAR(255) NOT NULL,\n \"from_status\" VARCHAR(16),\n \"to_status\" VARCHAR(16) NOT NULL,\n \"reason\" TEXT,\n \"revision\" INTEGER NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"quality_resource_history_type_ck\" CHECK (\n \"aggregate_type\" IN ('bad-case', 'missing-evidence') AND \"revision\" >= 1\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS \"quality_resource_history_revision_uq\"\n ON \"quality_resource_history\" (\n \"tenant_id\", \"knowledge_space_id\", \"aggregate_type\", \"aggregate_id\", \"revision\"\n );\n\n-- Golden-question visibility is frozen from referenced evidence. Legacy rows intentionally retain\n-- NULL provenance and therefore fail every public tenant/scope read.\nALTER TABLE \"golden_questions\"\n ADD COLUMN IF NOT EXISTS \"tenant_id\" VARCHAR(255),\n ADD COLUMN IF NOT EXISTS \"required_permission_scope\" JSONB;\n\nALTER TABLE \"golden_questions\"\n DROP CONSTRAINT IF EXISTS \"golden_questions_scope_json_ck\",\n ADD CONSTRAINT \"golden_questions_scope_json_ck\" CHECK (\n (\"tenant_id\" IS NULL AND \"required_permission_scope\" IS NULL)\n OR (\"tenant_id\" IS NOT NULL AND \"required_permission_scope\" IS NOT NULL\n AND jsonb_typeof(\"required_permission_scope\") = 'array')\n );\n\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'golden_questions_scope_fk'\n AND conrelid = 'golden_questions'::regclass\n ) THEN\n ALTER TABLE \"golden_questions\"\n ADD CONSTRAINT \"golden_questions_scope_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE;\n END IF;\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'quality_bad_cases_trace_scope_fk'\n AND conrelid = 'quality_bad_cases'::regclass\n ) THEN\n ALTER TABLE \"quality_bad_cases\"\n ADD CONSTRAINT \"quality_bad_cases_trace_scope_fk\"\n FOREIGN KEY (\"knowledge_space_id\", \"trace_id\")\n REFERENCES \"answer_traces\" (\"knowledge_space_id\", \"id\") ON DELETE CASCADE;\n END IF;\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'quality_missing_evidence_reviews_trace_scope_fk'\n AND conrelid = 'quality_missing_evidence_reviews'::regclass\n ) THEN\n ALTER TABLE \"quality_missing_evidence_reviews\"\n ADD CONSTRAINT \"quality_missing_evidence_reviews_trace_scope_fk\"\n FOREIGN KEY (\"knowledge_space_id\", \"trace_id\")\n REFERENCES \"answer_traces\" (\"knowledge_space_id\", \"id\") ON DELETE CASCADE;\n END IF;\nEND $$;\n\nDROP INDEX IF EXISTS \"golden_questions_space_id_idx\";\nCREATE INDEX IF NOT EXISTS \"golden_questions_space_id_idx\"\n ON \"golden_questions\" (\"tenant_id\", \"knowledge_space_id\", \"id\");\nDROP INDEX IF EXISTS \"golden_questions_space_created_idx\";\nCREATE INDEX IF NOT EXISTS \"golden_questions_space_created_idx\"\n ON \"golden_questions\" (\"tenant_id\", \"knowledge_space_id\", \"created_at\", \"id\");\n\nCREATE INDEX IF NOT EXISTS \"failed_queries_space_created_idx\"\n ON \"failed_queries\" (\"knowledge_space_id\", \"created_at\", \"id\");\n\n-- Legacy failed-query rows deliberately remain provenance-free and are fail-closed by every read.\n-- New captures write the complete binding atomically; nullable columns keep the migration online.\nALTER TABLE \"failed_queries\"\n ADD COLUMN IF NOT EXISTS \"tenant_id\" VARCHAR(255),\n ADD COLUMN IF NOT EXISTS \"requested_by_subject_id\" VARCHAR(255),\n ADD COLUMN IF NOT EXISTS \"access_channel\" VARCHAR(16),\n ADD COLUMN IF NOT EXISTS \"permission_snapshot_id\" UUID,\n ADD COLUMN IF NOT EXISTS \"permission_snapshot_revision\" INTEGER,\n ADD COLUMN IF NOT EXISTS \"required_permission_scope\" JSONB,\n ADD COLUMN IF NOT EXISTS \"revision\" INTEGER;\n\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'failed_queries_permission_binding_ck'\n AND conrelid = 'failed_queries'::regclass\n ) THEN\n ALTER TABLE \"failed_queries\"\n ADD CONSTRAINT \"failed_queries_permission_binding_ck\" CHECK (\n (\"tenant_id\" IS NULL AND \"requested_by_subject_id\" IS NULL\n AND \"access_channel\" IS NULL AND \"permission_snapshot_id\" IS NULL\n AND \"permission_snapshot_revision\" IS NULL\n AND \"required_permission_scope\" IS NULL AND \"revision\" IS NULL)\n OR (\"tenant_id\" IS NOT NULL AND \"requested_by_subject_id\" IS NOT NULL\n AND \"access_channel\" IS NOT NULL\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')\n AND \"permission_snapshot_id\" IS NOT NULL\n AND \"permission_snapshot_revision\" IS NOT NULL\n AND \"permission_snapshot_revision\" >= 1\n AND \"required_permission_scope\" IS NOT NULL\n AND jsonb_typeof(\"required_permission_scope\") = 'array'\n AND \"revision\" IS NOT NULL AND \"revision\" >= 1)\n );\n END IF;\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'failed_queries_scope_fk'\n AND conrelid = 'failed_queries'::regclass\n ) THEN\n ALTER TABLE \"failed_queries\"\n ADD CONSTRAINT \"failed_queries_scope_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE;\n END IF;\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'failed_queries_permission_snapshot_fk'\n AND conrelid = 'failed_queries'::regclass\n ) THEN\n ALTER TABLE \"failed_queries\"\n ADD CONSTRAINT \"failed_queries_permission_snapshot_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"permission_snapshot_id\",\n \"requested_by_subject_id\", \"access_channel\")\n REFERENCES \"knowledge_space_permission_snapshots\"\n (\"tenant_id\", \"knowledge_space_id\", \"id\", \"subject_id\", \"access_channel\")\n ON DELETE RESTRICT;\n END IF;\nEND $$;\n\nCREATE INDEX IF NOT EXISTS \"failed_queries_subject_created_idx\"\n ON \"failed_queries\" (\n \"tenant_id\", \"knowledge_space_id\", \"requested_by_subject_id\", \"created_at\", \"id\"\n );\n", path: "packages/database/migrations/0024_quality_control.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0024_quality_control\n-- Dialect: tidb\n-- New-table-only DDL plus IF NOT EXISTS indexes keeps marker-loss replay safe.\n\nCREATE UNIQUE INDEX IF NOT EXISTS `answer_traces_space_id_uq`\n ON `answer_traces` (`knowledge_space_id`, `id`);\n\nCREATE TABLE IF NOT EXISTS `quality_replay_runs` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `idempotency_key` VARCHAR(255) NOT NULL,\n `request_fingerprint` VARCHAR(71) NOT NULL,\n `mode` VARCHAR(16) NOT NULL,\n `state` VARCHAR(16) NOT NULL,\n `requested_by_subject_id` VARCHAR(255) NOT NULL,\n `access_channel` VARCHAR(16) NOT NULL,\n `permission_snapshot_id` CHAR(36) NOT NULL,\n `permission_snapshot_revision` INT NOT NULL,\n `required_permission_scope` JSON NOT NULL,\n `frozen_snapshot` JSON NOT NULL,\n `revision` INT NOT NULL,\n `attempt` INT NOT NULL,\n `lease_owner` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `error_message` TEXT,\n `started_at` DATETIME(3),\n `completed_at` DATETIME(3),\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `quality_replay_runs_state_ck` CHECK (\n `mode` IN ('fast', 'research', 'deep')\n AND `state` IN ('queued', 'running', 'passed', 'failed', 'canceled')\n ),\n CONSTRAINT `quality_replay_runs_lease_ck` CHECK (\n (`state` = 'running' AND `lease_owner` IS NOT NULL AND `lease_token` IS NOT NULL\n AND `lease_expires_at` IS NOT NULL AND `completed_at` IS NULL)\n OR (`state` <> 'running' AND `lease_owner` IS NULL AND `lease_token` IS NULL\n AND `lease_expires_at` IS NULL)\n ),\n CONSTRAINT `quality_replay_runs_terminal_ck` CHECK (\n (`state` IN ('passed', 'failed', 'canceled') AND `completed_at` IS NOT NULL)\n OR (`state` IN ('queued', 'running') AND `completed_at` IS NULL)\n ),\n CONSTRAINT `quality_replay_runs_revision_ck` CHECK (\n `revision` >= 1 AND `attempt` >= 0 AND `permission_snapshot_revision` >= 1\n AND `request_fingerprint` REGEXP '^sha256:[a-f0-9]{64}$'\n ),\n CONSTRAINT `quality_replay_runs_scope_json_ck`\n CHECK (JSON_TYPE(`required_permission_scope`) = 'ARRAY'),\n CONSTRAINT `quality_replay_runs_snapshot_json_ck`\n CHECK (JSON_TYPE(`frozen_snapshot`) = 'OBJECT'),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (\n `knowledge_space_id`, `permission_snapshot_id`, `requested_by_subject_id`, `access_channel`\n ) REFERENCES `knowledge_space_permission_snapshots` (\n `knowledge_space_id`, `id`, `subject_id`, `access_channel`\n ) ON DELETE RESTRICT,\n UNIQUE KEY `quality_replay_runs_scope_id_uq` (`tenant_id`, `knowledge_space_id`, `id`)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `quality_replay_runs_scope_id_uq`\n ON `quality_replay_runs` (`tenant_id`, `knowledge_space_id`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `quality_replay_runs_idempotency_uq`\n ON `quality_replay_runs` (`tenant_id`, `knowledge_space_id`, `idempotency_key`);\nCREATE INDEX IF NOT EXISTS `quality_replay_runs_scope_created_idx`\n ON `quality_replay_runs` (`tenant_id`, `knowledge_space_id`, `created_at` DESC, `id` DESC);\nCREATE INDEX IF NOT EXISTS `quality_replay_runs_claim_idx`\n ON `quality_replay_runs` (`state`, `lease_expires_at`, `created_at`, `id`);\n\nCREATE TABLE IF NOT EXISTS `quality_replay_items` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `run_id` CHAR(36) NOT NULL,\n `golden_question_id` CHAR(36) NOT NULL,\n `ordinal` INT NOT NULL,\n `question` TEXT NOT NULL,\n `expected_evidence_ids` JSON NOT NULL,\n `state` VARCHAR(16) NOT NULL,\n `result` JSON,\n `trace_id` CHAR(36),\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `quality_replay_items_state_ck` CHECK (\n `ordinal` >= 1 AND `state` IN ('queued', 'running', 'passed', 'failed', 'canceled')\n ),\n CONSTRAINT `quality_replay_items_expected_json_ck`\n CHECK (JSON_TYPE(`expected_evidence_ids`) = 'ARRAY'),\n CONSTRAINT `quality_replay_items_result_json_ck`\n CHECK (`result` IS NULL OR JSON_TYPE(`result`) = 'OBJECT'),\n FOREIGN KEY (`run_id`) REFERENCES `quality_replay_runs` (`id`) ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS `quality_replay_items_run_ordinal_uq`\n ON `quality_replay_items` (`run_id`, `ordinal`);\nCREATE UNIQUE INDEX IF NOT EXISTS `quality_replay_items_run_golden_uq`\n ON `quality_replay_items` (`run_id`, `golden_question_id`);\n\nCREATE TABLE IF NOT EXISTS `quality_replay_outbox` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `run_id` CHAR(36) NOT NULL,\n `delivery_revision` INT NOT NULL,\n `event_type` VARCHAR(64) NOT NULL,\n `delivery_state` VARCHAR(16) NOT NULL,\n `attempt` INT NOT NULL,\n `lease_owner` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `delivered_at` DATETIME(3),\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `quality_replay_outbox_state_ck` CHECK (\n `delivery_revision` >= 1\n AND `delivery_state` IN ('pending', 'claimed', 'delivered') AND `attempt` >= 0\n AND ((`delivery_state` = 'claimed' AND `lease_owner` IS NOT NULL\n AND `lease_token` IS NOT NULL AND `lease_expires_at` IS NOT NULL\n AND `delivered_at` IS NULL)\n OR (`delivery_state` <> 'claimed' AND `lease_owner` IS NULL\n AND `lease_token` IS NULL AND `lease_expires_at` IS NULL))\n AND ((`delivery_state` = 'delivered' AND `delivered_at` IS NOT NULL)\n OR (`delivery_state` <> 'delivered' AND `delivered_at` IS NULL))\n ),\n FOREIGN KEY (`run_id`) REFERENCES `quality_replay_runs` (`id`) ON DELETE CASCADE\n);\nALTER TABLE `quality_replay_outbox`\n ADD COLUMN IF NOT EXISTS `delivery_revision` INT;\nUPDATE `quality_replay_outbox` AS outbox\nINNER JOIN (\n SELECT `id`, ROW_NUMBER() OVER (\n PARTITION BY `run_id` ORDER BY `created_at`, `id`\n ) AS `delivery_revision`\n FROM `quality_replay_outbox`\n) AS ranked_delivery ON ranked_delivery.`id` = outbox.`id`\nSET outbox.`delivery_revision` = ranked_delivery.`delivery_revision`\nWHERE outbox.`delivery_revision` IS NULL;\nALTER TABLE `quality_replay_outbox`\n MODIFY COLUMN `delivery_revision` INT NOT NULL;\nSET @quality_outbox_revision_ck_exists = (\n SELECT COUNT(*) FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'quality_replay_outbox'\n AND constraint_name = 'quality_replay_outbox_delivery_revision_ck'\n);\nSET @quality_outbox_revision_ck_sql = IF(\n @quality_outbox_revision_ck_exists = 0,\n 'ALTER TABLE `quality_replay_outbox` ADD CONSTRAINT `quality_replay_outbox_delivery_revision_ck` CHECK (`delivery_revision` >= 1)',\n 'SELECT 1'\n);\nPREPARE quality_outbox_revision_ck_stmt FROM @quality_outbox_revision_ck_sql;\nEXECUTE quality_outbox_revision_ck_stmt;\nDEALLOCATE PREPARE quality_outbox_revision_ck_stmt;\nCREATE INDEX IF NOT EXISTS `quality_replay_outbox_claim_idx`\n ON `quality_replay_outbox` (`delivery_state`, `lease_expires_at`, `created_at`, `id`);\nCREATE UNIQUE INDEX IF NOT EXISTS `quality_replay_outbox_run_delivery_uq`\n ON `quality_replay_outbox` (`run_id`, `delivery_revision`);\nCREATE INDEX IF NOT EXISTS `quality_replay_outbox_run_idx`\n ON `quality_replay_outbox` (`run_id`, `delivery_state`, `id`);\n\nCREATE TABLE IF NOT EXISTS `quality_bad_cases` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `trace_id` CHAR(36) NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `reason` TEXT NOT NULL,\n `tags` JSON NOT NULL,\n `replay_run_id` CHAR(36),\n `actor_subject_id` VARCHAR(255) NOT NULL,\n `revision` INT NOT NULL,\n `required_permission_scope` JSON NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `quality_bad_cases_state_ck` CHECK (\n `status` IN ('open', 'replaying', 'fixed', 'dismissed') AND `revision` >= 1\n AND (`status` <> 'replaying' OR `replay_run_id` IS NOT NULL)\n ),\n CONSTRAINT `quality_bad_cases_tags_json_ck` CHECK (JSON_TYPE(`tags`) = 'ARRAY'),\n CONSTRAINT `quality_bad_cases_scope_json_ck`\n CHECK (JSON_TYPE(`required_permission_scope`) = 'ARRAY'),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n CONSTRAINT `quality_bad_cases_trace_scope_fk`\n FOREIGN KEY (`knowledge_space_id`, `trace_id`)\n REFERENCES `answer_traces` (`knowledge_space_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `replay_run_id`)\n REFERENCES `quality_replay_runs` (`tenant_id`, `knowledge_space_id`, `id`)\n);\nCREATE INDEX IF NOT EXISTS `quality_bad_cases_scope_status_idx`\n ON `quality_bad_cases` (`tenant_id`, `knowledge_space_id`, `status`, `created_at` DESC, `id` DESC);\n\nCREATE TABLE IF NOT EXISTS `quality_missing_evidence_reviews` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `trace_id` CHAR(36) NOT NULL,\n `item_key` VARCHAR(71) NOT NULL,\n `status` VARCHAR(16) NOT NULL,\n `reason` TEXT,\n `actor_subject_id` VARCHAR(255) NOT NULL,\n `revision` INT NOT NULL,\n `required_permission_scope` JSON NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `quality_missing_evidence_reviews_state_ck` CHECK (\n `status` IN ('active', 'dismissed') AND `revision` >= 1\n AND `item_key` REGEXP '^sha256:[a-f0-9]{64}$'\n ),\n CONSTRAINT `quality_missing_evidence_reviews_scope_json_ck`\n CHECK (JSON_TYPE(`required_permission_scope`) = 'ARRAY'),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n CONSTRAINT `quality_missing_evidence_reviews_trace_scope_fk`\n FOREIGN KEY (`knowledge_space_id`, `trace_id`)\n REFERENCES `answer_traces` (`knowledge_space_id`, `id`) ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS `quality_missing_reviews_item_uq`\n ON `quality_missing_evidence_reviews` (\n `tenant_id`, `knowledge_space_id`, `trace_id`, `item_key`\n );\n\nCREATE TABLE IF NOT EXISTS `quality_resource_history` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `aggregate_type` VARCHAR(32) NOT NULL,\n `aggregate_id` CHAR(36) NOT NULL,\n `action` VARCHAR(32) NOT NULL,\n `actor_subject_id` VARCHAR(255) NOT NULL,\n `from_status` VARCHAR(16),\n `to_status` VARCHAR(16) NOT NULL,\n `reason` TEXT,\n `revision` INT NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n CONSTRAINT `quality_resource_history_type_ck` CHECK (\n `aggregate_type` IN ('bad-case', 'missing-evidence') AND `revision` >= 1\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\nCREATE UNIQUE INDEX IF NOT EXISTS `quality_resource_history_revision_uq`\n ON `quality_resource_history` (\n `tenant_id`, `knowledge_space_id`, `aggregate_type`, `aggregate_id`, `revision`\n );\n\n-- Legacy golden questions keep NULL provenance and are intentionally unreadable by public APIs.\nALTER TABLE `golden_questions`\n ADD COLUMN IF NOT EXISTS `tenant_id` VARCHAR(255),\n ADD COLUMN IF NOT EXISTS `required_permission_scope` JSON;\nALTER TABLE `golden_questions`\n ADD COLUMN IF NOT EXISTS `scope_binding_complete` TINYINT GENERATED ALWAYS AS (\n CASE WHEN\n (`tenant_id` IS NULL AND `required_permission_scope` IS NULL)\n OR (`tenant_id` IS NOT NULL AND `required_permission_scope` IS NOT NULL\n AND JSON_TYPE(`required_permission_scope`) = 'ARRAY')\n THEN 1 ELSE 0\n END\n ) VIRTUAL;\n\nSET @golden_scope_ck_exists = (\n SELECT COUNT(*) FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'golden_questions'\n AND constraint_name = 'golden_questions_scope_json_ck'\n);\nSET @golden_scope_ck_drop_sql = IF(\n @golden_scope_ck_exists > 0,\n 'ALTER TABLE `golden_questions` DROP CONSTRAINT `golden_questions_scope_json_ck`',\n 'SELECT 1'\n);\nPREPARE golden_scope_ck_drop_stmt FROM @golden_scope_ck_drop_sql;\nEXECUTE golden_scope_ck_drop_stmt;\nDEALLOCATE PREPARE golden_scope_ck_drop_stmt;\nALTER TABLE `golden_questions`\n ADD CONSTRAINT `golden_questions_scope_json_ck` CHECK (`scope_binding_complete` = 1);\n\nSET @golden_scope_fk_exists = (\n SELECT COUNT(*) FROM information_schema.table_constraints\n WHERE table_schema = DATABASE()\n AND table_name = 'golden_questions'\n AND constraint_name = 'golden_questions_scope_fk'\n);\nSET @golden_scope_fk_sql = IF(\n @golden_scope_fk_exists = 0,\n 'ALTER TABLE `golden_questions` ADD CONSTRAINT `golden_questions_scope_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE',\n 'SELECT 1'\n);\nPREPARE golden_scope_fk_stmt FROM @golden_scope_fk_sql;\nEXECUTE golden_scope_fk_stmt;\nDEALLOCATE PREPARE golden_scope_fk_stmt;\n\nSET @quality_bad_trace_fk_exists = (\n SELECT COUNT(*) FROM information_schema.table_constraints\n WHERE table_schema = DATABASE()\n AND table_name = 'quality_bad_cases'\n AND constraint_name = 'quality_bad_cases_trace_scope_fk'\n);\nSET @quality_bad_trace_fk_sql = IF(\n @quality_bad_trace_fk_exists = 0,\n 'ALTER TABLE `quality_bad_cases` ADD CONSTRAINT `quality_bad_cases_trace_scope_fk` FOREIGN KEY (`knowledge_space_id`, `trace_id`) REFERENCES `answer_traces` (`knowledge_space_id`, `id`) ON DELETE CASCADE',\n 'SELECT 1'\n);\nPREPARE quality_bad_trace_fk_stmt FROM @quality_bad_trace_fk_sql;\nEXECUTE quality_bad_trace_fk_stmt;\nDEALLOCATE PREPARE quality_bad_trace_fk_stmt;\n\nSET @quality_missing_trace_fk_exists = (\n SELECT COUNT(*) FROM information_schema.table_constraints\n WHERE table_schema = DATABASE()\n AND table_name = 'quality_missing_evidence_reviews'\n AND constraint_name = 'quality_missing_evidence_reviews_trace_scope_fk'\n);\nSET @quality_missing_trace_fk_sql = IF(\n @quality_missing_trace_fk_exists = 0,\n 'ALTER TABLE `quality_missing_evidence_reviews` ADD CONSTRAINT `quality_missing_evidence_reviews_trace_scope_fk` FOREIGN KEY (`knowledge_space_id`, `trace_id`) REFERENCES `answer_traces` (`knowledge_space_id`, `id`) ON DELETE CASCADE',\n 'SELECT 1'\n);\nPREPARE quality_missing_trace_fk_stmt FROM @quality_missing_trace_fk_sql;\nEXECUTE quality_missing_trace_fk_stmt;\nDEALLOCATE PREPARE quality_missing_trace_fk_stmt;\n\nDROP INDEX IF EXISTS `golden_questions_space_id_idx` ON `golden_questions`;\nCREATE INDEX IF NOT EXISTS `golden_questions_space_id_idx`\n ON `golden_questions` (`tenant_id`, `knowledge_space_id`, `id`);\nDROP INDEX IF EXISTS `golden_questions_space_created_idx` ON `golden_questions`;\nCREATE INDEX IF NOT EXISTS `golden_questions_space_created_idx`\n ON `golden_questions` (`tenant_id`, `knowledge_space_id`, `created_at`, `id`);\n\nCREATE INDEX IF NOT EXISTS `failed_queries_space_created_idx`\n ON `failed_queries` (`knowledge_space_id`, `created_at`, `id`);\n\n-- Legacy rows keep every provenance column NULL and are intentionally unreadable. New rows must\n-- populate the complete binding. ADD COLUMN IF NOT EXISTS keeps marker-loss replay safe.\nALTER TABLE `failed_queries`\n ADD COLUMN IF NOT EXISTS `tenant_id` VARCHAR(255),\n ADD COLUMN IF NOT EXISTS `requested_by_subject_id` VARCHAR(255),\n ADD COLUMN IF NOT EXISTS `access_channel` VARCHAR(16),\n ADD COLUMN IF NOT EXISTS `permission_snapshot_id` CHAR(36),\n ADD COLUMN IF NOT EXISTS `permission_snapshot_revision` INT,\n ADD COLUMN IF NOT EXISTS `required_permission_scope` JSON,\n ADD COLUMN IF NOT EXISTS `revision` INT;\nALTER TABLE `failed_queries`\n ADD COLUMN IF NOT EXISTS `permission_binding_complete` TINYINT GENERATED ALWAYS AS (\n CASE WHEN\n (`tenant_id` IS NULL AND `requested_by_subject_id` IS NULL AND `access_channel` IS NULL\n AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL\n AND `required_permission_scope` IS NULL AND `revision` IS NULL)\n OR (`tenant_id` IS NOT NULL AND `requested_by_subject_id` IS NOT NULL\n AND `access_channel` IS NOT NULL\n AND `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent')\n AND `permission_snapshot_id` IS NOT NULL\n AND `permission_snapshot_revision` IS NOT NULL\n AND `permission_snapshot_revision` >= 1\n AND `required_permission_scope` IS NOT NULL\n AND JSON_TYPE(`required_permission_scope`) = 'ARRAY'\n AND `revision` IS NOT NULL AND `revision` >= 1)\n THEN 1 ELSE 0\n END\n ) VIRTUAL;\n\nSET @fq_binding_ck_exists = (\n SELECT COUNT(*) FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'failed_queries'\n AND constraint_name = 'failed_queries_permission_binding_ck'\n);\nSET @fq_binding_ck_drop_sql = IF(\n @fq_binding_ck_exists > 0,\n 'ALTER TABLE `failed_queries` DROP CONSTRAINT `failed_queries_permission_binding_ck`',\n 'SELECT 1'\n);\nPREPARE fq_binding_ck_drop_stmt FROM @fq_binding_ck_drop_sql;\nEXECUTE fq_binding_ck_drop_stmt;\nDEALLOCATE PREPARE fq_binding_ck_drop_stmt;\nALTER TABLE `failed_queries`\n ADD CONSTRAINT `failed_queries_permission_binding_ck`\n CHECK (`permission_binding_complete` = 1);\n\nSET @fq_scope_fk_exists = (\n SELECT COUNT(*) FROM information_schema.table_constraints\n WHERE table_schema = DATABASE()\n AND table_name = 'failed_queries'\n AND constraint_name = 'failed_queries_scope_fk'\n);\nSET @fq_scope_fk_sql = IF(\n @fq_scope_fk_exists = 0,\n 'ALTER TABLE `failed_queries` ADD CONSTRAINT `failed_queries_scope_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`) REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE',\n 'SELECT 1'\n);\nPREPARE fq_scope_fk_stmt FROM @fq_scope_fk_sql;\nEXECUTE fq_scope_fk_stmt;\nDEALLOCATE PREPARE fq_scope_fk_stmt;\n\nSET @fq_permission_fk_exists = (\n SELECT COUNT(*) FROM information_schema.table_constraints\n WHERE table_schema = DATABASE()\n AND table_name = 'failed_queries'\n AND constraint_name = 'failed_queries_permission_snapshot_fk'\n);\nSET @fq_permission_fk_sql = IF(\n @fq_permission_fk_exists = 0,\n 'ALTER TABLE `failed_queries` ADD CONSTRAINT `failed_queries_permission_snapshot_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `permission_snapshot_id`, `requested_by_subject_id`, `access_channel`) REFERENCES `knowledge_space_permission_snapshots` (`tenant_id`, `knowledge_space_id`, `id`, `subject_id`, `access_channel`)',\n 'SELECT 1'\n);\nPREPARE fq_permission_fk_stmt FROM @fq_permission_fk_sql;\nEXECUTE fq_permission_fk_stmt;\nDEALLOCATE PREPARE fq_permission_fk_stmt;\n\nCREATE INDEX IF NOT EXISTS `failed_queries_subject_created_idx`\n ON `failed_queries` (\n `tenant_id`, `knowledge_space_id`, `requested_by_subject_id`, `created_at`, `id`\n );\n", path: "packages/database/migrations/0024_quality_control.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0025_capability_grant_provenance\n-- Dialect: postgres\n-- New tables are replay-safe and never persist a Capability bearer token or raw jti.\n\nCREATE TABLE IF NOT EXISTS \"capability_grants\" (\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"grant_id\" UUID NOT NULL,\n \"claims_digest\" VARCHAR(71) NOT NULL,\n \"subject_id\" VARCHAR(255) NOT NULL,\n \"actor_id\" VARCHAR(255) NOT NULL,\n \"caller_kind\" VARCHAR(32) NOT NULL,\n \"action\" VARCHAR(128) NOT NULL,\n \"resource_type\" VARCHAR(64) NOT NULL,\n \"resource_id\" VARCHAR(255) NOT NULL,\n \"resource_parent_id\" VARCHAR(255),\n \"jti_hash\" VARCHAR(71) NOT NULL,\n \"trace_id\" VARCHAR(255) NOT NULL,\n \"authz_revision\" JSONB NOT NULL,\n \"content_scope_ids\" JSONB NOT NULL,\n \"content_policy_revision\" INTEGER NOT NULL,\n \"issued_at\" TIMESTAMPTZ NOT NULL,\n \"expires_at\" TIMESTAMPTZ NOT NULL,\n \"state\" VARCHAR(16) NOT NULL,\n \"highest_revoke_sequence\" BIGINT NOT NULL,\n \"revoke_reason_code\" VARCHAR(64),\n \"revoked_at\" TIMESTAMPTZ,\n \"revision\" INTEGER NOT NULL,\n \"admitted_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"capability_grants_state_ck\" CHECK (\n \"revision\" >= 1 AND \"highest_revoke_sequence\" >= 0\n AND \"state\" IN ('active', 'revoked')\n AND ((\"state\" = 'active' AND \"revoked_at\" IS NULL AND \"revoke_reason_code\" IS NULL)\n OR (\"state\" = 'revoked' AND \"revoked_at\" IS NOT NULL\n AND \"revoke_reason_code\" IS NOT NULL))\n ),\n CONSTRAINT \"capability_grants_digest_ck\" CHECK (\n \"claims_digest\" ~ '^sha256:[a-f0-9]{64}$'\n AND \"jti_hash\" ~ '^sha256:[a-f0-9]{64}$'\n ),\n CONSTRAINT \"capability_grants_json_ck\" CHECK (\n jsonb_typeof(\"authz_revision\") = 'object'\n AND jsonb_typeof(\"content_scope_ids\") = 'array'\n ),\n PRIMARY KEY (\"tenant_id\", \"knowledge_space_id\", \"grant_id\"),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS \"capability_space_fences\" (\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"tombstoned\" BOOLEAN NOT NULL,\n \"highest_revoke_sequence\" BIGINT NOT NULL,\n \"reason_code\" VARCHAR(64) NOT NULL,\n \"revision\" INTEGER NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"capability_space_fences_revision_ck\" CHECK (\n \"revision\" >= 1 AND \"highest_revoke_sequence\" >= 1\n ),\n PRIMARY KEY (\"tenant_id\", \"knowledge_space_id\"),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS \"capability_revoke_receipts\" (\n \"event_id\" VARCHAR(255) PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"target_kind\" VARCHAR(16) NOT NULL,\n \"grant_id\" UUID,\n \"revoke_sequence\" BIGINT NOT NULL,\n \"reason_code\" VARCHAR(64) NOT NULL,\n \"tombstoned\" BOOLEAN,\n \"applied\" BOOLEAN NOT NULL,\n \"received_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"capability_revoke_receipts_target_ck\" CHECK (\n \"revoke_sequence\" >= 1\n AND ((\"target_kind\" = 'grant' AND \"grant_id\" IS NOT NULL AND \"tombstoned\" IS NULL)\n OR (\"target_kind\" = 'space' AND \"grant_id\" IS NULL AND \"tombstoned\" IS NOT NULL))\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"capability_grants_jti_hash_uq\"\n ON \"capability_grants\" (\"jti_hash\");\nCREATE INDEX IF NOT EXISTS \"capability_grants_subject_audit_idx\"\n ON \"capability_grants\" (\n \"tenant_id\", \"knowledge_space_id\", \"subject_id\", \"admitted_at\", \"grant_id\"\n );\nCREATE INDEX IF NOT EXISTS \"capability_revoke_receipts_scope_sequence_idx\"\n ON \"capability_revoke_receipts\" (\n \"tenant_id\", \"knowledge_space_id\", \"revoke_sequence\", \"received_at\"\n );\n", path: "packages/database/migrations/0025_capability_grant_provenance.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0025_capability_grant_provenance\n-- Dialect: tidb\n-- New tables are replay-safe and never persist a Capability bearer token or raw jti.\n\nCREATE TABLE IF NOT EXISTS `capability_grants` (\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `grant_id` CHAR(36) NOT NULL,\n `claims_digest` VARCHAR(71) NOT NULL,\n `subject_id` VARCHAR(255) NOT NULL,\n `actor_id` VARCHAR(255) NOT NULL,\n `caller_kind` VARCHAR(32) NOT NULL,\n `action` VARCHAR(128) NOT NULL,\n `resource_type` VARCHAR(64) NOT NULL,\n `resource_id` VARCHAR(255) NOT NULL,\n `resource_parent_id` VARCHAR(255),\n `jti_hash` VARCHAR(71) NOT NULL,\n `trace_id` VARCHAR(255) NOT NULL,\n `authz_revision` JSON NOT NULL,\n `content_scope_ids` JSON NOT NULL,\n `content_policy_revision` INT NOT NULL,\n `issued_at` DATETIME(3) NOT NULL,\n `expires_at` DATETIME(3) NOT NULL,\n `state` VARCHAR(16) NOT NULL,\n `highest_revoke_sequence` BIGINT NOT NULL,\n `revoke_reason_code` VARCHAR(64),\n `revoked_at` DATETIME(3),\n `revision` INT NOT NULL,\n `admitted_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `capability_grants_state_ck` CHECK (\n `revision` >= 1 AND `highest_revoke_sequence` >= 0\n AND `state` IN ('active', 'revoked')\n AND ((`state` = 'active' AND `revoked_at` IS NULL AND `revoke_reason_code` IS NULL)\n OR (`state` = 'revoked' AND `revoked_at` IS NOT NULL\n AND `revoke_reason_code` IS NOT NULL))\n ),\n CONSTRAINT `capability_grants_digest_ck` CHECK (\n `claims_digest` REGEXP '^sha256:[a-f0-9]{64}$'\n AND `jti_hash` REGEXP '^sha256:[a-f0-9]{64}$'\n ),\n CONSTRAINT `capability_grants_json_ck` CHECK (\n JSON_TYPE(`authz_revision`) = 'OBJECT'\n AND JSON_TYPE(`content_scope_ids`) = 'ARRAY'\n ),\n PRIMARY KEY (`tenant_id`, `knowledge_space_id`, `grant_id`),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS `capability_space_fences` (\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `tombstoned` BOOLEAN NOT NULL,\n `highest_revoke_sequence` BIGINT NOT NULL,\n `reason_code` VARCHAR(64) NOT NULL,\n `revision` INT NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `capability_space_fences_revision_ck` CHECK (\n `revision` >= 1 AND `highest_revoke_sequence` >= 1\n ),\n PRIMARY KEY (`tenant_id`, `knowledge_space_id`),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE TABLE IF NOT EXISTS `capability_revoke_receipts` (\n `event_id` VARCHAR(255) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `target_kind` VARCHAR(16) NOT NULL,\n `grant_id` CHAR(36),\n `revoke_sequence` BIGINT NOT NULL,\n `reason_code` VARCHAR(64) NOT NULL,\n `tombstoned` BOOLEAN,\n `applied` BOOLEAN NOT NULL,\n `received_at` DATETIME(3) NOT NULL,\n CONSTRAINT `capability_revoke_receipts_target_ck` CHECK (\n `revoke_sequence` >= 1\n AND ((`target_kind` = 'grant' AND `grant_id` IS NOT NULL AND `tombstoned` IS NULL)\n OR (`target_kind` = 'space' AND `grant_id` IS NULL AND `tombstoned` IS NOT NULL))\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `capability_grants_jti_hash_uq`\n ON `capability_grants` (`jti_hash`);\nCREATE INDEX IF NOT EXISTS `capability_grants_subject_audit_idx`\n ON `capability_grants` (\n `tenant_id`, `knowledge_space_id`, `subject_id`, `admitted_at`, `grant_id`\n );\nCREATE INDEX IF NOT EXISTS `capability_revoke_receipts_scope_sequence_idx`\n ON `capability_revoke_receipts` (\n `tenant_id`, `knowledge_space_id`, `revoke_sequence`, `received_at`\n );\n", path: "packages/database/migrations/0025_capability_grant_provenance.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0026_capability_job_provenance\n-- Dialect: postgres\n-- Capability jobs persist only a grant locator. Nullable snapshot columns remain for rollback and\n-- already-durable legacy rows; no bearer token, raw jti, or Dify membership snapshot is stored.\n\nALTER TABLE \"research_task_jobs\"\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID,\n ALTER COLUMN \"subject_id\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_id\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_revision\" DROP NOT NULL,\n ALTER COLUMN \"access_channel\" DROP NOT NULL;\nALTER TABLE \"research_task_jobs\"\n DROP CONSTRAINT IF EXISTS \"research_task_jobs_authorization_binding_ck\";\nALTER TABLE \"research_task_jobs\"\n ADD CONSTRAINT \"research_task_jobs_authorization_binding_ck\" CHECK (\n (\"capability_grant_id\" IS NOT NULL AND \"subject_id\" IS NULL\n AND \"permission_snapshot_id\" IS NULL AND \"permission_snapshot_revision\" IS NULL\n AND \"access_channel\" IS NULL)\n OR (\"capability_grant_id\" IS NULL AND \"subject_id\" IS NOT NULL\n AND \"permission_snapshot_id\" IS NOT NULL AND \"permission_snapshot_revision\" >= 1\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT \"research_task_jobs_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\n\nALTER TABLE \"answer_traces\"\n ADD COLUMN IF NOT EXISTS \"tenant_id\" VARCHAR(255),\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID;\nALTER TABLE \"answer_traces\"\n DROP CONSTRAINT IF EXISTS \"answer_traces_permission_snapshot_binding_ck\",\n DROP CONSTRAINT IF EXISTS \"answer_traces_authorization_binding_ck\";\nALTER TABLE \"answer_traces\"\n ADD CONSTRAINT \"answer_traces_authorization_binding_ck\" CHECK (\n (\"capability_grant_id\" IS NOT NULL AND \"tenant_id\" IS NOT NULL AND \"subject_id\" IS NULL\n AND \"permission_snapshot_id\" IS NULL AND \"permission_snapshot_revision\" IS NULL\n AND \"access_channel\" IS NULL)\n OR (\"capability_grant_id\" IS NULL AND (\n (\"permission_snapshot_id\" IS NULL AND \"permission_snapshot_revision\" IS NULL\n AND \"access_channel\" IS NULL)\n OR (\"subject_id\" IS NOT NULL AND \"permission_snapshot_id\" IS NOT NULL\n AND \"permission_snapshot_revision\" >= 1\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent'))))\n ),\n ADD CONSTRAINT \"answer_traces_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\n\nALTER TABLE \"document_compilation_attempts\"\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID;\nALTER TABLE \"document_compilation_attempts\"\n DROP CONSTRAINT IF EXISTS \"document_compilation_attempts_permission_binding_ck\",\n DROP CONSTRAINT IF EXISTS \"document_compilation_attempts_authorization_binding_ck\";\nALTER TABLE \"document_compilation_attempts\"\n ADD CONSTRAINT \"document_compilation_attempts_authorization_binding_ck\" CHECK (\n (\"capability_grant_id\" IS NOT NULL AND \"requested_by_subject_id\" IS NULL\n AND \"permission_snapshot_id\" IS NULL AND \"permission_snapshot_revision\" IS NULL\n AND \"access_channel\" IS NULL)\n OR (\"capability_grant_id\" IS NULL AND (\n (\"requested_by_subject_id\" IS NULL AND \"permission_snapshot_id\" IS NULL\n AND \"permission_snapshot_revision\" IS NULL AND \"access_channel\" IS NULL)\n OR (\"requested_by_subject_id\" IS NOT NULL AND \"permission_snapshot_id\" IS NOT NULL\n AND \"permission_snapshot_revision\" >= 1\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent'))))\n ),\n ADD CONSTRAINT \"document_compilation_attempts_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\n\nALTER TABLE \"document_revisions\"\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID;\nALTER TABLE \"document_revisions\"\n DROP CONSTRAINT IF EXISTS \"document_revisions_capability_grant_fk\";\nALTER TABLE \"document_revisions\"\n ADD CONSTRAINT \"document_revisions_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\n\nALTER TABLE \"knowledge_space_profile_migration_runs\"\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID,\n ALTER COLUMN \"permission_snapshot_id\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_revision\" DROP NOT NULL,\n ALTER COLUMN \"requested_by_subject_id\" DROP NOT NULL,\n ALTER COLUMN \"access_channel\" DROP NOT NULL;\nALTER TABLE \"knowledge_space_profile_migration_runs\"\n DROP CONSTRAINT IF EXISTS \"knowledge_space_profile_migration_runs_authorization_binding_ck\";\nALTER TABLE \"knowledge_space_profile_migration_runs\"\n ADD CONSTRAINT \"knowledge_space_profile_migration_runs_authorization_binding_ck\" CHECK (\n (\"capability_grant_id\" IS NOT NULL AND \"requested_by_subject_id\" IS NULL\n AND \"permission_snapshot_id\" IS NULL AND \"permission_snapshot_revision\" IS NULL\n AND \"access_channel\" IS NULL)\n OR (\"capability_grant_id\" IS NULL AND \"requested_by_subject_id\" IS NOT NULL\n AND \"permission_snapshot_id\" IS NOT NULL AND \"permission_snapshot_revision\" >= 1\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT \"knowledge_space_profile_migration_runs_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\nALTER TABLE \"knowledge_space_profile_migration_runs\"\n DROP CONSTRAINT IF EXISTS \"knowledge_space_profile_migration_runs_positive_ck\";\nALTER TABLE \"knowledge_space_profile_migration_runs\"\n ADD CONSTRAINT \"knowledge_space_profile_migration_runs_positive_ck\" CHECK (\n \"candidate_profile_revision\" >= 1 AND \"base_retrieval_profile_revision\" >= 1\n AND \"base_publication_head_revision\" >= 1\n AND (\"capability_grant_id\" IS NOT NULL OR \"permission_snapshot_revision\" >= 1)\n AND \"execution_attempts\" >= 0 AND \"max_execution_attempts\" >= 1\n AND \"execution_attempts\" <= \"max_execution_attempts\" AND \"row_version\" >= 1\n AND (\"active_slot\" IS NULL OR \"active_slot\" = 1)\n );\n\nALTER TABLE \"source_workflow_runs\"\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID,\n ALTER COLUMN \"permission_snapshot_id\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_revision\" DROP NOT NULL,\n ALTER COLUMN \"requested_by_subject_id\" DROP NOT NULL,\n ALTER COLUMN \"required_permission_scope\" DROP NOT NULL,\n ALTER COLUMN \"access_channel\" DROP NOT NULL;\nALTER TABLE \"source_workflow_runs\"\n DROP CONSTRAINT IF EXISTS \"source_workflow_runs_authorization_binding_ck\";\nALTER TABLE \"source_workflow_runs\"\n ADD CONSTRAINT \"source_workflow_runs_authorization_binding_ck\" CHECK (\n (\"capability_grant_id\" IS NOT NULL AND \"requested_by_subject_id\" IS NULL\n AND \"permission_snapshot_id\" IS NULL AND \"permission_snapshot_revision\" IS NULL\n AND \"required_permission_scope\" IS NULL AND \"access_channel\" IS NULL)\n OR (\"capability_grant_id\" IS NULL AND \"requested_by_subject_id\" IS NOT NULL\n AND \"permission_snapshot_id\" IS NOT NULL AND \"permission_snapshot_revision\" >= 1\n AND \"required_permission_scope\" IS NOT NULL\n AND jsonb_typeof(\"required_permission_scope\") = 'array'\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT \"source_workflow_runs_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\nALTER TABLE \"source_workflow_runs\"\n DROP CONSTRAINT IF EXISTS \"source_workflow_runs_nonnegative_ck\";\nALTER TABLE \"source_workflow_runs\"\n ADD CONSTRAINT \"source_workflow_runs_nonnegative_ck\" CHECK (\n \"progress_completed\" >= 0 AND \"progress_skipped\" >= 0 AND \"progress_failed\" >= 0\n AND \"execution_attempts\" >= 0 AND \"max_execution_attempts\" >= 1\n AND \"execution_attempts\" <= \"max_execution_attempts\"\n AND (\"capability_grant_id\" IS NOT NULL OR \"permission_snapshot_revision\" >= 1)\n AND \"row_version\" >= 1 AND (\"active_slot\" IS NULL OR \"active_slot\" = 1)\n );\n\nALTER TABLE \"quality_replay_runs\"\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID,\n ALTER COLUMN \"requested_by_subject_id\" DROP NOT NULL,\n ALTER COLUMN \"access_channel\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_id\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_revision\" DROP NOT NULL,\n ALTER COLUMN \"required_permission_scope\" DROP NOT NULL;\nALTER TABLE \"quality_replay_runs\"\n DROP CONSTRAINT IF EXISTS \"quality_replay_runs_authorization_binding_ck\";\nALTER TABLE \"quality_replay_runs\"\n ADD CONSTRAINT \"quality_replay_runs_authorization_binding_ck\" CHECK (\n (\"capability_grant_id\" IS NOT NULL AND \"requested_by_subject_id\" IS NULL\n AND \"permission_snapshot_id\" IS NULL AND \"permission_snapshot_revision\" IS NULL\n AND \"required_permission_scope\" IS NULL AND \"access_channel\" IS NULL)\n OR (\"capability_grant_id\" IS NULL AND \"requested_by_subject_id\" IS NOT NULL\n AND \"permission_snapshot_id\" IS NOT NULL AND \"permission_snapshot_revision\" >= 1\n AND \"required_permission_scope\" IS NOT NULL\n AND jsonb_typeof(\"required_permission_scope\") = 'array'\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT \"quality_replay_runs_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\nALTER TABLE \"quality_replay_runs\"\n DROP CONSTRAINT IF EXISTS \"quality_replay_runs_revision_ck\";\nALTER TABLE \"quality_replay_runs\"\n ADD CONSTRAINT \"quality_replay_runs_revision_ck\" CHECK (\n \"revision\" >= 1 AND \"attempt\" >= 0\n AND (\"capability_grant_id\" IS NOT NULL OR \"permission_snapshot_revision\" >= 1)\n AND \"request_fingerprint\" ~ '^sha256:[a-f0-9]{64}$'\n );\n\nALTER TABLE \"deletion_jobs\"\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID,\n ALTER COLUMN \"requested_by_subject_id\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_id\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_revision\" DROP NOT NULL,\n ALTER COLUMN \"access_channel\" DROP NOT NULL;\nALTER TABLE \"deletion_jobs\"\n DROP CONSTRAINT IF EXISTS \"deletion_jobs_authorization_binding_ck\";\nALTER TABLE \"deletion_jobs\"\n ADD CONSTRAINT \"deletion_jobs_authorization_binding_ck\" CHECK (\n (\"capability_grant_id\" IS NOT NULL AND \"requested_by_subject_id\" IS NULL\n AND \"permission_snapshot_id\" IS NULL AND \"permission_snapshot_revision\" IS NULL\n AND \"access_channel\" IS NULL AND \"api_key_id\" IS NULL\n AND \"api_key_revision\" IS NULL AND \"api_key_expires_at\" IS NULL)\n OR (\"capability_grant_id\" IS NULL AND \"requested_by_subject_id\" IS NOT NULL\n AND \"permission_snapshot_id\" IS NOT NULL AND \"permission_snapshot_revision\" >= 1\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT \"deletion_jobs_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\nALTER TABLE \"deletion_jobs\"\n DROP CONSTRAINT IF EXISTS \"deletion_jobs_positive_ck\";\nALTER TABLE \"deletion_jobs\"\n ADD CONSTRAINT \"deletion_jobs_positive_ck\" CHECK (\n \"target_revision\" >= 1\n AND (\"capability_grant_id\" IS NOT NULL OR \"permission_snapshot_revision\" >= 1)\n AND \"row_version\" >= 1 AND \"execution_attempts\" >= 0\n AND \"max_execution_attempts\" >= 1 AND \"execution_attempts\" <= \"max_execution_attempts\"\n AND (\"active_slot\" IS NULL OR \"active_slot\" = 1)\n );\n\nALTER TABLE \"deletion_retry_audits\"\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID,\n ALTER COLUMN \"actor_subject_id\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_id\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_revision\" DROP NOT NULL,\n ALTER COLUMN \"access_channel\" DROP NOT NULL;\nALTER TABLE \"deletion_retry_audits\"\n DROP CONSTRAINT IF EXISTS \"deletion_retry_audits_positive_ck\",\n DROP CONSTRAINT IF EXISTS \"deletion_retry_audits_owner_rescue_ck\",\n DROP CONSTRAINT IF EXISTS \"deletion_retry_audits_authorization_binding_ck\";\nALTER TABLE \"deletion_retry_audits\"\n ADD CONSTRAINT \"deletion_retry_audits_authorization_binding_ck\" CHECK (\n (\"capability_grant_id\" IS NOT NULL AND \"retry_authority\" = 'original_requester'\n AND \"actor_subject_id\" IS NULL AND \"permission_snapshot_id\" IS NULL\n AND \"permission_snapshot_revision\" IS NULL AND \"access_channel\" IS NULL\n AND \"api_key_id\" IS NULL AND \"api_key_revision\" IS NULL\n AND \"api_key_expires_at\" IS NULL)\n OR (\"capability_grant_id\" IS NULL AND \"actor_subject_id\" IS NOT NULL\n AND \"permission_snapshot_id\" IS NOT NULL AND \"permission_snapshot_revision\" >= 1\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT \"deletion_retry_audits_positive_ck\" CHECK (\n \"capability_grant_id\" IS NOT NULL OR \"permission_snapshot_revision\" >= 1\n ),\n ADD CONSTRAINT \"deletion_retry_audits_owner_rescue_ck\" CHECK (\n \"retry_authority\" <> 'interactive_owner_rescue'\n OR (\"access_channel\" = 'interactive' AND \"api_key_id\" IS NULL\n AND \"api_key_revision\" IS NULL AND \"api_key_expires_at\" IS NULL)\n ),\n ADD CONSTRAINT \"deletion_retry_audits_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\n\nCREATE INDEX IF NOT EXISTS \"research_task_jobs_capability_grant_idx\"\n ON \"research_task_jobs\" (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\");\nCREATE INDEX IF NOT EXISTS \"answer_traces_capability_grant_idx\"\n ON \"answer_traces\" (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\");\nCREATE INDEX IF NOT EXISTS \"document_compilation_attempts_capability_grant_idx\"\n ON \"document_compilation_attempts\" (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\");\nCREATE INDEX IF NOT EXISTS \"document_revisions_capability_grant_idx\"\n ON \"document_revisions\" (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\");\nCREATE INDEX IF NOT EXISTS \"knowledge_space_profile_migration_runs_capability_grant_idx\"\n ON \"knowledge_space_profile_migration_runs\" (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\");\nCREATE INDEX IF NOT EXISTS \"source_workflow_runs_capability_grant_idx\"\n ON \"source_workflow_runs\" (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\");\nCREATE INDEX IF NOT EXISTS \"quality_replay_runs_capability_grant_idx\"\n ON \"quality_replay_runs\" (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\");\nCREATE INDEX IF NOT EXISTS \"deletion_jobs_capability_grant_idx\"\n ON \"deletion_jobs\" (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\");\nCREATE INDEX IF NOT EXISTS \"deletion_retry_audits_capability_grant_idx\"\n ON \"deletion_retry_audits\" (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\");\n", path: "packages/database/migrations/0026_capability_job_provenance.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0026_capability_job_provenance\n-- Dialect: tidb\n-- Capability jobs persist only a grant locator. Nullable snapshot columns remain for rollback and\n-- already-durable legacy rows; no bearer token, raw jti, or Dify membership snapshot is stored.\n\nALTER TABLE `research_task_jobs`\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL,\n MODIFY COLUMN `subject_id` VARCHAR(255) NULL,\n MODIFY COLUMN `permission_snapshot_id` CHAR(36) NULL,\n MODIFY COLUMN `permission_snapshot_revision` INT NULL,\n MODIFY COLUMN `access_channel` VARCHAR(16) NULL,\n ADD CONSTRAINT `research_task_jobs_authorization_binding_ck` CHECK (\n (`capability_grant_id` IS NOT NULL AND `subject_id` IS NULL\n AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL\n AND `access_channel` IS NULL)\n OR (`capability_grant_id` IS NULL AND `subject_id` IS NOT NULL\n AND `permission_snapshot_id` IS NOT NULL AND `permission_snapshot_revision` >= 1\n AND `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT `research_task_jobs_capability_grant_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`)\n ON DELETE RESTRICT;\n\nALTER TABLE `answer_traces`\n ADD COLUMN IF NOT EXISTS `tenant_id` VARCHAR(255) NULL,\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL,\n DROP CONSTRAINT `answer_traces_permission_snapshot_binding_ck`,\n ADD CONSTRAINT `answer_traces_authorization_binding_ck` CHECK (\n (`capability_grant_id` IS NOT NULL AND `tenant_id` IS NOT NULL AND `subject_id` IS NULL\n AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL\n AND `access_channel` IS NULL)\n OR (`capability_grant_id` IS NULL AND (\n (`permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL\n AND `access_channel` IS NULL)\n OR (`subject_id` IS NOT NULL AND `permission_snapshot_id` IS NOT NULL\n AND `permission_snapshot_revision` >= 1\n AND `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent'))))\n ),\n ADD CONSTRAINT `answer_traces_capability_grant_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`)\n ON DELETE RESTRICT;\n\nALTER TABLE `document_compilation_attempts`\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL,\n DROP CONSTRAINT `document_compilation_attempts_permission_binding_ck`,\n ADD CONSTRAINT `document_compilation_attempts_authorization_binding_ck` CHECK (\n (`capability_grant_id` IS NOT NULL AND `requested_by_subject_id` IS NULL\n AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL\n AND `access_channel` IS NULL)\n OR (`capability_grant_id` IS NULL AND (\n (`requested_by_subject_id` IS NULL AND `permission_snapshot_id` IS NULL\n AND `permission_snapshot_revision` IS NULL AND `access_channel` IS NULL)\n OR (`requested_by_subject_id` IS NOT NULL AND `permission_snapshot_id` IS NOT NULL\n AND `permission_snapshot_revision` >= 1\n AND `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent'))))\n ),\n ADD CONSTRAINT `document_compilation_attempts_capability_grant_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`)\n ON DELETE RESTRICT;\n\nALTER TABLE `document_revisions`\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL,\n ADD CONSTRAINT `document_revisions_capability_grant_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`)\n ON DELETE RESTRICT;\n\nALTER TABLE `knowledge_space_profile_migration_runs`\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL,\n MODIFY COLUMN `permission_snapshot_id` CHAR(36) NULL,\n MODIFY COLUMN `permission_snapshot_revision` INT NULL,\n MODIFY COLUMN `requested_by_subject_id` VARCHAR(255) NULL,\n MODIFY COLUMN `access_channel` VARCHAR(16) NULL,\n DROP CONSTRAINT `knowledge_space_profile_migration_runs_positive_ck`,\n ADD CONSTRAINT `knowledge_space_profile_migration_runs_authorization_binding_ck` CHECK (\n (`capability_grant_id` IS NOT NULL AND `requested_by_subject_id` IS NULL\n AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL\n AND `access_channel` IS NULL)\n OR (`capability_grant_id` IS NULL AND `requested_by_subject_id` IS NOT NULL\n AND `permission_snapshot_id` IS NOT NULL AND `permission_snapshot_revision` >= 1\n AND `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT `knowledge_space_profile_migration_runs_positive_ck` CHECK (\n `candidate_profile_revision` >= 1 AND `base_retrieval_profile_revision` >= 1\n AND `base_publication_head_revision` >= 1\n AND (`capability_grant_id` IS NOT NULL OR `permission_snapshot_revision` >= 1)\n AND `execution_attempts` >= 0 AND `max_execution_attempts` >= 1\n AND `execution_attempts` <= `max_execution_attempts` AND `row_version` >= 1\n AND (`active_slot` IS NULL OR `active_slot` = 1)\n ),\n ADD CONSTRAINT `knowledge_space_profile_migration_runs_capability_grant_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`)\n ON DELETE RESTRICT;\n\nALTER TABLE `source_workflow_runs`\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL,\n MODIFY COLUMN `permission_snapshot_id` CHAR(36) NULL,\n MODIFY COLUMN `permission_snapshot_revision` INT NULL,\n MODIFY COLUMN `requested_by_subject_id` VARCHAR(255) NULL,\n MODIFY COLUMN `required_permission_scope` JSON NULL,\n MODIFY COLUMN `access_channel` VARCHAR(16) NULL,\n DROP CONSTRAINT `source_workflow_runs_nonnegative_ck`,\n ADD CONSTRAINT `source_workflow_runs_authorization_binding_ck` CHECK (\n (`capability_grant_id` IS NOT NULL AND `requested_by_subject_id` IS NULL\n AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL\n AND `required_permission_scope` IS NULL AND `access_channel` IS NULL)\n OR (`capability_grant_id` IS NULL AND `requested_by_subject_id` IS NOT NULL\n AND `permission_snapshot_id` IS NOT NULL AND `permission_snapshot_revision` >= 1\n AND `required_permission_scope` IS NOT NULL\n AND JSON_TYPE(`required_permission_scope`) = 'ARRAY'\n AND `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT `source_workflow_runs_nonnegative_ck` CHECK (\n `progress_completed` >= 0 AND `progress_skipped` >= 0 AND `progress_failed` >= 0\n AND `execution_attempts` >= 0 AND `max_execution_attempts` >= 1\n AND `execution_attempts` <= `max_execution_attempts`\n AND (`capability_grant_id` IS NOT NULL OR `permission_snapshot_revision` >= 1)\n AND `row_version` >= 1 AND (`active_slot` IS NULL OR `active_slot` = 1)\n ),\n ADD CONSTRAINT `source_workflow_runs_capability_grant_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`)\n ON DELETE RESTRICT;\n\nALTER TABLE `quality_replay_runs`\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL,\n MODIFY COLUMN `requested_by_subject_id` VARCHAR(255) NULL,\n MODIFY COLUMN `access_channel` VARCHAR(16) NULL,\n MODIFY COLUMN `permission_snapshot_id` CHAR(36) NULL,\n MODIFY COLUMN `permission_snapshot_revision` INT NULL,\n MODIFY COLUMN `required_permission_scope` JSON NULL,\n DROP CONSTRAINT `quality_replay_runs_revision_ck`,\n ADD CONSTRAINT `quality_replay_runs_authorization_binding_ck` CHECK (\n (`capability_grant_id` IS NOT NULL AND `requested_by_subject_id` IS NULL\n AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL\n AND `required_permission_scope` IS NULL AND `access_channel` IS NULL)\n OR (`capability_grant_id` IS NULL AND `requested_by_subject_id` IS NOT NULL\n AND `permission_snapshot_id` IS NOT NULL AND `permission_snapshot_revision` >= 1\n AND `required_permission_scope` IS NOT NULL\n AND JSON_TYPE(`required_permission_scope`) = 'ARRAY'\n AND `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT `quality_replay_runs_revision_ck` CHECK (\n `revision` >= 1 AND `attempt` >= 0\n AND (`capability_grant_id` IS NOT NULL OR `permission_snapshot_revision` >= 1)\n AND `request_fingerprint` REGEXP '^sha256:[a-f0-9]{64}$'\n ),\n ADD CONSTRAINT `quality_replay_runs_capability_grant_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`)\n ON DELETE RESTRICT;\n\nALTER TABLE `deletion_jobs`\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL,\n MODIFY COLUMN `requested_by_subject_id` VARCHAR(255) NULL,\n MODIFY COLUMN `permission_snapshot_id` CHAR(36) NULL,\n MODIFY COLUMN `permission_snapshot_revision` INT NULL,\n MODIFY COLUMN `access_channel` VARCHAR(16) NULL,\n DROP CONSTRAINT `deletion_jobs_positive_ck`,\n ADD CONSTRAINT `deletion_jobs_authorization_binding_ck` CHECK (\n (`capability_grant_id` IS NOT NULL AND `requested_by_subject_id` IS NULL\n AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL\n AND `access_channel` IS NULL AND `api_key_id` IS NULL\n AND `api_key_revision` IS NULL AND `api_key_expires_at` IS NULL)\n OR (`capability_grant_id` IS NULL AND `requested_by_subject_id` IS NOT NULL\n AND `permission_snapshot_id` IS NOT NULL AND `permission_snapshot_revision` >= 1\n AND `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT `deletion_jobs_positive_ck` CHECK (\n `target_revision` >= 1\n AND (`capability_grant_id` IS NOT NULL OR `permission_snapshot_revision` >= 1)\n AND `row_version` >= 1 AND `execution_attempts` >= 0\n AND `max_execution_attempts` >= 1 AND `execution_attempts` <= `max_execution_attempts`\n AND (`active_slot` IS NULL OR `active_slot` = 1)\n ),\n ADD CONSTRAINT `deletion_jobs_capability_grant_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`)\n ON DELETE RESTRICT;\n\nALTER TABLE `deletion_retry_audits`\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL,\n MODIFY COLUMN `actor_subject_id` VARCHAR(255) NULL,\n MODIFY COLUMN `permission_snapshot_id` CHAR(36) NULL,\n MODIFY COLUMN `permission_snapshot_revision` INT NULL,\n MODIFY COLUMN `access_channel` VARCHAR(16) NULL,\n DROP CONSTRAINT `deletion_retry_audits_positive_ck`,\n DROP CONSTRAINT `deletion_retry_audits_owner_rescue_ck`,\n ADD CONSTRAINT `deletion_retry_audits_authorization_binding_ck` CHECK (\n (`capability_grant_id` IS NOT NULL AND `retry_authority` = 'original_requester'\n AND `actor_subject_id` IS NULL AND `permission_snapshot_id` IS NULL\n AND `permission_snapshot_revision` IS NULL AND `access_channel` IS NULL\n AND `api_key_id` IS NULL AND `api_key_revision` IS NULL\n AND `api_key_expires_at` IS NULL)\n OR (`capability_grant_id` IS NULL AND `actor_subject_id` IS NOT NULL\n AND `permission_snapshot_id` IS NOT NULL AND `permission_snapshot_revision` >= 1\n AND `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent'))\n ),\n ADD CONSTRAINT `deletion_retry_audits_positive_ck` CHECK (\n `capability_grant_id` IS NOT NULL OR `permission_snapshot_revision` >= 1\n ),\n ADD CONSTRAINT `deletion_retry_audits_owner_rescue_ck` CHECK (\n `retry_authority` <> 'interactive_owner_rescue'\n OR (`access_channel` = 'interactive' AND `api_key_id` IS NULL\n AND `api_key_revision` IS NULL AND `api_key_expires_at` IS NULL)\n ),\n ADD CONSTRAINT `deletion_retry_audits_capability_grant_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`)\n ON DELETE RESTRICT;\n\nCREATE INDEX IF NOT EXISTS `research_task_jobs_capability_grant_idx`\n ON `research_task_jobs` (`tenant_id`, `knowledge_space_id`, `capability_grant_id`);\nCREATE INDEX IF NOT EXISTS `answer_traces_capability_grant_idx`\n ON `answer_traces` (`tenant_id`, `knowledge_space_id`, `capability_grant_id`);\nCREATE INDEX IF NOT EXISTS `document_compilation_attempts_capability_grant_idx`\n ON `document_compilation_attempts` (`tenant_id`, `knowledge_space_id`, `capability_grant_id`);\nCREATE INDEX IF NOT EXISTS `document_revisions_capability_grant_idx`\n ON `document_revisions` (`tenant_id`, `knowledge_space_id`, `capability_grant_id`);\nCREATE INDEX IF NOT EXISTS `knowledge_space_profile_migration_runs_capability_grant_idx`\n ON `knowledge_space_profile_migration_runs` (`tenant_id`, `knowledge_space_id`, `capability_grant_id`);\nCREATE INDEX IF NOT EXISTS `source_workflow_runs_capability_grant_idx`\n ON `source_workflow_runs` (`tenant_id`, `knowledge_space_id`, `capability_grant_id`);\nCREATE INDEX IF NOT EXISTS `quality_replay_runs_capability_grant_idx`\n ON `quality_replay_runs` (`tenant_id`, `knowledge_space_id`, `capability_grant_id`);\nCREATE INDEX IF NOT EXISTS `deletion_jobs_capability_grant_idx`\n ON `deletion_jobs` (`tenant_id`, `knowledge_space_id`, `capability_grant_id`);\nCREATE INDEX IF NOT EXISTS `deletion_retry_audits_capability_grant_idx`\n ON `deletion_retry_audits` (`tenant_id`, `knowledge_space_id`, `capability_grant_id`);\n", path: "packages/database/migrations/0026_capability_job_provenance.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0027_upload_sessions\n-- Dialect: postgres\n-- Object bytes never pass through this table; presigned URLs and bearer tokens are never stored.\n\nCREATE TABLE IF NOT EXISTS \"upload_sessions\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"grant_id\" UUID NOT NULL,\n \"completion_grant_id\" UUID,\n \"idempotency_key\" VARCHAR(255) NOT NULL,\n \"object_key\" VARCHAR(1024) NOT NULL,\n \"file_name\" VARCHAR(512) NOT NULL,\n \"content_type\" VARCHAR(255) NOT NULL,\n \"checksum_sha256_base64\" VARCHAR(255) NOT NULL,\n \"expected_size_bytes\" BIGINT NOT NULL,\n \"reserved_bytes\" BIGINT NOT NULL,\n \"mode\" VARCHAR(16) NOT NULL,\n \"multipart_upload_id\" VARCHAR(1024),\n \"multipart_part_size_bytes\" BIGINT,\n \"multipart_part_count\" INTEGER,\n \"status\" VARCHAR(16) NOT NULL,\n \"completion_parts\" JSONB NOT NULL,\n \"document_asset_id\" UUID,\n \"compilation_job_id\" UUID,\n \"error_code\" VARCHAR(64),\n \"expires_at\" BIGINT NOT NULL,\n \"aborted_at\" BIGINT,\n \"completed_at\" BIGINT,\n \"row_version\" INTEGER NOT NULL,\n \"created_at\" BIGINT NOT NULL,\n \"updated_at\" BIGINT NOT NULL,\n CONSTRAINT \"upload_sessions_state_ck\" CHECK (\n \"mode\" IN ('single', 'multipart', 'small_fallback')\n AND \"status\" IN (\n 'creating', 'ready', 'completing', 'completed',\n 'aborting', 'aborted', 'expired', 'failed'\n )\n ),\n CONSTRAINT \"upload_sessions_bounds_ck\" CHECK (\n \"expected_size_bytes\" >= 1\n AND \"reserved_bytes\" >= 0\n AND \"reserved_bytes\" <= \"expected_size_bytes\"\n AND \"row_version\" >= 1\n AND \"expires_at\" > \"created_at\"\n ),\n CONSTRAINT \"upload_sessions_multipart_ck\" CHECK (\n (\"mode\" = 'multipart'\n AND \"multipart_part_size_bytes\" IS NOT NULL\n AND \"multipart_part_count\" IS NOT NULL\n AND \"multipart_part_size_bytes\" >= 5242880\n AND \"multipart_part_count\" BETWEEN 1 AND 10000)\n OR\n (\"mode\" <> 'multipart'\n AND \"multipart_upload_id\" IS NULL\n AND \"multipart_part_size_bytes\" IS NULL\n AND \"multipart_part_count\" IS NULL)\n ),\n CONSTRAINT \"upload_sessions_terminal_ck\" CHECK (\n ((\"status\" = 'completed'\n AND \"completed_at\" IS NOT NULL\n AND \"document_asset_id\" IS NOT NULL\n AND \"compilation_job_id\" IS NOT NULL\n AND \"reserved_bytes\" = 0)\n OR (\"status\" <> 'completed'\n AND \"completed_at\" IS NULL\n AND \"document_asset_id\" IS NULL\n AND \"compilation_job_id\" IS NULL))\n AND ((\"status\" = 'aborted' AND \"aborted_at\" IS NOT NULL AND \"reserved_bytes\" = 0)\n OR (\"status\" <> 'aborted' AND \"aborted_at\" IS NULL))\n AND (\"status\" NOT IN ('expired', 'failed') OR \"reserved_bytes\" = 0)\n ),\n CONSTRAINT \"upload_sessions_completion_grant_ck\" CHECK (\n ((\"status\" IN ('completing', 'completed') AND \"completion_grant_id\" IS NOT NULL)\n OR (\"status\" NOT IN ('completing', 'completed') AND \"completion_grant_id\" IS NULL))\n ),\n CONSTRAINT \"upload_sessions_completion_parts_ck\" CHECK (\n jsonb_typeof(\"completion_parts\") = 'array'\n ),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\"),\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"completion_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"upload_sessions_scope_idempotency_uq\"\n ON \"upload_sessions\" (\"tenant_id\", \"knowledge_space_id\", \"idempotency_key\");\nCREATE UNIQUE INDEX IF NOT EXISTS \"upload_sessions_object_key_uq\"\n ON \"upload_sessions\" (\"object_key\");\nCREATE INDEX IF NOT EXISTS \"upload_sessions_expiry_idx\"\n ON \"upload_sessions\" (\"tenant_id\", \"status\", \"expires_at\", \"id\");\nCREATE INDEX IF NOT EXISTS \"upload_sessions_grant_status_idx\"\n ON \"upload_sessions\" (\n \"tenant_id\", \"knowledge_space_id\", \"grant_id\", \"status\", \"id\"\n );\nCREATE INDEX IF NOT EXISTS \"upload_sessions_completion_grant_status_idx\"\n ON \"upload_sessions\" (\n \"tenant_id\", \"knowledge_space_id\", \"completion_grant_id\", \"status\", \"id\"\n );\n", path: "packages/database/migrations/0027_upload_sessions.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0027_upload_sessions\n-- Dialect: tidb\n-- Object bytes never pass through this table; presigned URLs and bearer tokens are never stored.\n\nCREATE TABLE IF NOT EXISTS `upload_sessions` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `grant_id` CHAR(36) NOT NULL,\n `completion_grant_id` CHAR(36),\n `idempotency_key` VARCHAR(255) NOT NULL,\n `object_key` VARCHAR(1024) NOT NULL,\n `file_name` VARCHAR(512) NOT NULL,\n `content_type` VARCHAR(255) NOT NULL,\n `checksum_sha256_base64` VARCHAR(255) NOT NULL,\n `expected_size_bytes` BIGINT NOT NULL,\n `reserved_bytes` BIGINT NOT NULL,\n `mode` VARCHAR(16) NOT NULL,\n `multipart_upload_id` VARCHAR(1024),\n `multipart_part_size_bytes` BIGINT,\n `multipart_part_count` INT,\n `status` VARCHAR(16) NOT NULL,\n `completion_parts` JSON NOT NULL,\n `document_asset_id` CHAR(36),\n `compilation_job_id` CHAR(36),\n `error_code` VARCHAR(64),\n `expires_at` BIGINT NOT NULL,\n `aborted_at` BIGINT,\n `completed_at` BIGINT,\n `row_version` INT NOT NULL,\n `created_at` BIGINT NOT NULL,\n `updated_at` BIGINT NOT NULL,\n CONSTRAINT `upload_sessions_state_ck` CHECK (\n `mode` IN ('single', 'multipart', 'small_fallback')\n AND `status` IN (\n 'creating', 'ready', 'completing', 'completed',\n 'aborting', 'aborted', 'expired', 'failed'\n )\n ),\n CONSTRAINT `upload_sessions_bounds_ck` CHECK (\n `expected_size_bytes` >= 1\n AND `reserved_bytes` >= 0\n AND `reserved_bytes` <= `expected_size_bytes`\n AND `row_version` >= 1\n AND `expires_at` > `created_at`\n ),\n CONSTRAINT `upload_sessions_multipart_ck` CHECK (\n (`mode` = 'multipart'\n AND `multipart_part_size_bytes` IS NOT NULL\n AND `multipart_part_count` IS NOT NULL\n AND `multipart_part_size_bytes` >= 5242880\n AND `multipart_part_count` BETWEEN 1 AND 10000)\n OR\n (`mode` <> 'multipart'\n AND `multipart_upload_id` IS NULL\n AND `multipart_part_size_bytes` IS NULL\n AND `multipart_part_count` IS NULL)\n ),\n CONSTRAINT `upload_sessions_terminal_ck` CHECK (\n ((`status` = 'completed'\n AND `completed_at` IS NOT NULL\n AND `document_asset_id` IS NOT NULL\n AND `compilation_job_id` IS NOT NULL\n AND `reserved_bytes` = 0)\n OR (`status` <> 'completed'\n AND `completed_at` IS NULL\n AND `document_asset_id` IS NULL\n AND `compilation_job_id` IS NULL))\n AND ((`status` = 'aborted' AND `aborted_at` IS NOT NULL AND `reserved_bytes` = 0)\n OR (`status` <> 'aborted' AND `aborted_at` IS NULL))\n AND (`status` NOT IN ('expired', 'failed') OR `reserved_bytes` = 0)\n ),\n CONSTRAINT `upload_sessions_completion_grant_ck` CHECK (\n ((`status` IN ('completing', 'completed') AND `completion_grant_id` IS NOT NULL)\n OR (`status` NOT IN ('completing', 'completed') AND `completion_grant_id` IS NULL))\n ),\n CONSTRAINT `upload_sessions_completion_parts_ck` CHECK (\n JSON_TYPE(`completion_parts`) = 'ARRAY'\n ),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`),\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `completion_grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `upload_sessions_scope_idempotency_uq`\n ON `upload_sessions` (`tenant_id`, `knowledge_space_id`, `idempotency_key`);\nCREATE UNIQUE INDEX IF NOT EXISTS `upload_sessions_object_key_uq`\n ON `upload_sessions` (`object_key`);\nCREATE INDEX IF NOT EXISTS `upload_sessions_expiry_idx`\n ON `upload_sessions` (`tenant_id`, `status`, `expires_at`, `id`);\nCREATE INDEX IF NOT EXISTS `upload_sessions_grant_status_idx`\n ON `upload_sessions` (\n `tenant_id`, `knowledge_space_id`, `grant_id`, `status`, `id`\n );\nCREATE INDEX IF NOT EXISTS `upload_sessions_completion_grant_status_idx`\n ON `upload_sessions` (\n `tenant_id`, `knowledge_space_id`, `completion_grant_id`, `status`, `id`\n );\n", path: "packages/database/migrations/0027_upload_sessions.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0028_dify_integration_states\n-- Dialect: postgres\n-- One-way, tenant-scoped activation prevents mixed authorization sources inside a Workspace.\n\nCREATE TABLE IF NOT EXISTS \"dify_integration_states\" (\n \"tenant_id\" VARCHAR(255) PRIMARY KEY NOT NULL,\n \"activation_id\" VARCHAR(255) NOT NULL,\n \"activation_revision\" BIGINT NOT NULL,\n \"source_revision_digest\" VARCHAR(71) NOT NULL,\n \"activated_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"dify_integration_states_evidence_ck\" CHECK (\n \"activation_revision\" >= 1\n AND \"source_revision_digest\" ~ '^sha256:[a-f0-9]{64}$'\n )\n);\n", path: "packages/database/migrations/0028_dify_integration_states.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0028_dify_integration_states\n-- Dialect: tidb\n-- One-way, tenant-scoped activation prevents mixed authorization sources inside a Workspace.\n\nCREATE TABLE IF NOT EXISTS `dify_integration_states` (\n `tenant_id` VARCHAR(255) PRIMARY KEY NOT NULL,\n `activation_id` VARCHAR(255) NOT NULL,\n `activation_revision` BIGINT NOT NULL,\n `source_revision_digest` VARCHAR(71) NOT NULL,\n `activated_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `dify_integration_states_evidence_ck` CHECK (\n `activation_revision` >= 1\n AND `source_revision_digest` REGEXP '^sha256:[a-f0-9]{64}$'\n )\n);\n", path: "packages/database/migrations/0028_dify_integration_states.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0029_dify_integration_freezes\n-- Dialect: postgres\n-- A durable per-Workspace maintenance freeze stops legacy writes before final delta.\n\nCREATE TABLE IF NOT EXISTS \"dify_integration_freezes\" (\n \"tenant_id\" VARCHAR(255) PRIMARY KEY NOT NULL,\n \"freeze_id\" VARCHAR(255) NOT NULL,\n \"freeze_revision\" BIGINT NOT NULL,\n \"source_revision_digest\" VARCHAR(71) NOT NULL,\n \"source_task_watermark\" BIGINT NOT NULL,\n \"frozen_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"dify_integration_freezes_evidence_ck\" CHECK (\n \"freeze_revision\" >= 1\n AND \"source_task_watermark\" >= 0\n AND \"source_revision_digest\" ~ '^sha256:[a-f0-9]{64}$'\n )\n);\n", path: "packages/database/migrations/0029_dify_integration_freezes.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0029_dify_integration_freezes\n-- Dialect: tidb\n-- A durable per-Workspace maintenance freeze stops legacy writes before final delta.\n\nCREATE TABLE IF NOT EXISTS `dify_integration_freezes` (\n `tenant_id` VARCHAR(255) PRIMARY KEY NOT NULL,\n `freeze_id` VARCHAR(255) NOT NULL,\n `freeze_revision` BIGINT NOT NULL,\n `source_revision_digest` VARCHAR(71) NOT NULL,\n `source_task_watermark` BIGINT NOT NULL,\n `frozen_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `dify_integration_freezes_evidence_ck` CHECK (\n `freeze_revision` >= 1\n AND `source_task_watermark` >= 0\n AND `source_revision_digest` REGEXP '^sha256:[a-f0-9]{64}$'\n )\n);\n", path: "packages/database/migrations/0029_dify_integration_freezes.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0030_bulk_operations\n-- Dialect: postgres\n-- Persists user-visible bulk document tasks so progress and controls survive process restarts.\n\nCREATE TABLE IF NOT EXISTS \"bulk_operations\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"operation_type\" VARCHAR(32) NOT NULL,\n \"items\" JSONB NOT NULL,\n \"required_permission_scope\" JSONB NOT NULL,\n \"has_not_found_items\" BOOLEAN NOT NULL DEFAULT FALSE,\n \"capability_grant_id\" UUID,\n \"permission_access_channel\" VARCHAR(16),\n \"permission_snapshot_id\" UUID,\n \"permission_snapshot_revision\" BIGINT,\n \"requested_by_subject_id\" VARCHAR(255),\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"bulk_operations_space_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"bulk_operations_type_ck\"\n CHECK (\"operation_type\" IN ('document_upload', 'document_delete', 'document_reindex')),\n CONSTRAINT \"bulk_operations_items_ck\"\n CHECK (jsonb_typeof(\"items\") = 'array'),\n CONSTRAINT \"bulk_operations_scope_ck\"\n CHECK (jsonb_typeof(\"required_permission_scope\") = 'array'),\n CONSTRAINT \"bulk_operations_permission_ck\"\n CHECK (\n (\n \"permission_access_channel\" IS NULL\n AND \"permission_snapshot_id\" IS NULL\n AND \"permission_snapshot_revision\" IS NULL\n )\n OR (\n \"permission_access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')\n AND \"permission_snapshot_id\" IS NOT NULL\n AND \"permission_snapshot_revision\" >= 1\n AND \"requested_by_subject_id\" IS NOT NULL\n )\n ),\n CONSTRAINT \"bulk_operations_authorization_ck\"\n CHECK (\n (\"capability_grant_id\" IS NOT NULL AND \"permission_snapshot_id\" IS NULL)\n OR (\"capability_grant_id\" IS NULL AND \"permission_snapshot_id\" IS NOT NULL)\n ),\n CONSTRAINT \"bulk_operations_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT,\n CONSTRAINT \"bulk_operations_permission_snapshot_fk\"\n FOREIGN KEY (\n \"tenant_id\", \"knowledge_space_id\", \"permission_snapshot_id\",\n \"requested_by_subject_id\", \"permission_access_channel\"\n ) REFERENCES \"knowledge_space_permission_snapshots\" (\n \"tenant_id\", \"knowledge_space_id\", \"id\", \"subject_id\", \"access_channel\"\n ) ON DELETE RESTRICT\n);\n\nCREATE INDEX IF NOT EXISTS \"bulk_operations_space_created_idx\"\n ON \"bulk_operations\" (\"tenant_id\", \"knowledge_space_id\", \"created_at\" DESC, \"id\" DESC);\n", path: "packages/database/migrations/0030_bulk_operations.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0030_bulk_operations\n-- Dialect: tidb\n-- Persists user-visible bulk document tasks so progress and controls survive process restarts.\n\nCREATE TABLE IF NOT EXISTS `bulk_operations` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `operation_type` VARCHAR(32) NOT NULL,\n `items` JSON NOT NULL,\n `required_permission_scope` JSON NOT NULL,\n `has_not_found_items` BOOLEAN NOT NULL DEFAULT FALSE,\n `capability_grant_id` CHAR(36),\n `permission_access_channel` VARCHAR(16),\n `permission_snapshot_id` CHAR(36),\n `permission_snapshot_revision` BIGINT,\n `requested_by_subject_id` VARCHAR(255),\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `bulk_operations_space_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n CONSTRAINT `bulk_operations_type_ck`\n CHECK (`operation_type` IN ('document_upload', 'document_delete', 'document_reindex')),\n CONSTRAINT `bulk_operations_items_ck`\n CHECK (JSON_TYPE(`items`) = 'ARRAY'),\n CONSTRAINT `bulk_operations_scope_ck`\n CHECK (JSON_TYPE(`required_permission_scope`) = 'ARRAY'),\n CONSTRAINT `bulk_operations_permission_ck`\n CHECK (\n (\n `permission_access_channel` IS NULL\n AND `permission_snapshot_id` IS NULL\n AND `permission_snapshot_revision` IS NULL\n )\n OR (\n `permission_access_channel` IN ('interactive', 'service_api', 'mcp', 'agent')\n AND `permission_snapshot_id` IS NOT NULL\n AND `permission_snapshot_revision` >= 1\n AND `requested_by_subject_id` IS NOT NULL\n )\n ),\n CONSTRAINT `bulk_operations_authorization_ck`\n CHECK (\n (`capability_grant_id` IS NOT NULL AND `permission_snapshot_id` IS NULL)\n OR (`capability_grant_id` IS NULL AND `permission_snapshot_id` IS NOT NULL)\n ),\n CONSTRAINT `bulk_operations_capability_grant_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`)\n REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`),\n CONSTRAINT `bulk_operations_permission_snapshot_fk`\n FOREIGN KEY (\n `tenant_id`, `knowledge_space_id`, `permission_snapshot_id`,\n `requested_by_subject_id`, `permission_access_channel`\n ) REFERENCES `knowledge_space_permission_snapshots` (\n `tenant_id`, `knowledge_space_id`, `id`, `subject_id`, `access_channel`\n )\n);\n\nCREATE INDEX IF NOT EXISTS `bulk_operations_space_created_idx`\n ON `bulk_operations` (`tenant_id`, `knowledge_space_id`, `created_at` DESC, `id` DESC);\n", path: "packages/database/migrations/0030_bulk_operations.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0031_source_connection_capability_provenance\n-- Dialect: postgres\n-- Integrated source connections persist only the admitted Capability grant locator. The bearer,\n-- raw jti, Dify credential, and membership snapshot never cross this persistence boundary.\n\nALTER TABLE \"source_connections\"\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID;\n\nDO $kfs_source_connection_capability_grant_fk$\nBEGIN\n IF NOT EXISTS (\n SELECT 1\n FROM pg_constraint\n WHERE conname = 'source_connections_capability_grant_fk'\n AND conrelid = 'source_connections'::regclass\n ) THEN\n ALTER TABLE \"source_connections\"\n ADD CONSTRAINT \"source_connections_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\n END IF;\nEND\n$kfs_source_connection_capability_grant_fk$;\n\nCREATE INDEX IF NOT EXISTS \"source_connections_capability_grant_idx\"\n ON \"source_connections\" (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\");\n", path: "packages/database/migrations/0031_source_connection_capability_provenance.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0031_source_connection_capability_provenance\n-- Dialect: tidb\n-- Integrated source connections persist only the admitted Capability grant locator. The bearer,\n-- raw jti, Dify credential, and membership snapshot never cross this persistence boundary.\n\nALTER TABLE `source_connections`\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL;\n\nSET @source_connection_capability_fk_exists = (\n SELECT COUNT(*)\n FROM information_schema.table_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'source_connections'\n AND constraint_name = 'source_connections_capability_grant_fk'\n);\nSET @source_connection_capability_fk_ddl = IF(\n @source_connection_capability_fk_exists = 0,\n 'ALTER TABLE `source_connections` ADD CONSTRAINT `source_connections_capability_grant_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`) REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`) ON DELETE RESTRICT',\n 'DO 0'\n);\nPREPARE source_connection_capability_fk_statement\n FROM @source_connection_capability_fk_ddl;\nEXECUTE source_connection_capability_fk_statement;\nDEALLOCATE PREPARE source_connection_capability_fk_statement;\n\nCREATE INDEX IF NOT EXISTS `source_connections_capability_grant_idx`\n ON `source_connections` (`tenant_id`, `knowledge_space_id`, `capability_grant_id`);\n", path: "packages/database/migrations/0031_source_connection_capability_provenance.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0032_capability_source_sync_policies\n-- Dialect: postgres\n-- Allows durable source sync policies to retain either a Capability grant or legacy ACL snapshot.\n\nALTER TABLE \"source_sync_policies\"\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID,\n ALTER COLUMN \"requested_by_subject_id\" DROP NOT NULL,\n ALTER COLUMN \"access_channel\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_id\" DROP NOT NULL,\n ALTER COLUMN \"permission_snapshot_revision\" DROP NOT NULL,\n ALTER COLUMN \"required_permission_scope\" DROP NOT NULL;\n\nALTER TABLE \"source_sync_policies\"\n DROP CONSTRAINT IF EXISTS \"source_sync_policies_channel_ck\",\n DROP CONSTRAINT IF EXISTS \"source_sync_policies_revision_ck\",\n DROP CONSTRAINT IF EXISTS \"source_sync_policies_authorization_binding_ck\";\n\nALTER TABLE \"source_sync_policies\"\n ADD CONSTRAINT \"source_sync_policies_channel_ck\" CHECK (\n \"access_channel\" IS NULL\n OR \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')\n ),\n ADD CONSTRAINT \"source_sync_policies_revision_ck\" CHECK (\n \"revision\" >= 1 AND \"expected_source_version\" >= 1\n AND (\"capability_grant_id\" IS NOT NULL OR \"permission_snapshot_revision\" >= 1)\n ),\n ADD CONSTRAINT \"source_sync_policies_authorization_binding_ck\" CHECK (\n (\n \"capability_grant_id\" IS NOT NULL\n AND \"requested_by_subject_id\" IS NULL\n AND \"access_channel\" IS NULL\n AND \"permission_snapshot_id\" IS NULL\n AND \"permission_snapshot_revision\" IS NULL\n AND \"required_permission_scope\" IS NULL\n )\n OR (\n \"capability_grant_id\" IS NULL\n AND \"requested_by_subject_id\" IS NOT NULL\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')\n AND \"permission_snapshot_id\" IS NOT NULL\n AND \"permission_snapshot_revision\" >= 1\n AND \"required_permission_scope\" IS NOT NULL\n AND jsonb_typeof(\"required_permission_scope\") = 'array'\n )\n );\n\nDO $kfs_0032_source_sync_policy_capability_fk$\nBEGIN\n IF NOT EXISTS (\n SELECT 1\n FROM pg_constraint\n WHERE conname = 'source_sync_policies_capability_grant_fk'\n AND conrelid = 'source_sync_policies'::regclass\n ) THEN\n ALTER TABLE \"source_sync_policies\"\n ADD CONSTRAINT \"source_sync_policies_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\n END IF;\nEND\n$kfs_0032_source_sync_policy_capability_fk$;\n\nCREATE INDEX IF NOT EXISTS \"source_sync_policies_capability_grant_idx\"\n ON \"source_sync_policies\" (\n \"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\"\n );\n", path: "packages/database/migrations/0032_capability_source_sync_policies.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0032_capability_source_sync_policies\n-- Dialect: tidb\n-- Allows durable source sync policies to retain either a Capability grant or legacy ACL snapshot.\n\nALTER TABLE `source_sync_policies`\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL,\n MODIFY COLUMN `requested_by_subject_id` VARCHAR(255) NULL,\n MODIFY COLUMN `access_channel` VARCHAR(16) NULL,\n MODIFY COLUMN `permission_snapshot_id` CHAR(36) NULL,\n MODIFY COLUMN `permission_snapshot_revision` INT NULL,\n MODIFY COLUMN `required_permission_scope` JSON NULL,\n DROP CONSTRAINT `source_sync_policies_channel_ck`,\n DROP CONSTRAINT `source_sync_policies_revision_ck`,\n ADD CONSTRAINT `source_sync_policies_channel_ck` CHECK (\n `access_channel` IS NULL\n OR `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent')\n ),\n ADD CONSTRAINT `source_sync_policies_revision_ck` CHECK (\n `revision` >= 1 AND `expected_source_version` >= 1\n AND (`capability_grant_id` IS NOT NULL OR `permission_snapshot_revision` >= 1)\n );\n\nSET @kfs_0032_source_sync_policy_authorization_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'source_sync_policies'\n AND constraint_name = 'source_sync_policies_authorization_binding_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `source_sync_policies` ADD CONSTRAINT `source_sync_policies_authorization_binding_ck` CHECK ((`capability_grant_id` IS NOT NULL AND `requested_by_subject_id` IS NULL AND `access_channel` IS NULL AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL AND `required_permission_scope` IS NULL) OR (`capability_grant_id` IS NULL AND `requested_by_subject_id` IS NOT NULL AND `access_channel` IN (''interactive'', ''service_api'', ''mcp'', ''agent'') AND `permission_snapshot_id` IS NOT NULL AND `permission_snapshot_revision` >= 1 AND `required_permission_scope` IS NOT NULL AND JSON_TYPE(`required_permission_scope`) = ''ARRAY''))'\n);\nPREPARE kfs_0032_source_sync_policy_authorization_stmt\n FROM @kfs_0032_source_sync_policy_authorization_sql;\nEXECUTE kfs_0032_source_sync_policy_authorization_stmt;\nDEALLOCATE PREPARE kfs_0032_source_sync_policy_authorization_stmt;\n\nSET @kfs_0032_source_sync_policy_capability_fk_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.referential_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'source_sync_policies'\n AND constraint_name = 'source_sync_policies_capability_grant_fk'\n ),\n 'DO 0',\n 'ALTER TABLE `source_sync_policies` ADD CONSTRAINT `source_sync_policies_capability_grant_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`) REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`) ON DELETE RESTRICT'\n);\nPREPARE kfs_0032_source_sync_policy_capability_fk_stmt\n FROM @kfs_0032_source_sync_policy_capability_fk_sql;\nEXECUTE kfs_0032_source_sync_policy_capability_fk_stmt;\nDEALLOCATE PREPARE kfs_0032_source_sync_policy_capability_fk_stmt;\n\nCREATE INDEX IF NOT EXISTS `source_sync_policies_capability_grant_idx`\n ON `source_sync_policies` (\n `tenant_id`, `knowledge_space_id`, `capability_grant_id`\n );\n", path: "packages/database/migrations/0032_capability_source_sync_policies.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0033_research_task_final_answers\n-- Dialect: postgres\n-- Research retrieval evidence and its final LLM synthesis are returned from the same durable row.\n\nALTER TABLE \"research_task_partial_results\"\n ADD COLUMN IF NOT EXISTS \"answer\" TEXT;\n", path: "packages/database/migrations/0033_research_task_final_answers.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0033_research_task_final_answers\n-- Dialect: tidb\n-- Research retrieval evidence and its final LLM synthesis are returned from the same durable row.\n\nALTER TABLE `research_task_partial_results`\n ADD COLUMN IF NOT EXISTS `answer` TEXT NULL;\n", path: "packages/database/migrations/0033_research_task_final_answers.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0034_knowledge_space_emoji_icons\n-- Dialect: postgres\n-- Dify persists bounded Emoji Mart identities while retaining the legacy builtin-prefixed form.\n\nALTER TABLE \"knowledge_spaces\"\n DROP CONSTRAINT IF EXISTS \"knowledge_spaces_icon_ref_ck\";\n\nALTER TABLE \"knowledge_spaces\"\n ADD CONSTRAINT \"knowledge_spaces_icon_ref_ck\"\n CHECK (\n \"icon_ref\" IS NULL\n OR \"icon_ref\" ~ '^(builtin:)?[+a-z0-9_-]{1,64}$'\n );\n", path: "packages/database/migrations/0034_knowledge_space_emoji_icons.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0034_knowledge_space_emoji_icons\n-- Dialect: tidb\n-- Dify persists bounded Emoji Mart identities while retaining the legacy builtin-prefixed form.\n-- TiDB DDL may commit before the migration marker, so both replacement steps are replay safe.\n\nSET @kfs_0034_icon_constraint_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'knowledge_spaces'\n AND constraint_name = 'knowledge_spaces_icon_ref_ck'\n ),\n 'ALTER TABLE `knowledge_spaces` DROP CHECK `knowledge_spaces_icon_ref_ck`',\n 'DO 0'\n);\nPREPARE kfs_0034_icon_constraint_stmt FROM @kfs_0034_icon_constraint_sql;\nEXECUTE kfs_0034_icon_constraint_stmt;\nDEALLOCATE PREPARE kfs_0034_icon_constraint_stmt;\n\nSET @kfs_0034_icon_constraint_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'knowledge_spaces'\n AND constraint_name = 'knowledge_spaces_icon_ref_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `knowledge_spaces` ADD CONSTRAINT `knowledge_spaces_icon_ref_ck` CHECK (`icon_ref` IS NULL OR `icon_ref` REGEXP ''^(builtin:)?[+a-z0-9_-]{1,64}$'')'\n);\nPREPARE kfs_0034_icon_constraint_stmt FROM @kfs_0034_icon_constraint_sql;\nEXECUTE kfs_0034_icon_constraint_stmt;\nDEALLOCATE PREPARE kfs_0034_icon_constraint_stmt;\n", path: "packages/database/migrations/0034_knowledge_space_emoji_icons.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0035_research_task_answer_streaming\n-- Dialect: postgres\n-- Allows durable Research progress streams to carry bounded, batched answer deltas.\n\nALTER TABLE \"research_task_progress_events\"\n DROP CONSTRAINT IF EXISTS \"research_task_progress_event_ck\";\n\nALTER TABLE \"research_task_progress_events\"\n ADD CONSTRAINT \"research_task_progress_event_ck\" CHECK (\n \"event_type\" IN (\n 'research_task.answer_delta', 'research_task.canceled', 'research_task.failed',\n 'research_task.paused', 'research_task.resumed', 'research_task.stage_changed',\n 'research_task.started'\n )\n );\n", path: "packages/database/migrations/0035_research_task_answer_streaming.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0035_research_task_answer_streaming\n-- Dialect: tidb\n-- Allows durable Research progress streams to carry bounded, batched answer deltas.\n-- TiDB DDL may commit before the migration marker, so both replacement steps are replay safe.\n\nSET @kfs_0035_progress_event_constraint_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'research_task_progress_events'\n AND constraint_name = 'research_task_progress_event_ck'\n ),\n 'ALTER TABLE `research_task_progress_events` DROP CHECK `research_task_progress_event_ck`',\n 'DO 0'\n);\nPREPARE kfs_0035_progress_event_constraint_stmt\n FROM @kfs_0035_progress_event_constraint_sql;\nEXECUTE kfs_0035_progress_event_constraint_stmt;\nDEALLOCATE PREPARE kfs_0035_progress_event_constraint_stmt;\n\nSET @kfs_0035_progress_event_constraint_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'research_task_progress_events'\n AND constraint_name = 'research_task_progress_event_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `research_task_progress_events` ADD CONSTRAINT `research_task_progress_event_ck` CHECK (`event_type` IN (''research_task.answer_delta'', ''research_task.canceled'', ''research_task.failed'', ''research_task.paused'', ''research_task.resumed'', ''research_task.stage_changed'', ''research_task.started''))'\n);\nPREPARE kfs_0035_progress_event_constraint_stmt\n FROM @kfs_0035_progress_event_constraint_sql;\nEXECUTE kfs_0035_progress_event_constraint_stmt;\nDEALLOCATE PREPARE kfs_0035_progress_event_constraint_stmt;\n", path: "packages/database/migrations/0035_research_task_answer_streaming.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0036_page_index_findability\n-- Dialect: postgres\n-- Persists generation-scoped PageIndex navigation quality and a bounded summary-repair request.\n\nCREATE TABLE IF NOT EXISTS \"page_index_findability_evaluations\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"document_version\" BIGINT NOT NULL,\n \"outline_id\" UUID NOT NULL,\n \"publication_generation_id\" UUID NOT NULL,\n \"publication_fingerprint\" VARCHAR(96) NOT NULL,\n \"compilation_attempt_id\" UUID NOT NULL,\n \"evaluator_version\" VARCHAR(128) NOT NULL,\n \"status\" VARCHAR(32) NOT NULL,\n \"recommended_route\" VARCHAR(16) NOT NULL,\n \"evaluation\" JSONB NOT NULL,\n \"summary_repair_state\" VARCHAR(24) NOT NULL,\n \"summary_repair_attempts\" INTEGER NOT NULL DEFAULT 0,\n \"summary_repair_error\" VARCHAR(2000),\n \"available_at\" TIMESTAMPTZ,\n \"lock_token\" UUID,\n \"locked_by\" VARCHAR(255),\n \"lease_expires_at\" TIMESTAMPTZ,\n \"evaluated_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"page_index_findability_scope_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"page_index_findability_status_ck\"\n CHECK (\"status\" IN ('failed', 'not-evaluated', 'passed')),\n CONSTRAINT \"page_index_findability_route_ck\"\n CHECK (\"recommended_route\" IN ('hybrid', 'layered', 'unchanged')),\n CONSTRAINT \"page_index_findability_repair_ck\"\n CHECK (\n \"summary_repair_state\" IN ('not-requested', 'queued', 'leased', 'dispatched', 'failed')\n AND \"summary_repair_attempts\" >= 0\n AND (\n (\"summary_repair_state\" = 'leased'\n AND \"lock_token\" IS NOT NULL\n AND \"locked_by\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL)\n OR\n (\"summary_repair_state\" <> 'leased'\n AND \"lock_token\" IS NULL\n AND \"locked_by\" IS NULL\n AND \"lease_expires_at\" IS NULL)\n )\n ),\n CONSTRAINT \"page_index_findability_evaluation_ck\"\n CHECK (jsonb_typeof(\"evaluation\") = 'object'),\n CONSTRAINT \"page_index_findability_document_version_ck\"\n CHECK (\"document_version\" >= 1)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"page_index_findability_generation_evaluator_uq\"\n ON \"page_index_findability_evaluations\" (\n \"tenant_id\", \"knowledge_space_id\", \"publication_generation_id\", \"evaluator_version\"\n );\nCREATE INDEX IF NOT EXISTS \"page_index_findability_route_idx\"\n ON \"page_index_findability_evaluations\" (\n \"tenant_id\", \"knowledge_space_id\", \"document_asset_id\", \"publication_generation_id\"\n );\nCREATE INDEX IF NOT EXISTS \"page_index_findability_repair_queue_idx\"\n ON \"page_index_findability_evaluations\" (\n \"summary_repair_state\", \"available_at\", \"lease_expires_at\", \"updated_at\", \"id\"\n );\n", path: "packages/database/migrations/0036_page_index_findability.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0036_page_index_findability\n-- Dialect: tidb\n-- Persists generation-scoped PageIndex navigation quality and a bounded summary-repair request.\n\nCREATE TABLE IF NOT EXISTS `page_index_findability_evaluations` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `document_version` BIGINT NOT NULL,\n `outline_id` CHAR(36) NOT NULL,\n `publication_generation_id` CHAR(36) NOT NULL,\n `publication_fingerprint` VARCHAR(96) NOT NULL,\n `compilation_attempt_id` CHAR(36) NOT NULL,\n `evaluator_version` VARCHAR(128) NOT NULL,\n `status` VARCHAR(32) NOT NULL,\n `recommended_route` VARCHAR(16) NOT NULL,\n `evaluation` JSON NOT NULL,\n `summary_repair_state` VARCHAR(24) NOT NULL,\n `summary_repair_attempts` INT NOT NULL DEFAULT 0,\n `summary_repair_error` VARCHAR(2000),\n `available_at` DATETIME(3),\n `lock_token` CHAR(36),\n `locked_by` VARCHAR(255),\n `lease_expires_at` DATETIME(3),\n `evaluated_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `page_index_findability_scope_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n CONSTRAINT `page_index_findability_status_ck`\n CHECK (`status` IN ('failed', 'not-evaluated', 'passed')),\n CONSTRAINT `page_index_findability_route_ck`\n CHECK (`recommended_route` IN ('hybrid', 'layered', 'unchanged')),\n CONSTRAINT `page_index_findability_repair_ck`\n CHECK (\n `summary_repair_state` IN ('not-requested', 'queued', 'leased', 'dispatched', 'failed')\n AND `summary_repair_attempts` >= 0\n AND (\n (`summary_repair_state` = 'leased'\n AND `lock_token` IS NOT NULL\n AND `locked_by` IS NOT NULL\n AND `lease_expires_at` IS NOT NULL)\n OR\n (`summary_repair_state` <> 'leased'\n AND `lock_token` IS NULL\n AND `locked_by` IS NULL\n AND `lease_expires_at` IS NULL)\n )\n ),\n CONSTRAINT `page_index_findability_evaluation_ck`\n CHECK (JSON_TYPE(`evaluation`) = 'OBJECT'),\n CONSTRAINT `page_index_findability_document_version_ck`\n CHECK (`document_version` >= 1)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `page_index_findability_generation_evaluator_uq`\n ON `page_index_findability_evaluations` (\n `tenant_id`, `knowledge_space_id`, `publication_generation_id`, `evaluator_version`\n );\nCREATE INDEX IF NOT EXISTS `page_index_findability_route_idx`\n ON `page_index_findability_evaluations` (\n `tenant_id`, `knowledge_space_id`, `document_asset_id`, `publication_generation_id`\n );\nCREATE INDEX IF NOT EXISTS `page_index_findability_repair_queue_idx`\n ON `page_index_findability_evaluations` (\n `summary_repair_state`, `available_at`, `lease_expires_at`, `updated_at`, `id`\n );\n", path: "packages/database/migrations/0036_page_index_findability.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0037_logical_document_zero_revision_deletion\n-- Dialect: postgres\n\n-- A logical document starts at row_version 0 and can fail before its first revision is activated.\n-- Durable deletion already accepts that CAS value; keep other target revisions strictly positive.\nALTER TABLE \"deletion_jobs\"\n DROP CONSTRAINT IF EXISTS \"deletion_jobs_positive_ck\";\nALTER TABLE \"deletion_jobs\"\n ADD CONSTRAINT \"deletion_jobs_positive_ck\" CHECK (\n (\n (\"target_type\" = 'logical_document' AND \"target_revision\" >= 0)\n OR (\"target_type\" <> 'logical_document' AND \"target_revision\" >= 1)\n )\n AND (\"capability_grant_id\" IS NOT NULL OR \"permission_snapshot_revision\" >= 1)\n AND \"row_version\" >= 1\n AND \"execution_attempts\" >= 0\n AND \"max_execution_attempts\" >= 1\n AND \"execution_attempts\" <= \"max_execution_attempts\"\n AND (\"active_slot\" IS NULL OR \"active_slot\" = 1)\n );\n\nALTER TABLE \"deletion_tombstones\"\n DROP CONSTRAINT IF EXISTS \"deletion_tombstones_positive_ck\";\nALTER TABLE \"deletion_tombstones\"\n ADD CONSTRAINT \"deletion_tombstones_positive_ck\" CHECK (\n (\n (\"target_type\" = 'logical_document' AND \"target_revision\" >= 0)\n OR (\"target_type\" <> 'logical_document' AND \"target_revision\" >= 1)\n )\n AND \"row_version\" >= 1\n );\n", path: "packages/database/migrations/0037_logical_document_zero_revision_deletion.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0037_logical_document_zero_revision_deletion\n-- Dialect: tidb\n\n-- A logical document starts at row_version 0 and can fail before its first revision is activated.\n-- Durable deletion already accepts that CAS value; keep other target revisions strictly positive.\n-- TiDB DDL may commit before the migration marker. Guard each independent schema change so a\n-- crash after either DROP can resume with the ADD, and marker loss after both ADDs can replay.\nSET @kfs_0037_deletion_jobs_positive_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_jobs'\n AND constraint_name = 'deletion_jobs_positive_ck'\n ),\n 'ALTER TABLE `deletion_jobs` DROP CHECK `deletion_jobs_positive_ck`',\n 'DO 0'\n);\nPREPARE kfs_0037_deletion_jobs_positive_stmt\n FROM @kfs_0037_deletion_jobs_positive_sql;\nEXECUTE kfs_0037_deletion_jobs_positive_stmt;\nDEALLOCATE PREPARE kfs_0037_deletion_jobs_positive_stmt;\n\nSET @kfs_0037_deletion_jobs_positive_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_jobs'\n AND constraint_name = 'deletion_jobs_positive_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `deletion_jobs` ADD CONSTRAINT `deletion_jobs_positive_ck` CHECK (((`target_type` = ''logical_document'' AND `target_revision` >= 0) OR (`target_type` <> ''logical_document'' AND `target_revision` >= 1)) AND (`capability_grant_id` IS NOT NULL OR `permission_snapshot_revision` >= 1) AND `row_version` >= 1 AND `execution_attempts` >= 0 AND `max_execution_attempts` >= 1 AND `execution_attempts` <= `max_execution_attempts` AND (`active_slot` IS NULL OR `active_slot` = 1))'\n);\nPREPARE kfs_0037_deletion_jobs_positive_stmt\n FROM @kfs_0037_deletion_jobs_positive_sql;\nEXECUTE kfs_0037_deletion_jobs_positive_stmt;\nDEALLOCATE PREPARE kfs_0037_deletion_jobs_positive_stmt;\n\nSET @kfs_0037_deletion_tombstones_positive_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_tombstones'\n AND constraint_name = 'deletion_tombstones_positive_ck'\n ),\n 'ALTER TABLE `deletion_tombstones` DROP CHECK `deletion_tombstones_positive_ck`',\n 'DO 0'\n);\nPREPARE kfs_0037_deletion_tombstones_positive_stmt\n FROM @kfs_0037_deletion_tombstones_positive_sql;\nEXECUTE kfs_0037_deletion_tombstones_positive_stmt;\nDEALLOCATE PREPARE kfs_0037_deletion_tombstones_positive_stmt;\n\nSET @kfs_0037_deletion_tombstones_positive_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_tombstones'\n AND constraint_name = 'deletion_tombstones_positive_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `deletion_tombstones` ADD CONSTRAINT `deletion_tombstones_positive_ck` CHECK (((`target_type` = ''logical_document'' AND `target_revision` >= 0) OR (`target_type` <> ''logical_document'' AND `target_revision` >= 1)) AND `row_version` >= 1)'\n);\nPREPARE kfs_0037_deletion_tombstones_positive_stmt\n FROM @kfs_0037_deletion_tombstones_positive_sql;\nEXECUTE kfs_0037_deletion_tombstones_positive_stmt;\nDEALLOCATE PREPARE kfs_0037_deletion_tombstones_positive_stmt;\n", path: "packages/database/migrations/0037_logical_document_zero_revision_deletion.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0038_document_outline_summary_checkpoints\n-- Dialect: postgres\n-- Persists exact-input outline summary batches so timed-out compilations resume completed LLM work.\n\nCREATE TABLE IF NOT EXISTS \"document_outline_summary_checkpoints\" (\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"document_version\" INTEGER NOT NULL,\n \"publication_generation_id\" UUID NOT NULL,\n \"outline_node_id\" VARCHAR(255) NOT NULL,\n \"input_fingerprint\" VARCHAR(71) NOT NULL,\n \"summary\" TEXT NOT NULL,\n \"metadata\" JSONB NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n PRIMARY KEY (\n \"tenant_id\", \"knowledge_space_id\", \"publication_generation_id\",\n \"outline_node_id\", \"input_fingerprint\"\n ),\n CONSTRAINT \"document_outline_summary_checkpoints_scope_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"document_outline_summary_checkpoints_asset_fk\"\n FOREIGN KEY (\"knowledge_space_id\", \"document_asset_id\", \"document_version\")\n REFERENCES \"document_assets\" (\"knowledge_space_id\", \"id\", \"version\") ON DELETE CASCADE,\n CONSTRAINT \"document_outline_summary_checkpoints_fingerprint_ck\"\n CHECK (\"input_fingerprint\" ~ '^sha256:[a-f0-9]{64}$'),\n CONSTRAINT \"document_outline_summary_checkpoints_metadata_ck\"\n CHECK (jsonb_typeof(\"metadata\") = 'object'),\n CONSTRAINT \"document_outline_summary_checkpoints_version_ck\"\n CHECK (\"document_version\" >= 1)\n);\n\nCREATE INDEX IF NOT EXISTS \"document_outline_summary_checkpoints_asset_idx\"\n ON \"document_outline_summary_checkpoints\" (\n \"knowledge_space_id\", \"document_asset_id\", \"document_version\"\n );\n", path: "packages/database/migrations/0038_document_outline_summary_checkpoints.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0038_document_outline_summary_checkpoints\n-- Dialect: tidb\n-- Persists exact-input outline summary batches so timed-out compilations resume completed LLM work.\n\nCREATE TABLE IF NOT EXISTS `document_outline_summary_checkpoints` (\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `document_version` INT NOT NULL,\n `publication_generation_id` CHAR(36) NOT NULL,\n `outline_node_id` VARCHAR(255) NOT NULL,\n `input_fingerprint` VARCHAR(71) NOT NULL,\n `summary` TEXT NOT NULL,\n `metadata` JSON NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n PRIMARY KEY (\n `tenant_id`, `knowledge_space_id`, `publication_generation_id`,\n `outline_node_id`, `input_fingerprint`\n ),\n CONSTRAINT `document_outline_summary_checkpoints_scope_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n CONSTRAINT `document_outline_summary_checkpoints_asset_fk`\n FOREIGN KEY (`knowledge_space_id`, `document_asset_id`, `document_version`)\n REFERENCES `document_assets` (`knowledge_space_id`, `id`, `version`) ON DELETE CASCADE,\n CONSTRAINT `document_outline_summary_checkpoints_fingerprint_ck`\n CHECK (`input_fingerprint` REGEXP '^sha256:[a-f0-9]{64}$'),\n CONSTRAINT `document_outline_summary_checkpoints_metadata_ck`\n CHECK (JSON_TYPE(`metadata`) = 'OBJECT'),\n CONSTRAINT `document_outline_summary_checkpoints_version_ck`\n CHECK (`document_version` >= 1)\n);\n\nCREATE INDEX IF NOT EXISTS `document_outline_summary_checkpoints_asset_idx`\n ON `document_outline_summary_checkpoints` (\n `knowledge_space_id`, `document_asset_id`, `document_version`\n );\n", path: "packages/database/migrations/0038_document_outline_summary_checkpoints.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0039_document_semantic_enrichment\n-- Dialect: postgres\n-- Moves optional graph extraction behind searchable publication with resumable node checkpoints.\n\nCREATE TABLE IF NOT EXISTS \"document_semantic_enrichment_jobs\" (\n \"id\" UUID PRIMARY KEY,\n \"compilation_attempt_id\" UUID NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"document_version\" INTEGER NOT NULL,\n \"parse_artifact_id\" UUID NOT NULL,\n \"publication_generation_id\" UUID NOT NULL,\n \"base_head_revision\" BIGINT NOT NULL,\n \"retrieval_profile\" JSONB NOT NULL,\n \"run_state\" VARCHAR(24) NOT NULL,\n \"execution_attempts\" INTEGER NOT NULL DEFAULT 0,\n \"max_execution_attempts\" INTEGER NOT NULL,\n \"available_at\" TIMESTAMPTZ NOT NULL,\n \"worker_id\" VARCHAR(255),\n \"lease_token\" UUID,\n \"lease_expires_at\" TIMESTAMPTZ,\n \"heartbeat_at\" TIMESTAMPTZ,\n \"last_error_code\" VARCHAR(128),\n \"last_error_message\" VARCHAR(2000),\n \"result\" JSONB NOT NULL DEFAULT '{}'::jsonb,\n \"row_version\" INTEGER NOT NULL DEFAULT 0,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n \"completed_at\" TIMESTAMPTZ,\n CONSTRAINT \"document_semantic_enrichment_jobs_scope_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"document_semantic_enrichment_jobs_asset_fk\"\n FOREIGN KEY (\"knowledge_space_id\", \"document_asset_id\", \"document_version\")\n REFERENCES \"document_assets\" (\"knowledge_space_id\", \"id\", \"version\") ON DELETE CASCADE,\n CONSTRAINT \"document_semantic_enrichment_jobs_attempt_fk\"\n FOREIGN KEY (\"compilation_attempt_id\")\n REFERENCES \"document_compilation_attempts\" (\"id\") ON DELETE CASCADE,\n CONSTRAINT \"document_semantic_enrichment_jobs_generation_uq\"\n UNIQUE (\"tenant_id\", \"knowledge_space_id\", \"publication_generation_id\"),\n CONSTRAINT \"document_semantic_enrichment_jobs_state_ck\"\n CHECK (\"run_state\" IN ('queued', 'running', 'retry_wait', 'succeeded', 'failed', 'superseded')),\n CONSTRAINT \"document_semantic_enrichment_jobs_counts_ck\"\n CHECK (\"document_version\" >= 1 AND \"base_head_revision\" >= 0 AND \"execution_attempts\" >= 0 AND \"max_execution_attempts\" >= 1 AND \"execution_attempts\" <= \"max_execution_attempts\" AND \"row_version\" >= 0),\n CONSTRAINT \"document_semantic_enrichment_jobs_json_ck\"\n CHECK (jsonb_typeof(\"retrieval_profile\") = 'object' AND jsonb_typeof(\"result\") = 'object'),\n CONSTRAINT \"document_semantic_enrichment_jobs_lease_ck\"\n CHECK ((\"run_state\" = 'running' AND \"worker_id\" IS NOT NULL AND \"lease_token\" IS NOT NULL AND \"lease_expires_at\" IS NOT NULL AND \"heartbeat_at\" IS NOT NULL AND \"completed_at\" IS NULL) OR (\"run_state\" <> 'running' AND \"worker_id\" IS NULL AND \"lease_token\" IS NULL AND \"lease_expires_at\" IS NULL AND \"heartbeat_at\" IS NULL)),\n CONSTRAINT \"document_semantic_enrichment_jobs_terminal_ck\"\n CHECK ((\"run_state\" IN ('succeeded', 'failed', 'superseded') AND \"completed_at\" IS NOT NULL) OR (\"run_state\" IN ('queued', 'running', 'retry_wait') AND \"completed_at\" IS NULL))\n);\n\nCREATE INDEX IF NOT EXISTS \"document_semantic_enrichment_jobs_claim_idx\"\n ON \"document_semantic_enrichment_jobs\" (\"run_state\", \"available_at\", \"lease_expires_at\", \"updated_at\", \"id\");\n\nCREATE INDEX IF NOT EXISTS \"document_semantic_enrichment_jobs_asset_idx\"\n ON \"document_semantic_enrichment_jobs\" (\"knowledge_space_id\", \"document_asset_id\", \"document_version\");\n\nCREATE TABLE IF NOT EXISTS \"document_semantic_extraction_checkpoints\" (\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"document_version\" INTEGER NOT NULL,\n \"publication_generation_id\" UUID NOT NULL,\n \"node_id\" UUID NOT NULL,\n \"stage\" VARCHAR(16) NOT NULL,\n \"input_fingerprint\" VARCHAR(71) NOT NULL,\n \"result\" JSONB NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n PRIMARY KEY (\"tenant_id\", \"knowledge_space_id\", \"publication_generation_id\", \"node_id\", \"stage\", \"input_fingerprint\"),\n CONSTRAINT \"document_semantic_extraction_checkpoints_scope_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"document_semantic_extraction_checkpoints_asset_fk\"\n FOREIGN KEY (\"knowledge_space_id\", \"document_asset_id\", \"document_version\")\n REFERENCES \"document_assets\" (\"knowledge_space_id\", \"id\", \"version\") ON DELETE CASCADE,\n CONSTRAINT \"document_semantic_extraction_checkpoints_stage_ck\"\n CHECK (\"stage\" IN ('entity', 'relation')),\n CONSTRAINT \"document_semantic_extraction_checkpoints_fingerprint_ck\"\n CHECK (\"input_fingerprint\" ~ '^sha256:[a-f0-9]{64}$'),\n CONSTRAINT \"document_semantic_extraction_checkpoints_result_ck\"\n CHECK (jsonb_typeof(\"result\") = 'object')\n);\n\nCREATE INDEX IF NOT EXISTS \"document_semantic_extraction_checkpoints_asset_idx\"\n ON \"document_semantic_extraction_checkpoints\" (\"knowledge_space_id\", \"document_asset_id\", \"document_version\");\n", path: "packages/database/migrations/0039_document_semantic_enrichment.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0039_document_semantic_enrichment\n-- Dialect: tidb\n-- Moves optional graph extraction behind searchable publication with resumable node checkpoints.\n\nCREATE TABLE IF NOT EXISTS `document_semantic_enrichment_jobs` (\n `id` CHAR(36) PRIMARY KEY,\n `compilation_attempt_id` CHAR(36) NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `document_version` INT NOT NULL,\n `parse_artifact_id` CHAR(36) NOT NULL,\n `publication_generation_id` CHAR(36) NOT NULL,\n `base_head_revision` BIGINT NOT NULL,\n `retrieval_profile` JSON NOT NULL,\n `run_state` VARCHAR(24) NOT NULL,\n `execution_attempts` INT NOT NULL DEFAULT 0,\n `max_execution_attempts` INT NOT NULL,\n `available_at` DATETIME(3) NOT NULL,\n `worker_id` VARCHAR(255),\n `lease_token` CHAR(36),\n `lease_expires_at` DATETIME(3),\n `heartbeat_at` DATETIME(3),\n `last_error_code` VARCHAR(128),\n `last_error_message` VARCHAR(2000),\n `result` JSON NOT NULL,\n `row_version` INT NOT NULL DEFAULT 0,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n `completed_at` DATETIME(3),\n CONSTRAINT `document_semantic_enrichment_jobs_scope_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n CONSTRAINT `document_semantic_enrichment_jobs_asset_fk`\n FOREIGN KEY (`knowledge_space_id`, `document_asset_id`, `document_version`)\n REFERENCES `document_assets` (`knowledge_space_id`, `id`, `version`) ON DELETE CASCADE,\n CONSTRAINT `document_semantic_enrichment_jobs_attempt_fk`\n FOREIGN KEY (`compilation_attempt_id`)\n REFERENCES `document_compilation_attempts` (`id`) ON DELETE CASCADE,\n CONSTRAINT `document_semantic_enrichment_jobs_generation_uq`\n UNIQUE (`tenant_id`, `knowledge_space_id`, `publication_generation_id`),\n CONSTRAINT `document_semantic_enrichment_jobs_state_ck`\n CHECK (`run_state` IN ('queued', 'running', 'retry_wait', 'succeeded', 'failed', 'superseded')),\n CONSTRAINT `document_semantic_enrichment_jobs_counts_ck`\n CHECK (`document_version` >= 1 AND `base_head_revision` >= 0 AND `execution_attempts` >= 0 AND `max_execution_attempts` >= 1 AND `execution_attempts` <= `max_execution_attempts` AND `row_version` >= 0),\n CONSTRAINT `document_semantic_enrichment_jobs_json_ck`\n CHECK (JSON_TYPE(`retrieval_profile`) = 'OBJECT' AND JSON_TYPE(`result`) = 'OBJECT'),\n CONSTRAINT `document_semantic_enrichment_jobs_lease_ck`\n CHECK ((`run_state` = 'running' AND `worker_id` IS NOT NULL AND `lease_token` IS NOT NULL AND `lease_expires_at` IS NOT NULL AND `heartbeat_at` IS NOT NULL AND `completed_at` IS NULL) OR (`run_state` <> 'running' AND `worker_id` IS NULL AND `lease_token` IS NULL AND `lease_expires_at` IS NULL AND `heartbeat_at` IS NULL)),\n CONSTRAINT `document_semantic_enrichment_jobs_terminal_ck`\n CHECK ((`run_state` IN ('succeeded', 'failed', 'superseded') AND `completed_at` IS NOT NULL) OR (`run_state` IN ('queued', 'running', 'retry_wait') AND `completed_at` IS NULL))\n);\n\nCREATE INDEX IF NOT EXISTS `document_semantic_enrichment_jobs_claim_idx`\n ON `document_semantic_enrichment_jobs` (`run_state`, `available_at`, `lease_expires_at`, `updated_at`, `id`);\n\nCREATE INDEX IF NOT EXISTS `document_semantic_enrichment_jobs_asset_idx`\n ON `document_semantic_enrichment_jobs` (`knowledge_space_id`, `document_asset_id`, `document_version`);\n\nCREATE TABLE IF NOT EXISTS `document_semantic_extraction_checkpoints` (\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `document_version` INT NOT NULL,\n `publication_generation_id` CHAR(36) NOT NULL,\n `node_id` CHAR(36) NOT NULL,\n `stage` VARCHAR(16) NOT NULL,\n `input_fingerprint` VARCHAR(71) NOT NULL,\n `result` JSON NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n PRIMARY KEY (`tenant_id`, `knowledge_space_id`, `publication_generation_id`, `node_id`, `stage`, `input_fingerprint`),\n CONSTRAINT `document_semantic_extraction_checkpoints_scope_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n CONSTRAINT `document_semantic_extraction_checkpoints_asset_fk`\n FOREIGN KEY (`knowledge_space_id`, `document_asset_id`, `document_version`)\n REFERENCES `document_assets` (`knowledge_space_id`, `id`, `version`) ON DELETE CASCADE,\n CONSTRAINT `document_semantic_extraction_checkpoints_stage_ck`\n CHECK (`stage` IN ('entity', 'relation')),\n CONSTRAINT `document_semantic_extraction_checkpoints_fingerprint_ck`\n CHECK (`input_fingerprint` REGEXP '^sha256:[a-f0-9]{64}$'),\n CONSTRAINT `document_semantic_extraction_checkpoints_result_ck`\n CHECK (JSON_TYPE(`result`) = 'OBJECT')\n);\n\nCREATE INDEX IF NOT EXISTS `document_semantic_extraction_checkpoints_asset_idx`\n ON `document_semantic_extraction_checkpoints` (`knowledge_space_id`, `document_asset_id`, `document_version`);\n", path: "packages/database/migrations/0039_document_semantic_enrichment.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0040_knowledge_space_metadata\n-- Dialect: postgres\n-- Adds a durable metadata-field catalog and document bindings without copying metadata values.\n\nCREATE TABLE IF NOT EXISTS \"knowledge_space_metadata_fields\" (\n \"id\" UUID PRIMARY KEY,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"name\" VARCHAR(255) NOT NULL,\n \"type\" VARCHAR(16) NOT NULL,\n \"row_version\" INTEGER NOT NULL DEFAULT 0,\n \"created_by_subject_id\" VARCHAR(255) NOT NULL,\n \"updated_by_subject_id\" VARCHAR(255),\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"knowledge_space_metadata_fields_scope_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"knowledge_space_metadata_fields_scope_id_uq\"\n UNIQUE (\"tenant_id\", \"knowledge_space_id\", \"id\"),\n CONSTRAINT \"knowledge_space_metadata_fields_name_uq\"\n UNIQUE (\"tenant_id\", \"knowledge_space_id\", \"name\"),\n CONSTRAINT \"knowledge_space_metadata_fields_type_ck\"\n CHECK (\"type\" IN ('string', 'number', 'time')),\n CONSTRAINT \"knowledge_space_metadata_fields_row_version_ck\"\n CHECK (\"row_version\" >= 0)\n);\n\nCREATE INDEX IF NOT EXISTS \"knowledge_space_metadata_fields_cursor_idx\"\n ON \"knowledge_space_metadata_fields\" (\"tenant_id\", \"knowledge_space_id\", \"name\", \"id\");\n\nCREATE TABLE IF NOT EXISTS \"logical_document_metadata_bindings\" (\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_id\" UUID NOT NULL,\n \"metadata_field_id\" UUID NOT NULL,\n \"created_by_subject_id\" VARCHAR(255) NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n PRIMARY KEY (\"tenant_id\", \"knowledge_space_id\", \"document_id\", \"metadata_field_id\"),\n CONSTRAINT \"logical_document_metadata_bindings_field_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"metadata_field_id\")\n REFERENCES \"knowledge_space_metadata_fields\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE CASCADE,\n CONSTRAINT \"logical_document_metadata_bindings_document_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"document_id\")\n REFERENCES \"logical_documents\" (\"tenant_id\", \"knowledge_space_id\", \"id\")\n ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS \"logical_document_metadata_bindings_field_idx\"\n ON \"logical_document_metadata_bindings\"\n (\"tenant_id\", \"knowledge_space_id\", \"metadata_field_id\", \"document_id\");\n\n-- Existing KnowledgeFS documents predate the field catalog. Only consistently typed, valid custom\n-- names are admitted; mixed/object values remain in user_metadata but are not exposed as fields.\nWITH metadata_values AS (\n SELECT\n document.\"tenant_id\",\n document.\"knowledge_space_id\",\n entry.key AS name,\n CASE\n WHEN jsonb_typeof(entry.value) = 'number' THEN 'number'\n WHEN jsonb_typeof(entry.value) = 'string'\n AND (entry.value #>> '{}') ~ '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$'\n THEN 'time'\n WHEN jsonb_typeof(entry.value) = 'string' THEN 'string'\n ELSE NULL\n END AS metadata_type\n FROM \"logical_documents\" document\n CROSS JOIN LATERAL jsonb_each(document.\"user_metadata\") entry\n WHERE entry.key ~ '^[a-z][a-z0-9_]{0,254}$'\n AND entry.key NOT IN ('provenance', 'system')\n AND jsonb_typeof(entry.value) <> 'null'\n), valid_fields AS (\n SELECT \"tenant_id\", \"knowledge_space_id\", name, MIN(metadata_type) AS metadata_type\n FROM metadata_values\n GROUP BY \"tenant_id\", \"knowledge_space_id\", name\n HAVING COUNT(*) FILTER (WHERE metadata_type IS NULL) = 0\n AND COUNT(DISTINCT metadata_type) = 1\n), ranked_fields AS (\n SELECT\n \"tenant_id\",\n \"knowledge_space_id\",\n name,\n metadata_type,\n ROW_NUMBER() OVER (\n PARTITION BY \"tenant_id\", \"knowledge_space_id\"\n ORDER BY name\n ) AS field_rank\n FROM valid_fields\n), deterministic_fields AS (\n SELECT\n \"tenant_id\",\n \"knowledge_space_id\",\n name,\n metadata_type,\n md5(\"tenant_id\" || ':' || \"knowledge_space_id\"::text || ':' || name) AS digest\n FROM ranked_fields\n WHERE field_rank <= 100\n)\nINSERT INTO \"knowledge_space_metadata_fields\" (\n \"id\", \"tenant_id\", \"knowledge_space_id\", \"name\", \"type\", \"row_version\",\n \"created_by_subject_id\", \"updated_by_subject_id\", \"created_at\", \"updated_at\"\n)\nSELECT\n (substr(digest, 1, 8) || '-' || substr(digest, 9, 4) || '-5' || substr(digest, 14, 3) ||\n '-a' || substr(digest, 18, 3) || '-' || substr(digest, 21, 12))::uuid,\n \"tenant_id\", \"knowledge_space_id\", name, metadata_type, 0,\n 'migration:0040', NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP\nFROM deterministic_fields\nON CONFLICT (\"tenant_id\", \"knowledge_space_id\", \"name\") DO NOTHING;\n\nINSERT INTO \"logical_document_metadata_bindings\" (\n \"tenant_id\", \"knowledge_space_id\", \"document_id\", \"metadata_field_id\",\n \"created_by_subject_id\", \"created_at\"\n)\nSELECT\n document.\"tenant_id\", document.\"knowledge_space_id\", document.\"id\", field.\"id\",\n 'migration:0040', CURRENT_TIMESTAMP\nFROM \"logical_documents\" document\nJOIN \"knowledge_space_metadata_fields\" field\n ON field.\"tenant_id\" = document.\"tenant_id\"\n AND field.\"knowledge_space_id\" = document.\"knowledge_space_id\"\n AND document.\"user_metadata\" ? field.\"name\"\n AND jsonb_typeof(document.\"user_metadata\" -> field.\"name\") <> 'null'\nON CONFLICT DO NOTHING;\n", path: "packages/database/migrations/0040_knowledge_space_metadata.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0040_knowledge_space_metadata\n-- Dialect: tidb\n-- Adds a durable metadata-field catalog and document bindings without copying metadata values.\n\nCREATE TABLE IF NOT EXISTS `knowledge_space_metadata_fields` (\n `id` CHAR(36) PRIMARY KEY,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `name` VARCHAR(255) NOT NULL,\n `type` VARCHAR(16) NOT NULL,\n `row_version` INT NOT NULL DEFAULT 0,\n `created_by_subject_id` VARCHAR(255) NOT NULL,\n `updated_by_subject_id` VARCHAR(255),\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `knowledge_space_metadata_fields_scope_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n CONSTRAINT `knowledge_space_metadata_fields_scope_id_uq`\n UNIQUE (`tenant_id`, `knowledge_space_id`, `id`),\n CONSTRAINT `knowledge_space_metadata_fields_name_uq`\n UNIQUE (`tenant_id`, `knowledge_space_id`, `name`),\n CONSTRAINT `knowledge_space_metadata_fields_type_ck`\n CHECK (`type` IN ('string', 'number', 'time')),\n CONSTRAINT `knowledge_space_metadata_fields_row_version_ck`\n CHECK (`row_version` >= 0)\n);\n\nCREATE INDEX IF NOT EXISTS `knowledge_space_metadata_fields_cursor_idx`\n ON `knowledge_space_metadata_fields` (`tenant_id`, `knowledge_space_id`, `name`, `id`);\n\nCREATE TABLE IF NOT EXISTS `logical_document_metadata_bindings` (\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_id` CHAR(36) NOT NULL,\n `metadata_field_id` CHAR(36) NOT NULL,\n `created_by_subject_id` VARCHAR(255) NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n PRIMARY KEY (`tenant_id`, `knowledge_space_id`, `document_id`, `metadata_field_id`),\n CONSTRAINT `logical_document_metadata_bindings_field_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `metadata_field_id`)\n REFERENCES `knowledge_space_metadata_fields` (`tenant_id`, `knowledge_space_id`, `id`)\n ON DELETE CASCADE,\n CONSTRAINT `logical_document_metadata_bindings_document_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `document_id`)\n REFERENCES `logical_documents` (`tenant_id`, `knowledge_space_id`, `id`)\n ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS `logical_document_metadata_bindings_field_idx`\n ON `logical_document_metadata_bindings`\n (`tenant_id`, `knowledge_space_id`, `metadata_field_id`, `document_id`);\n\nINSERT IGNORE INTO `knowledge_space_metadata_fields` (\n `id`, `tenant_id`, `knowledge_space_id`, `name`, `type`, `row_version`,\n `created_by_subject_id`, `updated_by_subject_id`, `created_at`, `updated_at`\n)\nSELECT\n CONCAT(SUBSTRING(ranked.digest, 1, 8), '-', SUBSTRING(ranked.digest, 9, 4), '-5',\n SUBSTRING(ranked.digest, 14, 3), '-a', SUBSTRING(ranked.digest, 18, 3), '-',\n SUBSTRING(ranked.digest, 21, 12)),\n ranked.tenant_id, ranked.knowledge_space_id, ranked.name, ranked.metadata_type, 0,\n 'migration:0040', NULL, CURRENT_TIMESTAMP(3), CURRENT_TIMESTAMP(3)\nFROM (\n SELECT\n valid_fields.*,\n ROW_NUMBER() OVER (\n PARTITION BY valid_fields.tenant_id, valid_fields.knowledge_space_id\n ORDER BY valid_fields.name\n ) AS field_rank\n FROM (\n SELECT\n grouped.tenant_id,\n grouped.knowledge_space_id,\n grouped.name,\n grouped.digest,\n MIN(grouped.metadata_type) AS metadata_type\n FROM (\n SELECT\n document.tenant_id,\n document.knowledge_space_id,\n metadata_key.name,\n MD5(CONCAT(document.tenant_id, ':', document.knowledge_space_id, ':', metadata_key.name)) AS digest,\n CASE\n WHEN JSON_TYPE(JSON_EXTRACT(document.user_metadata, CONCAT('$.\"', metadata_key.name, '\"')))\n IN ('INTEGER', 'DOUBLE', 'DECIMAL') THEN 'number'\n WHEN JSON_TYPE(JSON_EXTRACT(document.user_metadata, CONCAT('$.\"', metadata_key.name, '\"'))) = 'STRING'\n AND JSON_UNQUOTE(JSON_EXTRACT(document.user_metadata, CONCAT('$.\"', metadata_key.name, '\"')))\n REGEXP '^\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}(\\\\.\\\\d+)?Z$' THEN 'time'\n WHEN JSON_TYPE(JSON_EXTRACT(document.user_metadata, CONCAT('$.\"', metadata_key.name, '\"'))) = 'STRING'\n THEN 'string'\n ELSE NULL\n END AS metadata_type\n FROM `logical_documents` document\n JOIN JSON_TABLE(\n JSON_KEYS(document.user_metadata),\n '$[*]' COLUMNS (`name` VARCHAR(255) PATH '$')\n ) metadata_key ON TRUE\n WHERE metadata_key.name REGEXP '^[a-z][a-z0-9_]{0,254}$'\n AND metadata_key.name NOT IN ('provenance', 'system')\n AND JSON_TYPE(JSON_EXTRACT(document.user_metadata, CONCAT('$.\"', metadata_key.name, '\"'))) <> 'NULL'\n ) grouped\n GROUP BY grouped.tenant_id, grouped.knowledge_space_id, grouped.name, grouped.digest\n HAVING SUM(grouped.metadata_type IS NULL) = 0 AND COUNT(DISTINCT grouped.metadata_type) = 1\n ) valid_fields\n) ranked\nWHERE ranked.field_rank <= 100;\n\nINSERT IGNORE INTO `logical_document_metadata_bindings` (\n `tenant_id`, `knowledge_space_id`, `document_id`, `metadata_field_id`,\n `created_by_subject_id`, `created_at`\n)\nSELECT\n document.tenant_id, document.knowledge_space_id, document.id, field.id,\n 'migration:0040', CURRENT_TIMESTAMP(3)\nFROM `logical_documents` document\nJOIN `knowledge_space_metadata_fields` field\n ON field.tenant_id = document.tenant_id\n AND field.knowledge_space_id = document.knowledge_space_id\n AND JSON_CONTAINS_PATH(document.user_metadata, 'one', CONCAT('$.\"', field.name, '\"')) = 1\n AND JSON_TYPE(JSON_EXTRACT(document.user_metadata, CONCAT('$.\"', field.name, '\"'))) <> 'NULL';\n", path: "packages/database/migrations/0040_knowledge_space_metadata.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0041_logical_document_availability\n-- Dialect: postgres\n-- Adds document-scoped availability without mutating source or physical index state.\n\nALTER TABLE \"logical_documents\"\n ADD COLUMN IF NOT EXISTS \"enabled\" BOOLEAN NOT NULL DEFAULT TRUE,\n ADD COLUMN IF NOT EXISTS \"disabled_at\" TIMESTAMP,\n ADD COLUMN IF NOT EXISTS \"disabled_by_subject_id\" VARCHAR(255);\n\nALTER TABLE \"logical_documents\"\n ADD CONSTRAINT \"logical_documents_availability_ck\"\n CHECK (\n (\"enabled\" AND \"disabled_at\" IS NULL AND \"disabled_by_subject_id\" IS NULL)\n OR (NOT \"enabled\" AND \"disabled_at\" IS NOT NULL AND \"disabled_by_subject_id\" IS NOT NULL)\n );\n", path: "packages/database/migrations/0041_logical_document_availability.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0041_logical_document_availability\n-- Dialect: tidb\n-- Adds document-scoped availability without mutating source or physical index state.\n\nALTER TABLE `logical_documents`\n ADD COLUMN IF NOT EXISTS `enabled` BOOLEAN NOT NULL DEFAULT TRUE,\n ADD COLUMN IF NOT EXISTS `disabled_at` TIMESTAMP NULL,\n ADD COLUMN IF NOT EXISTS `disabled_by_subject_id` VARCHAR(255) NULL;\n\nALTER TABLE `logical_documents`\n ADD CONSTRAINT `logical_documents_availability_ck`\n CHECK (\n (`enabled` AND `disabled_at` IS NULL AND `disabled_by_subject_id` IS NULL)\n OR (NOT `enabled` AND `disabled_at` IS NOT NULL AND `disabled_by_subject_id` IS NOT NULL)\n );\n", path: "packages/database/migrations/0041_logical_document_availability.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0042_workflow_failed_retrieval_capture\n-- Dialect: postgres\n-- Workflow empty-retrieval events retain their admitted Capability provenance and frozen scope.\n\nALTER TABLE \"failed_queries\"\n ADD COLUMN IF NOT EXISTS \"capability_grant_id\" UUID;\n\nALTER TABLE \"failed_queries\"\n DROP CONSTRAINT IF EXISTS \"failed_queries_permission_binding_ck\";\n\nALTER TABLE \"failed_queries\"\n ADD CONSTRAINT \"failed_queries_permission_binding_ck\" CHECK (\n (\"tenant_id\" IS NULL AND \"capability_grant_id\" IS NULL\n AND \"requested_by_subject_id\" IS NULL AND \"access_channel\" IS NULL\n AND \"permission_snapshot_id\" IS NULL AND \"permission_snapshot_revision\" IS NULL\n AND \"required_permission_scope\" IS NULL AND \"revision\" IS NULL)\n OR (\"tenant_id\" IS NOT NULL AND \"capability_grant_id\" IS NOT NULL\n AND \"requested_by_subject_id\" IS NULL AND \"access_channel\" IS NULL\n AND \"permission_snapshot_id\" IS NULL AND \"permission_snapshot_revision\" IS NULL\n AND \"required_permission_scope\" IS NOT NULL\n AND jsonb_typeof(\"required_permission_scope\") = 'array'\n AND \"revision\" IS NOT NULL AND \"revision\" >= 1)\n OR (\"tenant_id\" IS NOT NULL AND \"capability_grant_id\" IS NULL\n AND \"requested_by_subject_id\" IS NOT NULL\n AND \"access_channel\" IS NOT NULL\n AND \"access_channel\" IN ('interactive', 'service_api', 'mcp', 'agent')\n AND \"permission_snapshot_id\" IS NOT NULL\n AND \"permission_snapshot_revision\" IS NOT NULL\n AND \"permission_snapshot_revision\" >= 1\n AND \"required_permission_scope\" IS NOT NULL\n AND jsonb_typeof(\"required_permission_scope\") = 'array'\n AND \"revision\" IS NOT NULL AND \"revision\" >= 1)\n );\n\nDO $kfs_0042_failed_query_capability_fk$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'failed_queries_capability_grant_fk'\n AND conrelid = 'failed_queries'::regclass\n ) THEN\n ALTER TABLE \"failed_queries\"\n ADD CONSTRAINT \"failed_queries_capability_grant_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\")\n REFERENCES \"capability_grants\" (\"tenant_id\", \"knowledge_space_id\", \"grant_id\")\n ON DELETE RESTRICT;\n END IF;\nEND\n$kfs_0042_failed_query_capability_fk$;\n\nCREATE INDEX IF NOT EXISTS \"failed_queries_capability_grant_idx\"\n ON \"failed_queries\" (\"tenant_id\", \"knowledge_space_id\", \"capability_grant_id\");\n", path: "packages/database/migrations/0042_workflow_failed_retrieval_capture.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0042_workflow_failed_retrieval_capture\n-- Dialect: tidb\n-- Workflow empty-retrieval events retain their admitted Capability provenance and frozen scope.\n\nALTER TABLE `failed_queries`\n ADD COLUMN IF NOT EXISTS `capability_grant_id` CHAR(36) NULL;\n\nSET @fq_0042_binding_ck_exists = (\n SELECT COUNT(*) FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'failed_queries'\n AND constraint_name = 'failed_queries_permission_binding_ck'\n);\nSET @fq_0042_binding_ck_drop_sql = IF(\n @fq_0042_binding_ck_exists > 0,\n 'ALTER TABLE `failed_queries` DROP CONSTRAINT `failed_queries_permission_binding_ck`',\n 'SELECT 1'\n);\nPREPARE fq_0042_binding_ck_drop_stmt FROM @fq_0042_binding_ck_drop_sql;\nEXECUTE fq_0042_binding_ck_drop_stmt;\nDEALLOCATE PREPARE fq_0042_binding_ck_drop_stmt;\n\nALTER TABLE `failed_queries`\n MODIFY COLUMN `permission_binding_complete` TINYINT GENERATED ALWAYS AS (\n CASE WHEN\n (`tenant_id` IS NULL AND `capability_grant_id` IS NULL\n AND `requested_by_subject_id` IS NULL AND `access_channel` IS NULL\n AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL\n AND `required_permission_scope` IS NULL AND `revision` IS NULL)\n OR (`tenant_id` IS NOT NULL AND `capability_grant_id` IS NOT NULL\n AND `requested_by_subject_id` IS NULL AND `access_channel` IS NULL\n AND `permission_snapshot_id` IS NULL AND `permission_snapshot_revision` IS NULL\n AND `required_permission_scope` IS NOT NULL\n AND JSON_TYPE(`required_permission_scope`) = 'ARRAY'\n AND `revision` IS NOT NULL AND `revision` >= 1)\n OR (`tenant_id` IS NOT NULL AND `capability_grant_id` IS NULL\n AND `requested_by_subject_id` IS NOT NULL\n AND `access_channel` IN ('interactive', 'service_api', 'mcp', 'agent')\n AND `permission_snapshot_id` IS NOT NULL\n AND `permission_snapshot_revision` IS NOT NULL\n AND `permission_snapshot_revision` >= 1\n AND `required_permission_scope` IS NOT NULL\n AND JSON_TYPE(`required_permission_scope`) = 'ARRAY'\n AND `revision` IS NOT NULL AND `revision` >= 1)\n THEN 1 ELSE 0\n END\n ) VIRTUAL;\n\nALTER TABLE `failed_queries`\n ADD CONSTRAINT `failed_queries_permission_binding_ck`\n CHECK (`permission_binding_complete` = 1);\n\nSET @fq_0042_capability_fk_exists = (\n SELECT COUNT(*) FROM information_schema.table_constraints\n WHERE table_schema = DATABASE()\n AND table_name = 'failed_queries'\n AND constraint_name = 'failed_queries_capability_grant_fk'\n);\nSET @fq_0042_capability_fk_sql = IF(\n @fq_0042_capability_fk_exists = 0,\n 'ALTER TABLE `failed_queries` ADD CONSTRAINT `failed_queries_capability_grant_fk` FOREIGN KEY (`tenant_id`, `knowledge_space_id`, `capability_grant_id`) REFERENCES `capability_grants` (`tenant_id`, `knowledge_space_id`, `grant_id`) ON DELETE RESTRICT',\n 'SELECT 1'\n);\nPREPARE fq_0042_capability_fk_stmt FROM @fq_0042_capability_fk_sql;\nEXECUTE fq_0042_capability_fk_stmt;\nDEALLOCATE PREPARE fq_0042_capability_fk_stmt;\n\nCREATE INDEX IF NOT EXISTS `failed_queries_capability_grant_idx`\n ON `failed_queries` (`tenant_id`, `knowledge_space_id`, `capability_grant_id`);\n", path: "packages/database/migrations/0042_workflow_failed_retrieval_capture.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0043_semantic_generation_receipts\n-- Dialect: postgres\n\nCREATE TABLE IF NOT EXISTS \"knowledge_node_generation_receipts\" (\n \"knowledge_space_id\" UUID NOT NULL,\n \"publication_generation_id\" UUID NOT NULL,\n \"parse_artifact_id\" UUID NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"artifact_hash\" VARCHAR(64) NOT NULL,\n \"document_chunk_count\" INTEGER NOT NULL,\n \"stored_node_count\" INTEGER NOT NULL,\n \"request_fingerprint\" VARCHAR(71) NOT NULL,\n \"response_fingerprint\" VARCHAR(71) NOT NULL,\n \"prompt_response_fingerprint\" VARCHAR(71) NOT NULL,\n \"receipt\" JSONB NOT NULL,\n PRIMARY KEY (\"knowledge_space_id\", \"publication_generation_id\", \"parse_artifact_id\"),\n CONSTRAINT \"knowledge_node_generation_receipts_counts_ck\" CHECK (\n \"document_chunk_count\" >= 0 AND \"stored_node_count\" >= 0\n AND \"stored_node_count\" <= \"document_chunk_count\"\n ),\n CONSTRAINT \"knowledge_node_generation_receipts_hashes_ck\" CHECK (\n \"artifact_hash\" ~ '^[a-f0-9]{64}$'\n AND \"request_fingerprint\" ~ '^sha256:[a-f0-9]{64}$'\n AND \"response_fingerprint\" ~ '^sha256:[a-f0-9]{64}$'\n AND \"prompt_response_fingerprint\" ~ '^sha256:[a-f0-9]{64}$'\n ),\n CONSTRAINT \"knowledge_node_generation_receipts_json_ck\"\n CHECK (jsonb_typeof(\"receipt\") = 'object'),\n CONSTRAINT \"knowledge_node_generation_receipts_bytes_ck\"\n CHECK (octet_length(\"receipt\"::text) <= 8388608),\n CONSTRAINT \"knowledge_node_generation_receipts_pub_gen_nonzero_ck\"\n CHECK (\"publication_generation_id\" <> '00000000-0000-0000-0000-000000000000'::uuid),\n FOREIGN KEY (\"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"id\") ON DELETE CASCADE,\n FOREIGN KEY (\"document_asset_id\")\n REFERENCES \"document_assets\" (\"id\") ON DELETE CASCADE,\n FOREIGN KEY (\"parse_artifact_id\")\n REFERENCES \"parse_artifacts\" (\"id\") ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS \"knowledge_node_generation_receipts_document_idx\"\n ON \"knowledge_node_generation_receipts\"\n (\"knowledge_space_id\", \"document_asset_id\", \"publication_generation_id\", \"parse_artifact_id\");\n", path: "packages/database/migrations/0043_semantic_generation_receipts.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0043_semantic_generation_receipts\n-- Dialect: tidb\n\nCREATE TABLE IF NOT EXISTS `knowledge_node_generation_receipts` (\n `knowledge_space_id` CHAR(36) NOT NULL,\n `publication_generation_id` CHAR(36) NOT NULL,\n `parse_artifact_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `artifact_hash` VARCHAR(64) NOT NULL,\n `document_chunk_count` INT NOT NULL,\n `stored_node_count` INT NOT NULL,\n `request_fingerprint` VARCHAR(71) NOT NULL,\n `response_fingerprint` VARCHAR(71) NOT NULL,\n `prompt_response_fingerprint` VARCHAR(71) NOT NULL,\n `receipt` JSON NOT NULL,\n PRIMARY KEY (`knowledge_space_id`, `publication_generation_id`, `parse_artifact_id`),\n CONSTRAINT `knowledge_node_generation_receipts_counts_ck` CHECK (\n `document_chunk_count` >= 0 AND `stored_node_count` >= 0\n AND `stored_node_count` <= `document_chunk_count`\n ),\n CONSTRAINT `knowledge_node_generation_receipts_hashes_ck` CHECK (\n `artifact_hash` REGEXP '^[a-f0-9]{64}$'\n AND `request_fingerprint` REGEXP '^sha256:[a-f0-9]{64}$'\n AND `response_fingerprint` REGEXP '^sha256:[a-f0-9]{64}$'\n AND `prompt_response_fingerprint` REGEXP '^sha256:[a-f0-9]{64}$'\n ),\n CONSTRAINT `knowledge_node_generation_receipts_json_ck`\n CHECK (JSON_TYPE(`receipt`) = 'OBJECT'),\n CONSTRAINT `knowledge_node_generation_receipts_bytes_ck`\n CHECK (OCTET_LENGTH(CAST(`receipt` AS CHAR)) <= 8388608),\n CONSTRAINT `knowledge_node_generation_receipts_pub_gen_nonzero_ck` CHECK (\n `publication_generation_id` REGEXP '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'\n AND `publication_generation_id` <> '00000000-0000-0000-0000-000000000000'\n ),\n FOREIGN KEY (`knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`id`) ON DELETE CASCADE,\n FOREIGN KEY (`document_asset_id`)\n REFERENCES `document_assets` (`id`) ON DELETE CASCADE,\n FOREIGN KEY (`parse_artifact_id`)\n REFERENCES `parse_artifacts` (`id`) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS `knowledge_node_generation_receipts_document_idx`\n ON `knowledge_node_generation_receipts`\n (`knowledge_space_id`, `document_asset_id`, `publication_generation_id`, `parse_artifact_id`);\n", path: "packages/database/migrations/0043_semantic_generation_receipts.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0044_document_semantic_window_checkpoints\n-- Dialect: postgres\n-- Persists validated semantic-window model responses for generation-scoped retry recovery.\n\nCREATE TABLE IF NOT EXISTS \"document_semantic_window_checkpoints\" (\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"document_version\" INTEGER NOT NULL,\n \"publication_generation_id\" UUID NOT NULL,\n \"window_id\" VARCHAR(128) NOT NULL,\n \"input_fingerprint\" VARCHAR(71) NOT NULL,\n \"model_fingerprint\" VARCHAR(71) NOT NULL,\n \"response_text\" TEXT NOT NULL,\n \"completion\" JSONB NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n PRIMARY KEY (\n \"tenant_id\", \"knowledge_space_id\", \"publication_generation_id\",\n \"window_id\", \"input_fingerprint\"\n ),\n CONSTRAINT \"document_semantic_window_checkpoints_scope_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"document_semantic_window_checkpoints_asset_fk\"\n FOREIGN KEY (\"knowledge_space_id\", \"document_asset_id\", \"document_version\")\n REFERENCES \"document_assets\" (\"knowledge_space_id\", \"id\", \"version\") ON DELETE CASCADE,\n CONSTRAINT \"document_semantic_window_checkpoints_fingerprint_ck\"\n CHECK (\n \"input_fingerprint\" ~ '^sha256:[a-f0-9]{64}$'\n AND \"model_fingerprint\" ~ '^sha256:[a-f0-9]{64}$'\n ),\n CONSTRAINT \"document_semantic_window_checkpoints_completion_ck\"\n CHECK (jsonb_typeof(\"completion\") = 'object'),\n CONSTRAINT \"document_semantic_window_checkpoints_version_ck\"\n CHECK (\"document_version\" >= 1)\n);\n\nCREATE INDEX IF NOT EXISTS \"document_semantic_window_checkpoints_asset_idx\"\n ON \"document_semantic_window_checkpoints\" (\n \"knowledge_space_id\", \"document_asset_id\", \"document_version\"\n );\n", path: "packages/database/migrations/0044_document_semantic_window_checkpoints.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0044_document_semantic_window_checkpoints\n-- Dialect: tidb\n-- Persists validated semantic-window model responses for generation-scoped retry recovery.\n\nCREATE TABLE IF NOT EXISTS `document_semantic_window_checkpoints` (\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `document_version` INT NOT NULL,\n `publication_generation_id` CHAR(36) NOT NULL,\n `window_id` VARCHAR(128) NOT NULL,\n `input_fingerprint` VARCHAR(71) NOT NULL,\n `model_fingerprint` VARCHAR(71) NOT NULL,\n `response_text` MEDIUMTEXT NOT NULL,\n `completion` JSON NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n PRIMARY KEY (\n `tenant_id`, `knowledge_space_id`, `publication_generation_id`,\n `window_id`, `input_fingerprint`\n ),\n CONSTRAINT `document_semantic_window_checkpoints_scope_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n CONSTRAINT `document_semantic_window_checkpoints_asset_fk`\n FOREIGN KEY (`knowledge_space_id`, `document_asset_id`, `document_version`)\n REFERENCES `document_assets` (`knowledge_space_id`, `id`, `version`) ON DELETE CASCADE,\n CONSTRAINT `document_semantic_window_checkpoints_fingerprint_ck`\n CHECK (\n `input_fingerprint` REGEXP '^sha256:[a-f0-9]{64}$'\n AND `model_fingerprint` REGEXP '^sha256:[a-f0-9]{64}$'\n ),\n CONSTRAINT `document_semantic_window_checkpoints_completion_ck`\n CHECK (JSON_TYPE(`completion`) = 'OBJECT'),\n CONSTRAINT `document_semantic_window_checkpoints_version_ck`\n CHECK (`document_version` >= 1)\n);\n\nCREATE INDEX IF NOT EXISTS `document_semantic_window_checkpoints_asset_idx`\n ON `document_semantic_window_checkpoints` (\n `knowledge_space_id`, `document_asset_id`, `document_version`\n );\n", path: "packages/database/migrations/0044_document_semantic_window_checkpoints.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0045_quality_replay_match_policy\n-- Dialect: postgres\n-- Freezes each golden question's evidence match policy into durable quality replay items.\n\nALTER TABLE \"quality_replay_items\"\n ADD COLUMN IF NOT EXISTS \"match_policy\" VARCHAR(8);\n\nUPDATE \"quality_replay_items\"\nSET \"match_policy\" = 'all'\nWHERE \"match_policy\" IS NULL;\n\nALTER TABLE \"quality_replay_items\"\n ALTER COLUMN \"match_policy\" SET NOT NULL;\n\nALTER TABLE \"quality_replay_items\"\n DROP CONSTRAINT IF EXISTS \"quality_replay_items_match_policy_ck\";\n\nALTER TABLE \"quality_replay_items\"\n ADD CONSTRAINT \"quality_replay_items_match_policy_ck\"\n CHECK (\"match_policy\" IN ('all', 'any'));\n", path: "packages/database/migrations/0045_quality_replay_match_policy.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0045_quality_replay_match_policy\n-- Dialect: tidb\n-- Freezes each golden question's evidence match policy into durable quality replay items.\n\nALTER TABLE `quality_replay_items`\n ADD COLUMN IF NOT EXISTS `match_policy` VARCHAR(8);\n\nUPDATE `quality_replay_items`\nSET `match_policy` = 'all'\nWHERE `match_policy` IS NULL;\n\nALTER TABLE `quality_replay_items`\n MODIFY COLUMN `match_policy` VARCHAR(8) NOT NULL;\n\nSET @qr_0045_match_policy_ck_exists = (\n SELECT COUNT(*) FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'quality_replay_items'\n AND constraint_name = 'quality_replay_items_match_policy_ck'\n);\nSET @qr_0045_match_policy_ck_drop_sql = IF(\n @qr_0045_match_policy_ck_exists > 0,\n 'ALTER TABLE `quality_replay_items` DROP CONSTRAINT `quality_replay_items_match_policy_ck`',\n 'SELECT 1'\n);\nPREPARE qr_0045_match_policy_ck_drop_stmt FROM @qr_0045_match_policy_ck_drop_sql;\nEXECUTE qr_0045_match_policy_ck_drop_stmt;\nDEALLOCATE PREPARE qr_0045_match_policy_ck_drop_stmt;\n\nALTER TABLE `quality_replay_items`\n ADD CONSTRAINT `quality_replay_items_match_policy_ck`\n CHECK (`match_policy` IN ('all', 'any'));\n", path: "packages/database/migrations/0045_quality_replay_match_policy.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0046_remove_provider_sync_policy\n-- Dialect: postgres\n-- Historical provider policies must be converted to manual before this migration runs.\n\nALTER TABLE \"source_sync_policies\"\n DROP CONSTRAINT IF EXISTS \"source_sync_policies_mode_ck\";\n\nALTER TABLE \"source_sync_policies\"\n ADD CONSTRAINT \"source_sync_policies_mode_ck\"\n CHECK (\"mode\" IN ('manual', 'interval', 'custom'));\n", path: "packages/database/migrations/0046_remove_provider_sync_policy.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0046_remove_provider_sync_policy\n-- Dialect: tidb\n-- Historical provider policies must be converted to manual before this migration runs.\n\nSET @kfs_0046_mode_ck_exists = (\n SELECT COUNT(*) FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'source_sync_policies'\n AND constraint_name = 'source_sync_policies_mode_ck'\n);\nSET @kfs_0046_mode_ck_drop_sql = IF(\n @kfs_0046_mode_ck_exists > 0,\n 'ALTER TABLE `source_sync_policies` DROP CONSTRAINT `source_sync_policies_mode_ck`',\n 'SELECT 1'\n);\nPREPARE kfs_0046_mode_ck_drop_stmt FROM @kfs_0046_mode_ck_drop_sql;\nEXECUTE kfs_0046_mode_ck_drop_stmt;\nDEALLOCATE PREPARE kfs_0046_mode_ck_drop_stmt;\n\nALTER TABLE `source_sync_policies`\n ADD CONSTRAINT `source_sync_policies_mode_ck`\n CHECK (`mode` IN ('manual', 'interval', 'custom'));\n", path: "packages/database/migrations/0046_remove_provider_sync_policy.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0047_parse_artifact_checkpoints\n-- Dialect: postgres\n-- Persists raw parser output across compilation retries without exposing it as canonical content.\n\nCREATE TABLE IF NOT EXISTS \"parse_artifact_checkpoints\" (\n \"document_asset_id\" UUID NOT NULL,\n \"version\" INTEGER NOT NULL,\n \"policy_fingerprint\" VARCHAR(64) NOT NULL,\n \"artifact\" JSONB NOT NULL,\n \"created_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"parse_artifact_checkpoints_asset_fk\"\n FOREIGN KEY (\"document_asset_id\")\n REFERENCES \"document_assets\" (\"id\") ON DELETE CASCADE,\n CONSTRAINT \"parse_artifact_checkpoints_version_ck\"\n CHECK (\"version\" >= 1),\n CONSTRAINT \"parse_artifact_checkpoints_policy_fingerprint_ck\"\n CHECK (\"policy_fingerprint\" ~ '^[a-f0-9]{64}$'),\n CONSTRAINT \"parse_artifact_checkpoints_artifact_ck\"\n CHECK (jsonb_typeof(\"artifact\") = 'object')\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"parse_artifact_checkpoints_asset_version_uq\"\n ON \"parse_artifact_checkpoints\" (\"document_asset_id\", \"version\");\n", path: "packages/database/migrations/0047_parse_artifact_checkpoints.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0047_parse_artifact_checkpoints\n-- Dialect: tidb\n-- Persists raw parser output across compilation retries without exposing it as canonical content.\n\nCREATE TABLE IF NOT EXISTS `parse_artifact_checkpoints` (\n `document_asset_id` CHAR(36) NOT NULL,\n `version` INT NOT NULL,\n `policy_fingerprint` VARCHAR(64) NOT NULL,\n `artifact` JSON NOT NULL,\n `created_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `parse_artifact_checkpoints_asset_fk`\n FOREIGN KEY (`document_asset_id`)\n REFERENCES `document_assets` (`id`) ON DELETE CASCADE,\n CONSTRAINT `parse_artifact_checkpoints_version_ck`\n CHECK (`version` >= 1),\n CONSTRAINT `parse_artifact_checkpoints_policy_fingerprint_ck`\n CHECK (`policy_fingerprint` REGEXP '^[a-f0-9]{64}$'),\n CONSTRAINT `parse_artifact_checkpoints_artifact_ck`\n CHECK (JSON_TYPE(`artifact`) = 'OBJECT')\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `parse_artifact_checkpoints_asset_version_uq`\n ON `parse_artifact_checkpoints` (`document_asset_id`, `version`);\n", path: "packages/database/migrations/0047_parse_artifact_checkpoints.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0048_deletion_active_scope_indexes\n-- Dialect: postgres\n-- Keeps active deletion admission proportional to live fences instead of permanent audit history.\n\nCREATE INDEX IF NOT EXISTS \"deletion_jobs_active_scope_idx\"\n ON \"deletion_jobs\" (\"tenant_id\", \"knowledge_space_id\", \"target_type\", \"target_id\")\n WHERE \"active_slot\" = 1;\n\nCREATE INDEX IF NOT EXISTS \"deletion_tombstones_active_scope_idx\"\n ON \"deletion_tombstones\" (\"tenant_id\", \"knowledge_space_id\")\n WHERE \"state\" = 'active';\n", path: "packages/database/migrations/0048_deletion_active_scope_indexes.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0048_deletion_active_scope_indexes\n-- Dialect: tidb\n-- Keeps active deletion admission proportional to live fences instead of permanent audit history.\n\nCREATE INDEX IF NOT EXISTS `deletion_jobs_active_scope_idx`\n ON `deletion_jobs` (`tenant_id`, `knowledge_space_id`, `active_slot`, `target_type`, `target_id`);\n\nCREATE INDEX IF NOT EXISTS `deletion_tombstones_active_scope_idx`\n ON `deletion_tombstones` (`tenant_id`, `knowledge_space_id`, `state`);\n", path: "packages/database/migrations/0048_deletion_active_scope_indexes.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0049_answer_trace_query_images\n-- Dialect: postgres\n-- Answer traces keep the query-image references they were run with so history can show them.\n\nALTER TABLE \"answer_traces\"\n ADD COLUMN IF NOT EXISTS \"query_images\" JSONB;\n", path: "packages/database/migrations/0049_answer_trace_query_images.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0049_answer_trace_query_images\n-- Dialect: tidb\n-- Answer traces keep the query-image references they were run with so history can show them.\n\nALTER TABLE `answer_traces`\n ADD COLUMN IF NOT EXISTS `query_images` JSON NULL;\n", path: "packages/database/migrations/0049_answer_trace_query_images.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0050_deletion_job_capability_provenance\n-- Dialect: postgres\n-- A space deletion job outlives the space-owned grant that authorized it. Preserve the grant id\n-- as immutable audit provenance without a live FK that blocks the terminal space delete.\n\nALTER TABLE \"deletion_jobs\"\n DROP CONSTRAINT IF EXISTS \"deletion_jobs_capability_grant_fk\";\n\nALTER TABLE \"deletion_retry_audits\"\n DROP CONSTRAINT IF EXISTS \"deletion_retry_audits_capability_grant_fk\";\n", path: "packages/database/migrations/0050_deletion_job_capability_provenance.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0050_deletion_job_capability_provenance\n-- Dialect: tidb\n-- A space deletion job outlives the space-owned grant that authorized it. Preserve the grant id\n-- as immutable audit provenance without a live FK that blocks the terminal space delete.\n\nALTER TABLE `deletion_jobs`\n DROP FOREIGN KEY `deletion_jobs_capability_grant_fk`;\n\nALTER TABLE `deletion_retry_audits`\n DROP FOREIGN KEY `deletion_retry_audits_capability_grant_fk`;\n", path: "packages/database/migrations/0050_deletion_job_capability_provenance.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0051_failed_query_trace_outcome_index\n-- Dialect: postgres\n-- Supports Overview outcome joins by exact trace and trigger without scanning space history.\n\nCREATE INDEX IF NOT EXISTS \"failed_queries_trace_trigger_idx\"\n ON \"failed_queries\" (\n \"tenant_id\", \"knowledge_space_id\", \"answer_trace_id\", \"trigger\"\n )\n WHERE \"answer_trace_id\" IS NOT NULL;\n", path: "packages/database/migrations/0051_failed_query_trace_outcome_index.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0051_failed_query_trace_outcome_index\n-- Dialect: tidb\n-- Supports Overview outcome joins by exact trace and trigger without scanning space history.\n\nCREATE INDEX IF NOT EXISTS `failed_queries_trace_trigger_idx`\n ON `failed_queries` (\n `tenant_id`, `knowledge_space_id`, `answer_trace_id`\n );\n", path: "packages/database/migrations/0051_failed_query_trace_outcome_index.tidb.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0052_namespace_source_previews\n-- Dialect: postgres\n\nCREATE TABLE IF NOT EXISTS \"namespace_source_preview_jobs\" (\n \"id\" uuid PRIMARY KEY,\n \"tenant_id\" varchar(255) NOT NULL,\n \"account_id\" varchar(255) NOT NULL,\n \"status\" varchar(32) NOT NULL,\n \"provider_config\" jsonb NOT NULL,\n \"configuration_fingerprint\" varchar(128) NOT NULL,\n \"expires_at\" timestamptz NOT NULL,\n \"consumed_at\" timestamptz NULL,\n \"content_cleaned_at\" timestamptz NULL,\n \"import_workflow_id\" uuid NULL,\n \"error_code\" varchar(128) NULL,\n \"created_at\" timestamptz NOT NULL,\n \"updated_at\" timestamptz NOT NULL\n);\nCREATE INDEX IF NOT EXISTS \"namespace_source_preview_jobs_claim_idx\"\n ON \"namespace_source_preview_jobs\" (\"status\", \"expires_at\", \"created_at\");\nCREATE INDEX IF NOT EXISTS \"namespace_source_preview_jobs_owner_idx\"\n ON \"namespace_source_preview_jobs\" (\"tenant_id\", \"account_id\", \"created_at\");\nCREATE INDEX IF NOT EXISTS \"namespace_source_preview_jobs_cleanup_idx\"\n ON \"namespace_source_preview_jobs\" (\"status\", \"content_cleaned_at\", \"updated_at\");\n\nCREATE TABLE IF NOT EXISTS \"namespace_source_preview_pages\" (\n \"job_id\" uuid NOT NULL REFERENCES \"namespace_source_preview_jobs\" (\"id\") ON DELETE CASCADE,\n \"page_id\" varchar(128) NOT NULL,\n \"source_url\" varchar(4096) NOT NULL,\n \"title\" varchar(500) NULL,\n \"description\" text NULL,\n \"content_hash\" varchar(64) NOT NULL,\n \"content_object_key\" varchar(2048) NOT NULL,\n \"created_at\" timestamptz NOT NULL,\n PRIMARY KEY (\"job_id\", \"page_id\")\n);\n", path: "packages/database/migrations/0052_namespace_source_previews.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0052_namespace_source_previews\n-- Dialect: tidb\n\nCREATE TABLE IF NOT EXISTS `namespace_source_preview_jobs` (\n `id` char(36) PRIMARY KEY,\n `tenant_id` varchar(255) NOT NULL,\n `account_id` varchar(255) NOT NULL,\n `status` varchar(32) NOT NULL,\n `provider_config` json NOT NULL,\n `configuration_fingerprint` varchar(128) NOT NULL,\n `expires_at` datetime(6) NOT NULL,\n `consumed_at` datetime(6) NULL,\n `content_cleaned_at` datetime(6) NULL,\n `import_workflow_id` char(36) NULL,\n `error_code` varchar(128) NULL,\n `created_at` datetime(6) NOT NULL,\n `updated_at` datetime(6) NOT NULL,\n INDEX `namespace_source_preview_jobs_claim_idx` (`status`, `expires_at`, `created_at`),\n INDEX `namespace_source_preview_jobs_owner_idx` (`tenant_id`, `account_id`, `created_at`),\n INDEX `namespace_source_preview_jobs_cleanup_idx` (`status`, `content_cleaned_at`, `updated_at`)\n);\n\nCREATE TABLE IF NOT EXISTS `namespace_source_preview_pages` (\n `job_id` char(36) NOT NULL,\n `page_id` varchar(128) NOT NULL,\n `source_url` varchar(4096) NOT NULL,\n `title` varchar(500) NULL,\n `description` text NULL,\n `content_hash` varchar(64) NOT NULL,\n `content_object_key` varchar(2048) NOT NULL,\n `created_at` datetime(6) NOT NULL,\n PRIMARY KEY (`job_id`, `page_id`),\n CONSTRAINT `namespace_source_preview_pages_job_fk` FOREIGN KEY (`job_id`) REFERENCES `namespace_source_preview_jobs` (`id`) ON DELETE CASCADE\n);\n", path: "packages/database/migrations/0052_namespace_source_previews.tidb.sql" },
] as const satisfies readonly MigrationArtifact[];