20260720_100_llamaindex_lightrag_knowledge.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. """Add the governance knowledge retrieval and dynamic-update control plane."""
  2. from alembic import op
  3. revision = "20260720_100"
  4. down_revision = "20260719_90"
  5. branch_labels = None
  6. depends_on = None
  7. def upgrade() -> None:
  8. op.execute(
  9. """
  10. CREATE EXTENSION IF NOT EXISTS vector;
  11. CREATE EXTENSION IF NOT EXISTS pg_trgm;
  12. CREATE TABLE IF NOT EXISTS public.user_business_domain_grants (
  13. user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
  14. business_domain_uid UUID NOT NULL,
  15. grant_type VARCHAR(20) NOT NULL CHECK (grant_type IN ('read','manage')),
  16. granted_by UUID REFERENCES public.users(id) ON DELETE SET NULL,
  17. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  18. PRIMARY KEY (user_id, business_domain_uid, grant_type)
  19. );
  20. CREATE TABLE IF NOT EXISTS public.knowledge_embedding_profiles (
  21. id UUID PRIMARY KEY,
  22. provider VARCHAR(30) NOT NULL,
  23. model VARCHAR(120) NOT NULL,
  24. dimension INTEGER NOT NULL CHECK (dimension > 0),
  25. distance VARCHAR(20) NOT NULL DEFAULT 'cosine',
  26. status VARCHAR(20) NOT NULL CHECK (
  27. status IN ('building','active','retired','failed')
  28. ),
  29. config_hash CHAR(64) NOT NULL,
  30. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  31. activated_at TIMESTAMPTZ
  32. );
  33. CREATE UNIQUE INDEX IF NOT EXISTS uq_knowledge_embedding_profile_active
  34. ON public.knowledge_embedding_profiles ((status))
  35. WHERE status = 'active';
  36. CREATE TABLE IF NOT EXISTS public.knowledge_points (
  37. id UUID PRIMARY KEY,
  38. point_key VARCHAR(500) NOT NULL,
  39. point_revision BIGINT NOT NULL CHECK (point_revision > 0),
  40. source_type VARCHAR(50) NOT NULL,
  41. source_uid UUID NOT NULL,
  42. source_revision BIGINT NOT NULL CHECK (source_revision > 0),
  43. semantic_path VARCHAR(500) NOT NULL,
  44. content TEXT NOT NULL,
  45. content_hash CHAR(64) NOT NULL,
  46. metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
  47. metadata_hash CHAR(64) NOT NULL,
  48. permission_scope JSONB NOT NULL DEFAULT '{}'::jsonb,
  49. permission_hash CHAR(64) NOT NULL,
  50. status VARCHAR(20) NOT NULL CHECK (
  51. status IN ('building','active','superseded','deleted','failed')
  52. ),
  53. valid_from TIMESTAMPTZ,
  54. valid_to TIMESTAMPTZ,
  55. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  56. activated_at TIMESTAMPTZ,
  57. UNIQUE (point_key, point_revision)
  58. );
  59. CREATE UNIQUE INDEX IF NOT EXISTS uq_knowledge_point_active
  60. ON public.knowledge_points(point_key) WHERE status = 'active';
  61. CREATE INDEX IF NOT EXISTS idx_knowledge_point_source_revision
  62. ON public.knowledge_points(source_type, source_uid, source_revision);
  63. CREATE INDEX IF NOT EXISTS idx_knowledge_point_content_hash
  64. ON public.knowledge_points(content_hash);
  65. CREATE TABLE IF NOT EXISTS public.knowledge_point_dependencies (
  66. from_point_key VARCHAR(500) NOT NULL,
  67. to_point_key VARCHAR(500) NOT NULL,
  68. relation_type VARCHAR(50) NOT NULL,
  69. source VARCHAR(30) NOT NULL CHECK (
  70. source IN ('governance','deterministic_rule')
  71. ),
  72. generation BIGINT NOT NULL,
  73. status VARCHAR(20) NOT NULL CHECK (
  74. status IN ('active','superseded','deleted')
  75. ),
  76. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  77. PRIMARY KEY (
  78. from_point_key, to_point_key, relation_type, source, generation
  79. )
  80. );
  81. CREATE INDEX IF NOT EXISTS idx_knowledge_dependency_reverse
  82. ON public.knowledge_point_dependencies(to_point_key, status);
  83. CREATE TABLE IF NOT EXISTS public.knowledge_change_sets (
  84. id UUID PRIMARY KEY,
  85. correlation_id UUID NOT NULL,
  86. source_type VARCHAR(50) NOT NULL,
  87. source_uid UUID NOT NULL,
  88. source_revision BIGINT NOT NULL,
  89. change_type VARCHAR(20) NOT NULL CHECK (
  90. change_type IN ('create','update','delete','permission','rebuild')
  91. ),
  92. source_snapshot_hash CHAR(64),
  93. added_count INTEGER NOT NULL DEFAULT 0,
  94. modified_count INTEGER NOT NULL DEFAULT 0,
  95. deleted_count INTEGER NOT NULL DEFAULT 0,
  96. impacted_point_count INTEGER NOT NULL DEFAULT 0,
  97. impact_truncated BOOLEAN NOT NULL DEFAULT FALSE,
  98. status VARCHAR(30) NOT NULL CHECK (status IN (
  99. 'pending','diffed','building','validating','canonical_active',
  100. 'projecting','complete','degraded','failed','rolled_back'
  101. )),
  102. target_generation BIGINT NOT NULL,
  103. last_error VARCHAR(1000),
  104. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  105. updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  106. activated_at TIMESTAMPTZ,
  107. UNIQUE (source_type, source_uid, source_revision, target_generation)
  108. );
  109. CREATE INDEX IF NOT EXISTS idx_knowledge_change_set_status
  110. ON public.knowledge_change_sets(status, created_at);
  111. ALTER TABLE public.governance_documents
  112. ADD COLUMN IF NOT EXISTS point_set_hash CHAR(64),
  113. ADD COLUMN IF NOT EXISTS active_generation BIGINT,
  114. ADD COLUMN IF NOT EXISTS change_set_id UUID
  115. REFERENCES public.knowledge_change_sets(id);
  116. CREATE TABLE IF NOT EXISTS public.knowledge_change_items (
  117. change_set_id UUID NOT NULL REFERENCES public.knowledge_change_sets(id)
  118. ON DELETE CASCADE,
  119. point_key VARCHAR(500) NOT NULL,
  120. change_kind VARCHAR(20) NOT NULL CHECK (
  121. change_kind IN ('added','modified','deleted','impacted')
  122. ),
  123. old_point_revision BIGINT,
  124. new_point_revision BIGINT,
  125. old_content_hash CHAR(64),
  126. new_content_hash CHAR(64),
  127. caused_by_point_key VARCHAR(500),
  128. propagation_hop INTEGER,
  129. canonical_status VARCHAR(20) NOT NULL DEFAULT 'pending',
  130. embedding_status VARCHAR(20) NOT NULL DEFAULT 'pending',
  131. cache_status VARCHAR(20) NOT NULL DEFAULT 'pending',
  132. lightrag_status VARCHAR(20) NOT NULL DEFAULT 'pending',
  133. attempts INTEGER NOT NULL DEFAULT 0,
  134. last_error VARCHAR(1000),
  135. PRIMARY KEY (change_set_id, point_key)
  136. );
  137. ALTER TABLE public.governance_chunks
  138. ADD COLUMN IF NOT EXISTS chunk_kind VARCHAR(30),
  139. ADD COLUMN IF NOT EXISTS section_path VARCHAR(500),
  140. ADD COLUMN IF NOT EXISTS metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
  141. ADD COLUMN IF NOT EXISTS token_count INTEGER,
  142. ADD COLUMN IF NOT EXISTS lexical_text TEXT,
  143. ADD COLUMN IF NOT EXISTS search_vector TSVECTOR,
  144. ADD COLUMN IF NOT EXISTS source_locator JSONB NOT NULL DEFAULT '{}'::jsonb,
  145. ADD COLUMN IF NOT EXISTS primary_point_key VARCHAR(500),
  146. ADD COLUMN IF NOT EXISTS point_keys JSONB NOT NULL DEFAULT '[]'::jsonb,
  147. ADD COLUMN IF NOT EXISTS point_set_hash CHAR(64),
  148. ADD COLUMN IF NOT EXISTS change_set_id UUID REFERENCES public.knowledge_change_sets(id);
  149. CREATE INDEX IF NOT EXISTS idx_governance_chunks_search_vector
  150. ON public.governance_chunks USING GIN(search_vector);
  151. CREATE INDEX IF NOT EXISTS idx_governance_chunks_metadata
  152. ON public.governance_chunks USING GIN(metadata jsonb_path_ops);
  153. CREATE INDEX IF NOT EXISTS idx_governance_chunks_lexical_trgm
  154. ON public.governance_chunks USING GIN(lexical_text gin_trgm_ops);
  155. CREATE TABLE IF NOT EXISTS public.knowledge_chunk_embeddings (
  156. id UUID PRIMARY KEY,
  157. chunk_id UUID NOT NULL REFERENCES public.governance_chunks(id)
  158. ON DELETE CASCADE,
  159. profile_id UUID NOT NULL REFERENCES public.knowledge_embedding_profiles(id),
  160. embedding vector(1024) NOT NULL,
  161. embedding_hash CHAR(64) NOT NULL,
  162. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  163. UNIQUE (chunk_id, profile_id)
  164. );
  165. CREATE INDEX IF NOT EXISTS idx_knowledge_chunk_embedding_hnsw
  166. ON public.knowledge_chunk_embeddings
  167. USING hnsw (embedding vector_cosine_ops);
  168. CREATE TABLE IF NOT EXISTS public.knowledge_index_projections (
  169. id UUID PRIMARY KEY,
  170. document_id UUID NOT NULL REFERENCES public.governance_documents(id)
  171. ON DELETE CASCADE,
  172. change_set_id UUID REFERENCES public.knowledge_change_sets(id)
  173. ON DELETE SET NULL,
  174. engine VARCHAR(30) NOT NULL CHECK (
  175. engine IN ('canonical_vector','lightrag')
  176. ),
  177. generation BIGINT NOT NULL,
  178. workspace VARCHAR(300) NOT NULL,
  179. external_document_id VARCHAR(700) NOT NULL,
  180. content_hash CHAR(64) NOT NULL,
  181. status VARCHAR(20) NOT NULL CHECK (status IN (
  182. 'pending','processing','ready','failed','deleting','deleted','unverified'
  183. )),
  184. attempts INTEGER NOT NULL DEFAULT 0,
  185. external_track_id VARCHAR(300),
  186. available_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  187. last_error VARCHAR(1000),
  188. updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  189. UNIQUE (document_id, engine, generation, workspace)
  190. );
  191. CREATE TABLE IF NOT EXISTS public.knowledge_cache_dependencies (
  192. cache_key_hash CHAR(64) NOT NULL,
  193. point_key VARCHAR(500) NOT NULL,
  194. point_revision BIGINT NOT NULL,
  195. generation BIGINT NOT NULL,
  196. business_domain_uid UUID,
  197. expires_at TIMESTAMPTZ NOT NULL,
  198. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  199. PRIMARY KEY (cache_key_hash, point_key, point_revision, generation)
  200. );
  201. CREATE INDEX IF NOT EXISTS idx_knowledge_cache_point
  202. ON public.knowledge_cache_dependencies(point_key, expires_at);
  203. CREATE TABLE IF NOT EXISTS public.knowledge_query_audits (
  204. id UUID PRIMARY KEY,
  205. query_hash CHAR(64) NOT NULL,
  206. user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
  207. roles JSONB NOT NULL DEFAULT '[]'::jsonb,
  208. business_domain_uids JSONB NOT NULL DEFAULT '[]'::jsonb,
  209. mode VARCHAR(30) NOT NULL,
  210. retriever_counts JSONB NOT NULL DEFAULT '{}'::jsonb,
  211. cited_points JSONB NOT NULL DEFAULT '[]'::jsonb,
  212. degraded_components JSONB NOT NULL DEFAULT '[]'::jsonb,
  213. correlation_id UUID NOT NULL,
  214. latency_ms INTEGER,
  215. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
  216. );
  217. CREATE TABLE IF NOT EXISTS public.knowledge_evaluation_sets (
  218. id UUID PRIMARY KEY,
  219. name VARCHAR(200) NOT NULL,
  220. description TEXT,
  221. status VARCHAR(20) NOT NULL DEFAULT 'draft',
  222. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
  223. );
  224. CREATE TABLE IF NOT EXISTS public.knowledge_evaluation_cases (
  225. id UUID PRIMARY KEY,
  226. evaluation_set_id UUID NOT NULL REFERENCES public.knowledge_evaluation_sets(id)
  227. ON DELETE CASCADE,
  228. case_type VARCHAR(40) NOT NULL,
  229. query TEXT NOT NULL,
  230. allowed_business_domains JSONB NOT NULL DEFAULT '[]'::jsonb,
  231. expected_sources JSONB NOT NULL DEFAULT '[]'::jsonb,
  232. expected_answer_points JSONB NOT NULL DEFAULT '[]'::jsonb,
  233. must_refuse BOOLEAN NOT NULL DEFAULT FALSE,
  234. mutation JSONB,
  235. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
  236. );
  237. CREATE TABLE IF NOT EXISTS public.knowledge_evaluation_runs (
  238. id UUID PRIMARY KEY,
  239. evaluation_set_id UUID NOT NULL REFERENCES public.knowledge_evaluation_sets(id),
  240. configuration JSONB NOT NULL,
  241. status VARCHAR(20) NOT NULL,
  242. started_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  243. finished_at TIMESTAMPTZ
  244. );
  245. CREATE TABLE IF NOT EXISTS public.knowledge_evaluation_results (
  246. run_id UUID NOT NULL REFERENCES public.knowledge_evaluation_runs(id)
  247. ON DELETE CASCADE,
  248. case_id UUID NOT NULL REFERENCES public.knowledge_evaluation_cases(id)
  249. ON DELETE CASCADE,
  250. metrics JSONB NOT NULL DEFAULT '{}'::jsonb,
  251. citations JSONB NOT NULL DEFAULT '[]'::jsonb,
  252. passed BOOLEAN NOT NULL,
  253. failure_reason VARCHAR(1000),
  254. PRIMARY KEY (run_id, case_id)
  255. );
  256. """
  257. )
  258. def downgrade() -> None:
  259. # Dynamic knowledge data is intentionally preserved during application rollback.
  260. pass