dify/knowledge-fs/packages/database/src/migration-artifacts.generated.ts
Jyong b79b8a879b feat(knowledge-fs): record and count every knowledge space query
Workflow KnowledgeFS retrieval nodes ran through `retrieval-tests`, which
never wrote an AnswerTrace or an overview event, so their queries were
missing from the retrieval history and from the space overview; the history
also only ever showed each member their own traces.

- AnswerTrace gains a `source` (retrieval_test | workflow | service_api |
  agent | mcp; migration 0051_answer_trace_source) derived from the
  Capability v2 caller kind. The retrieval-tests route records one trace per
  run (stages, evidence bundle, profile metadata), returns its id as
  `answerTraceId`, and the workflow node's failed-retrieval capture attaches
  to that trace instead of creating a second record.
- The quality trace list exposes and filters by `source`; traces from other
  caller kinds are visible to any current reader of the space, and counts and
  scores fall back to the evidence embedded in the trace when no bundle row
  exists.
- Overview accounting: retrieval-tests and Research tasks now emit
  `query.requested`, and the Research job state machine emits
  `query.completed` / `query.failed` on terminal stages (wired for both the
  in-process gateway and the durable runtime), so query volume, answer rate
  and outcomes include every caller. Activity details keep `source` and
  `taskKind`.
- Console and service trace routes accept a `source` filter; the retrieval
  test page shows a source badge and an all / retrieval tests / workflow
  filter, with translations for every locale.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zw5G5SX3HmVfnZof6YWAc
2026-09-04 05:48:17 -04:00

113 lines
630 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" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0053_answer_trace_source\n-- Dialect: postgres\n-- Answer traces record which caller produced them (console retrieval test, workflow node, service\n-- API, agent, MCP) so retrieval history can distinguish and filter them. NULL means retrieval_test.\n\nALTER TABLE \"answer_traces\"\n ADD COLUMN IF NOT EXISTS \"source\" TEXT;\n\nCREATE INDEX IF NOT EXISTS \"answer_traces_space_source_created_idx\"\n ON \"answer_traces\" (\"knowledge_space_id\", \"source\", \"created_at\", \"id\");\n", path: "packages/database/migrations/0053_answer_trace_source.postgres.sql" },
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0053_answer_trace_source\n-- Dialect: tidb\n-- Answer traces record which caller produced them (console retrieval test, workflow node, service\n-- API, agent, MCP) so retrieval history can distinguish and filter them. NULL means retrieval_test.\n\nALTER TABLE `answer_traces`\n ADD COLUMN IF NOT EXISTS `source` VARCHAR(32) NULL;\n\nCREATE INDEX IF NOT EXISTS `answer_traces_space_source_created_idx`\n ON `answer_traces` (`knowledge_space_id`, `source`, `created_at`, `id`);\n", path: "packages/database/migrations/0053_answer_trace_source.tidb.sql" },
] as const satisfies readonly MigrationArtifact[];