| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- """Add the governance knowledge retrieval and dynamic-update control plane."""
- from alembic import op
- revision = "20260720_100"
- down_revision = "20260719_90"
- branch_labels = None
- depends_on = None
- def upgrade() -> None:
- op.execute(
- """
- CREATE EXTENSION IF NOT EXISTS vector;
- CREATE EXTENSION IF NOT EXISTS pg_trgm;
- CREATE TABLE IF NOT EXISTS public.user_business_domain_grants (
- user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
- business_domain_uid UUID NOT NULL,
- grant_type VARCHAR(20) NOT NULL CHECK (grant_type IN ('read','manage')),
- granted_by UUID REFERENCES public.users(id) ON DELETE SET NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (user_id, business_domain_uid, grant_type)
- );
- CREATE TABLE IF NOT EXISTS public.knowledge_embedding_profiles (
- id UUID PRIMARY KEY,
- provider VARCHAR(30) NOT NULL,
- model VARCHAR(120) NOT NULL,
- dimension INTEGER NOT NULL CHECK (dimension > 0),
- distance VARCHAR(20) NOT NULL DEFAULT 'cosine',
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('building','active','retired','failed')
- ),
- config_hash CHAR(64) NOT NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- activated_at TIMESTAMPTZ
- );
- CREATE UNIQUE INDEX IF NOT EXISTS uq_knowledge_embedding_profile_active
- ON public.knowledge_embedding_profiles ((status))
- WHERE status = 'active';
- CREATE TABLE IF NOT EXISTS public.knowledge_points (
- id UUID PRIMARY KEY,
- point_key VARCHAR(500) NOT NULL,
- point_revision BIGINT NOT NULL CHECK (point_revision > 0),
- source_type VARCHAR(50) NOT NULL,
- source_uid UUID NOT NULL,
- source_revision BIGINT NOT NULL CHECK (source_revision > 0),
- semantic_path VARCHAR(500) NOT NULL,
- content TEXT NOT NULL,
- content_hash CHAR(64) NOT NULL,
- metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
- metadata_hash CHAR(64) NOT NULL,
- permission_scope JSONB NOT NULL DEFAULT '{}'::jsonb,
- permission_hash CHAR(64) NOT NULL,
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('building','active','superseded','deleted','failed')
- ),
- valid_from TIMESTAMPTZ,
- valid_to TIMESTAMPTZ,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- activated_at TIMESTAMPTZ,
- UNIQUE (point_key, point_revision)
- );
- CREATE UNIQUE INDEX IF NOT EXISTS uq_knowledge_point_active
- ON public.knowledge_points(point_key) WHERE status = 'active';
- CREATE INDEX IF NOT EXISTS idx_knowledge_point_source_revision
- ON public.knowledge_points(source_type, source_uid, source_revision);
- CREATE INDEX IF NOT EXISTS idx_knowledge_point_content_hash
- ON public.knowledge_points(content_hash);
- CREATE TABLE IF NOT EXISTS public.knowledge_point_dependencies (
- from_point_key VARCHAR(500) NOT NULL,
- to_point_key VARCHAR(500) NOT NULL,
- relation_type VARCHAR(50) NOT NULL,
- source VARCHAR(30) NOT NULL CHECK (
- source IN ('governance','deterministic_rule')
- ),
- generation BIGINT NOT NULL,
- status VARCHAR(20) NOT NULL CHECK (
- status IN ('active','superseded','deleted')
- ),
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (
- from_point_key, to_point_key, relation_type, source, generation
- )
- );
- CREATE INDEX IF NOT EXISTS idx_knowledge_dependency_reverse
- ON public.knowledge_point_dependencies(to_point_key, status);
- CREATE TABLE IF NOT EXISTS public.knowledge_change_sets (
- id UUID PRIMARY KEY,
- correlation_id UUID NOT NULL,
- source_type VARCHAR(50) NOT NULL,
- source_uid UUID NOT NULL,
- source_revision BIGINT NOT NULL,
- change_type VARCHAR(20) NOT NULL CHECK (
- change_type IN ('create','update','delete','permission','rebuild')
- ),
- source_snapshot_hash CHAR(64),
- added_count INTEGER NOT NULL DEFAULT 0,
- modified_count INTEGER NOT NULL DEFAULT 0,
- deleted_count INTEGER NOT NULL DEFAULT 0,
- impacted_point_count INTEGER NOT NULL DEFAULT 0,
- impact_truncated BOOLEAN NOT NULL DEFAULT FALSE,
- status VARCHAR(30) NOT NULL CHECK (status IN (
- 'pending','diffed','building','validating','canonical_active',
- 'projecting','complete','degraded','failed','rolled_back'
- )),
- target_generation BIGINT NOT NULL,
- last_error VARCHAR(1000),
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- activated_at TIMESTAMPTZ,
- UNIQUE (source_type, source_uid, source_revision, target_generation)
- );
- CREATE INDEX IF NOT EXISTS idx_knowledge_change_set_status
- ON public.knowledge_change_sets(status, created_at);
- ALTER TABLE public.governance_documents
- ADD COLUMN IF NOT EXISTS point_set_hash CHAR(64),
- ADD COLUMN IF NOT EXISTS active_generation BIGINT,
- ADD COLUMN IF NOT EXISTS change_set_id UUID
- REFERENCES public.knowledge_change_sets(id);
- CREATE TABLE IF NOT EXISTS public.knowledge_change_items (
- change_set_id UUID NOT NULL REFERENCES public.knowledge_change_sets(id)
- ON DELETE CASCADE,
- point_key VARCHAR(500) NOT NULL,
- change_kind VARCHAR(20) NOT NULL CHECK (
- change_kind IN ('added','modified','deleted','impacted')
- ),
- old_point_revision BIGINT,
- new_point_revision BIGINT,
- old_content_hash CHAR(64),
- new_content_hash CHAR(64),
- caused_by_point_key VARCHAR(500),
- propagation_hop INTEGER,
- canonical_status VARCHAR(20) NOT NULL DEFAULT 'pending',
- embedding_status VARCHAR(20) NOT NULL DEFAULT 'pending',
- cache_status VARCHAR(20) NOT NULL DEFAULT 'pending',
- lightrag_status VARCHAR(20) NOT NULL DEFAULT 'pending',
- attempts INTEGER NOT NULL DEFAULT 0,
- last_error VARCHAR(1000),
- PRIMARY KEY (change_set_id, point_key)
- );
- ALTER TABLE public.governance_chunks
- ADD COLUMN IF NOT EXISTS chunk_kind VARCHAR(30),
- ADD COLUMN IF NOT EXISTS section_path VARCHAR(500),
- ADD COLUMN IF NOT EXISTS metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
- ADD COLUMN IF NOT EXISTS token_count INTEGER,
- ADD COLUMN IF NOT EXISTS lexical_text TEXT,
- ADD COLUMN IF NOT EXISTS search_vector TSVECTOR,
- ADD COLUMN IF NOT EXISTS source_locator JSONB NOT NULL DEFAULT '{}'::jsonb,
- ADD COLUMN IF NOT EXISTS primary_point_key VARCHAR(500),
- ADD COLUMN IF NOT EXISTS point_keys JSONB NOT NULL DEFAULT '[]'::jsonb,
- ADD COLUMN IF NOT EXISTS point_set_hash CHAR(64),
- ADD COLUMN IF NOT EXISTS change_set_id UUID REFERENCES public.knowledge_change_sets(id);
- CREATE INDEX IF NOT EXISTS idx_governance_chunks_search_vector
- ON public.governance_chunks USING GIN(search_vector);
- CREATE INDEX IF NOT EXISTS idx_governance_chunks_metadata
- ON public.governance_chunks USING GIN(metadata jsonb_path_ops);
- CREATE INDEX IF NOT EXISTS idx_governance_chunks_lexical_trgm
- ON public.governance_chunks USING GIN(lexical_text gin_trgm_ops);
- CREATE TABLE IF NOT EXISTS public.knowledge_chunk_embeddings (
- id UUID PRIMARY KEY,
- chunk_id UUID NOT NULL REFERENCES public.governance_chunks(id)
- ON DELETE CASCADE,
- profile_id UUID NOT NULL REFERENCES public.knowledge_embedding_profiles(id),
- embedding vector(1024) NOT NULL,
- embedding_hash CHAR(64) NOT NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- UNIQUE (chunk_id, profile_id)
- );
- CREATE INDEX IF NOT EXISTS idx_knowledge_chunk_embedding_hnsw
- ON public.knowledge_chunk_embeddings
- USING hnsw (embedding vector_cosine_ops);
- CREATE TABLE IF NOT EXISTS public.knowledge_index_projections (
- id UUID PRIMARY KEY,
- document_id UUID NOT NULL REFERENCES public.governance_documents(id)
- ON DELETE CASCADE,
- change_set_id UUID REFERENCES public.knowledge_change_sets(id)
- ON DELETE SET NULL,
- engine VARCHAR(30) NOT NULL CHECK (
- engine IN ('canonical_vector','lightrag')
- ),
- generation BIGINT NOT NULL,
- workspace VARCHAR(300) NOT NULL,
- external_document_id VARCHAR(700) NOT NULL,
- content_hash CHAR(64) NOT NULL,
- status VARCHAR(20) NOT NULL CHECK (status IN (
- 'pending','processing','ready','failed','deleting','deleted','unverified'
- )),
- attempts INTEGER NOT NULL DEFAULT 0,
- external_track_id VARCHAR(300),
- available_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- last_error VARCHAR(1000),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- UNIQUE (document_id, engine, generation, workspace)
- );
- CREATE TABLE IF NOT EXISTS public.knowledge_cache_dependencies (
- cache_key_hash CHAR(64) NOT NULL,
- point_key VARCHAR(500) NOT NULL,
- point_revision BIGINT NOT NULL,
- generation BIGINT NOT NULL,
- business_domain_uid UUID,
- expires_at TIMESTAMPTZ NOT NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (cache_key_hash, point_key, point_revision, generation)
- );
- CREATE INDEX IF NOT EXISTS idx_knowledge_cache_point
- ON public.knowledge_cache_dependencies(point_key, expires_at);
- CREATE TABLE IF NOT EXISTS public.knowledge_query_audits (
- id UUID PRIMARY KEY,
- query_hash CHAR(64) NOT NULL,
- user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
- roles JSONB NOT NULL DEFAULT '[]'::jsonb,
- business_domain_uids JSONB NOT NULL DEFAULT '[]'::jsonb,
- mode VARCHAR(30) NOT NULL,
- retriever_counts JSONB NOT NULL DEFAULT '{}'::jsonb,
- cited_points JSONB NOT NULL DEFAULT '[]'::jsonb,
- degraded_components JSONB NOT NULL DEFAULT '[]'::jsonb,
- correlation_id UUID NOT NULL,
- latency_ms INTEGER,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
- );
- CREATE TABLE IF NOT EXISTS public.knowledge_evaluation_sets (
- id UUID PRIMARY KEY,
- name VARCHAR(200) NOT NULL,
- description TEXT,
- status VARCHAR(20) NOT NULL DEFAULT 'draft',
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
- );
- CREATE TABLE IF NOT EXISTS public.knowledge_evaluation_cases (
- id UUID PRIMARY KEY,
- evaluation_set_id UUID NOT NULL REFERENCES public.knowledge_evaluation_sets(id)
- ON DELETE CASCADE,
- case_type VARCHAR(40) NOT NULL,
- query TEXT NOT NULL,
- allowed_business_domains JSONB NOT NULL DEFAULT '[]'::jsonb,
- expected_sources JSONB NOT NULL DEFAULT '[]'::jsonb,
- expected_answer_points JSONB NOT NULL DEFAULT '[]'::jsonb,
- must_refuse BOOLEAN NOT NULL DEFAULT FALSE,
- mutation JSONB,
- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
- );
- CREATE TABLE IF NOT EXISTS public.knowledge_evaluation_runs (
- id UUID PRIMARY KEY,
- evaluation_set_id UUID NOT NULL REFERENCES public.knowledge_evaluation_sets(id),
- configuration JSONB NOT NULL,
- status VARCHAR(20) NOT NULL,
- started_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
- finished_at TIMESTAMPTZ
- );
- CREATE TABLE IF NOT EXISTS public.knowledge_evaluation_results (
- run_id UUID NOT NULL REFERENCES public.knowledge_evaluation_runs(id)
- ON DELETE CASCADE,
- case_id UUID NOT NULL REFERENCES public.knowledge_evaluation_cases(id)
- ON DELETE CASCADE,
- metrics JSONB NOT NULL DEFAULT '{}'::jsonb,
- citations JSONB NOT NULL DEFAULT '[]'::jsonb,
- passed BOOLEAN NOT NULL,
- failure_reason VARCHAR(1000),
- PRIMARY KEY (run_id, case_id)
- );
- """
- )
- def downgrade() -> None:
- # Dynamic knowledge data is intentionally preserved during application rollback.
- pass
|